My question is about Android/Java.
I wanna access to other views from my custom view.
My main.xml:
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="#+id/mainLinearLayout1">
<org.javaforum.input
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:inputType="textEmailAddress"
required=""
android:hint="Enter your E-Mail"
oninvalid="this.setCustomValidity('SO - Please enter a correct E-Mail!!!')"/>
<org.javaforum.input
android:layout_width="wrap_content"
android:layout_height="wrap_content"
inputType="submit"/>
</LinearLayout>
So when the second "input" view is clicked, I wanna check if there exists an attribute named "required" at the first "input" view.
I tried this in my input.java:
public class input extends TextView{
public input(Context context, AttributeSet attr) {
super(context, attr, getIdentifier(context,attr));
}
#Override
public void onFinishInflate() {
setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
LinearLayout layout = (LinearLayout)findViewById(R.id.mainLinearLayout1);
View v = null;
for(int i=0; i<layout.getChildCount(); i++) {//Error
//Code
}
}
});
}
}
But I get the following error:
java.lang.NullPointerException: Attempt to invoke virtual method 'int android.widget.LinearLayout.getChildCount()' on a null object reference
I don't understand why it's null. It should be already created, right?
So how can I get an LinearLayout reference of my main.xml at my custom view correctly?
To solve the null pointer exception, you will need to search for the other widget in the parent LinearLayout. You can use View.getParent() to identify the parent. Since the view you are looking for is a sibling of the current view, you will find it in the parent.
As for determining if a certain attribute is set, I recommend that the value be captured by its view and a method provided to present that value upon request. Say the value is your "required" value then capture that value locally and provide a method, getRequiredValue(), for instance, to expose it.
You can use
LinearLayout layout = (LinearLayout) getRootView().findViewById(R.id.mainLinearLayout1);
if you only call findViewById inside custom view it only lookup the child view.
This should be called in the Activity's onCreate instead of TextView's onFinishInflate.
First give the submit button an id eg btn_submit, then in onCreate
Button btn_submit = (Button)findViewById(R.id.btn_submit);
btn_submit.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
LinearLayout layout = (LinearLayout)findViewById(R.id.mainLinearLayout1);
View v = null;
for(int i=0; i<layout.getChildCount(); i++) {
//Code
}
}
});
Related
for my app I need to show a bottom dynamic dialog. I found this solution:
public class bottomBar extends BottomSheetDialogFragment {
public TextView txtLabel;
#SuppressLint("RestrictedApi")
#Override
public void setupDialog(Dialog dialog, int style) {
super.setupDialog(dialog, style);
View contentView = View.inflate(getContext(), R.layout.bottom_bar, null);
dialog.setContentView(contentView);
txtLabel = contentView.findViewById(R.id.txtLabel);
}
wuith bottom_bar.xml like this:
<TextView
android:id="#+id/txtLabel"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="10dp" />
From my MainActivity I call my dialog, and I try to change the txtLabel text this way:
bottomBar bb = new bottomBar();
bb.txtLabel.setText(String.format("mytext %s %s?" , var1, var2));
bb.show(getSupportFragmentManager(), bb.getTag());
When I try to call setText() the app crash:
java.lang.NullPointerException: Attempt to invoke virtual method 'void android.widget.TextView.setText(java.lang.CharSequence)' on a null objectbb.txtLabel
Can you help me?
note: if I change order I have the same result:
bb.show(getSupportFragmentManager(), bb.getTag());
bb.txtLabel.setText(String.format("mytext %s %s?" , var1, var2));
Your inflated layout is bottom_bar.xml .But your layout name is layout_bar.xml . Check that properly
When you create a new bottomBar() then your attribute public TextView txtLabel; isn't initialized. you should try to show() it first in order to initialized this attribute.
I have IconText that has image and text views inside it, both class and xml.
I then populate the Spinner inside of the MainActivity with these IconTexts, using extended BaseAdapter (IconTextAdapter) as adapter.
Now, IconText works fine (shows as it should).
Spinner however doesn't.
When I start the app, it shows the first IconText as it should.
When I open the choose dialog, everything is showing as it should.
When I select another item, the choose dialog collapses and spinner displays no IconText (ie. only "arrow down" for opening choose dialog).
I can still open the choose dialog and choose another.
I've noticed that if I exit from app (return button, not really quiting the app) and enter again that the proper IconText is shown.
I guess that the fault lies with the adapter?
I will try avoiding posting a lot of code [:
IconText.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="horizontal">
<ImageView android:id="#+id/it_image"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:src="#drawable/home"/>
<TextView
android:id="#+id/it_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingLeft="20sp"
android:text="ABC"
android:textColor="#color/black_overlay"
android:textSize="20sp"
android:textStyle="bold" />
</LinearLayout>
IconText.java
public class IconText {
static LayoutInflater inflator;
public ImageView icon;
public TextView text;
public View view;
public IconText(String title, int icon_id){
Log.d("IconText", "Create");
view = inflator.inflate(R.layout.icon_text, null);
text = (TextView) view.findViewById(R.id.it_text);
icon = (ImageView) view.findViewById(R.id.it_image);
text.setText(title);
icon.setImageResource(icon_id);
}
public static void initInflator(Context context){
if(inflator != null) return;
Log.d("IconText", "Init inflator");
inflator = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
}
}
IconTextAdapter.java
public class IconTextAdapter extends BaseAdapter implements SpinnerAdapter{
public ArrayList<IconText> items;
public IconTextAdapter(){
super();
items = new ArrayList<>();
}
#Override
public int getCount() {
return items.size();
}
#Override
public Object getItem(int arg0) {
return items.get(arg0);
}
#Override
public long getItemId(int position) {
return items.get(position).view.getId();
}
#Override
public View getView(int position, View convertView, ViewGroup parent) {
return items.get(position).view;
}
}
MainActivity, relevant code
void initFilter(){
Log.d("MainAction", "Find Filter");
filter = (Spinner) findViewById(R.id.filter);
Log.d("MainAction", "Create Adapter");
IconTextAdapter adapter = new IconTextAdapter();
Log.d("MainAction", "Create items");
for(int i=0; i<ETypes.names.length; i++){
IconText it = new IconText(ETypes.names[i], ETypes.icons[i]);
adapter.items.add(it);
}
Log.d("MainAction", "Setting adapter");
filter.setAdapter(adapter);
}
ETypes names[string] and icons[id] are static.
Inflator is initialized succesfully.
I haven't worked much in Android. If it were Java/Swing, I guess I would just call a redraw or something.
I know there are some bad practices here (all public variables, and so on). This code is still in early prototype stage, it will be fixed soon. I'm not looking for optimization, just for the solution to my problem.
Update 1:
So I saw I didn't implement getDropDownView so I did, the same code as getView (no need to post it?).
Also I made an experiment: At the end of IconText contrusctor I added
view = text
And it works just fine (showing only text).
I guess this pinpoints that the problem originates from custom view?
Update 2:
Did another experiment with IconText, setting view = icon; and it doesn't behave as it should, ie. it behaves like it's a custom view.
Doesn't really solve this specific bug, but it solves my problem.
Custom Image and Text View in Spinner Solution.
To update the list you must call
adapter.notifyDataSetChanged()
after adding new data to your adapter
Please refer to the following
https://developer.android.com/reference/android/widget/BaseAdapter.html#notifyDataSetChanged()
I have a HorizontalScrollView containing a LinearLayout which is used to display a file tree ribbon
<HorizontalScrollView
android:id="#+id/content_ribbon_scrollview"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:scrollbars="none">
<LinearLayout
android:id="#+id/content_file_ribbon"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="horizontal">
</LinearLayout>
</HorizontalScrollView>
Items are added to the LinearLayout as the user navigates to them
void addRibbonItem(final Node node) {
final TextView view = (TextView) getLayoutInflater().inflate(R.layout.shard_ribbon_item, mRibbon, false);
view.setText(node.getName());
mRibbon.addView(view);
mRibbon.postDelayed(() -> mRibonScrollView.fullScroll(View.FOCUS_RIGHT), 17);
When an item is clicked, all of the items to the right of it should be removed.
I have tried to do this with the removeViewsInLayout method, as well as repeatedly removing the final view.
(Ignore the lack of bounds checks)
view.setOnClickListener(v -> {
mRibbon.removeViewsInLayout(mRibbon.indexOfChild(view) + 1, mRibbon.getChildCount());
});
view.setOnClickListener((v) -> {
final int index = mRibbon.indexOfChild(view);
for(int i = 0; i < index; i++) mRibbon.removeViewAt(mRibbon.getChildCount());
});
Both of these methods give the same exception
java.lang.NullPointerException: Attempt to invoke virtual method 'void android.view.View.unFocus(android.view.View)' on a null object reference
at android.view.ViewGroup.removeViewsInternal(ViewGroup.java:4691)
at android.view.ViewGroup.removeViewsInLayout(ViewGroup.java:4539)
at com.tpb.projects.repo.content.ContentActivity.lambda$-com_tpb_projects_repo_content_ContentActivity_lambda$1(ContentActivity.java:67)
Any help is appreciated.
Edit:
Removing and re-adding the Views does work, but I don't believe that this is a good solution.
view.setOnClickListener(v -> {
final ArrayList<View> views = new ArrayList<>();
for(int i = 0; i <= mRibbon.indexOfChild(view); i++) {
views.add(mRibbon.getChildAt(i));
}
mRibbon.removeAllViews();
for(View item : views) {
mRibbon.addView(item);
}
});
Try this:
1. Use the child element to get a reference to the parent.
2. Cast the parent to a ViewGroup so that you get access to the removeView method and use that.
((ViewGroup)scrollChildLayout.getParent()).removeView(scrollChildLayout);
and not like this:
//scrollView.removeView(scrollChildLayout);
I'm building my first app based on material from http://javatechig.com/video/json-feed-reader-in-android.
Everything goes ok so far, but I found one bug with ListView elements, which I can not manage to resolve by myself :(
I have extended list_row_layout.xml by 2 fields:
<Button
android:layout_width="wrap_content"
android:layout_height="20dp"
android:text="komcie"
android:textSize="11sp"
android:id="#+id/loadComments"
android:layout_gravity="center|bottom"
android:background="#bbb"
android:layout_marginLeft="5dp"
android:enabled="true"
android:clickable="true"
android:onClick="clickedLoadComments"
android:elegantTextHeight="true"
android:layout_toRightOf="#id/thumbImage"
android:layout_below="#+id/content"
android:padding="1px" />
<ListView
android:id="#+id/comment_list"
android:layout_toRightOf="#id/thumbImage"
android:layout_below="#+id/content"
android:paddingTop="5dp"
android:layout_marginTop="0dp"
android:paddingLeft="5dp"
android:layout_width="fill_parent"
android:layout_height="0dp"
android:cacheColorHint="#00000000"
android:dividerHeight="1dp"
android:focusable="false"
android:listSelector="#drawable/list_selector_flatcolor"
android:visibility="invisible" />
Button.android:onClick="clickedLoadComments" function load Json with elements into ListView/comment_list. It works quite fine. But if there are more elements than could be displayed on screen (~8 elements) there is a bug. Comments from clicked element are loaded into every 8th element in a ListView.
Some code:
public void clickedLoadComments(View v)
{
try {
View parent = (View)v.getParent();
ViewHolder t = (ViewHolder) parent.getTag();
if( parent != null ) {
this.loadCommentsForLeaf(parent);
}
}
catch (Exception e)
{
e.printStackTrace();
}
}
protected void loadCommentsForLeaf( View view )
{
String tmpUrl = "http://some.url.com/Ajax/LoadComments?lid=" + this.currentLeafInUse;
JSONObject commentsJson = this.getJSONFromUrl(tmpUrl);
this.parseJsonComments(commentsJson);
if( commentsJson != null )
this.updateCommentList(view);
}
public void updateCommentList( View view) {
commentListView = (ListView) view.findViewById(R.id.comment_list);
commentListView.setVisibility(View.VISIBLE);
CommentListAdapter cla = new CommentListAdapter(this, this.commentList.get(this.currentLeafInUse));
commentListView.setAdapter(cla);
// Set list height.
ViewGroup.LayoutParams params = commentListView.getLayoutParams();
params.height = setListViewHeightBasedOnItems(commentListView) + 20;
commentListView.setLayoutParams(params);
commentListView.requestLayout();
}
CustomListAdapter.java code is mostly the same as the one in tutorial.
I would really appreciate help as I have spent many hours figuring it out with not success :(
This is just a guess. You might post your Adapter code and your parseJsonComments also if this does not work.
The Cause:
The problem you are describing might be caused due to the recycling and the reusage of Views. Take a look at this image from http://android.amberfog.com
As you can see the 1. items is reused and becomes the 8. item after scrolling.
Let's assume that Item 1 has an OnClickListener which updates a Text of the item.
For example we set the text to "clicked" after the OnClickListener is triggered.
Because item 1 is reused to create item 8, item 8 will also display the text "clicked".
The Solution:
The usual way is to save all states/content in a List(or whatever) and update everything in the getView call. So if you want to update text:
public View getView(int position, View convertView, ViewGroup parent) {
ViewHolder holder;
...
holder.textView.setText(jsonTexts[position]);
...
return convertView;
}
And if you want to update an item just update the List in your Adapter which holds the content/JsonObjects(etc.) and call notifyDataSetChanged.
public void updateCommentList(JSONObject commentsJson, int position) {
// does not exist you might create something
//like that in your Adapter class
commentListAdapter.updateItem(commentsJson,position);
commentListAdapter.notifyDataSetChanged();
}
After i populate the listview i call this method:
private void registerClickCallback() {
ListView list = (ListView) findViewById(R.id.lv);
list.setOnItemClickListener(new AdapterView.OnItemClickListener() {
#Override
public void onItemClick(AdapterView<?> parent, View viewClicked,
int position, long id) {
String xx = position+ ":" + id;
//then you can do what ever you want
}
});
}
I have the following layout defined in useful_numbers_item_fragment.xml:
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:id="#+id/call_linear_layout">
<LinearLayout
android:layout_width="0dp"
android:layout_height="wrap_content"
android:orientation="vertical">
<TextView
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:id="#+id/useful_nums_item_name"/>
<TextView
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:id="#+id/useful_nums_item_value"/>
</LinearLayout>
<ImageButton
android:layout_width="0dp"
android:layout_height="wrap_content"
android:src="#drawable/call"
android:id="#+id/call_btn"
android:onClick="callNumber"/>
</LinearLayout>
I dynamically populate the two text views in a class called UNItemListFragment.java
in the onCreate method:
public void onCreate(Bundle savedInstance) {
super.onCreate(savedInstance);
if (getArguments().containsKey(Constants.UNItem.GROUP_ID)) {
simpleCursorAdapter = new SimpleCursorAdapter(getActivity(), R.layout.useful_numbers_item_fragment, null,
new String[]{Constants.UNItem.NAME, Constants.UNItem.VALUE},
new int[]{R.id.useful_nums_item_name, R.id.useful_nums_item_value}, 0);
setListAdapter(simpleCursorAdapter);
getLoaderManager().initLoader(0, getArguments(), this);
}
}
For each number if i click on the button i want to make a phone call by
calling the callNumber method when the user clicks the button:
public void callNumber(View view) {
Intent callIntent = new Intent(Intent.ACTION_CALL);
TextView unItemVal = (TextView) findViewById(R.id.useful_nums_item_value);
String phoneNumber = unItemVal.getText().toString();
callIntent.setData(Uri.parse("tel:" + phoneNumber));
startActivity(callIntent);
}
It is ok when I click the first button in the list, but when I click on the other buttons
it continues calling the number defined in the first row...
Any idea how to resolve this?
The problem is that this line:
TextView unItemVal = (TextView) findViewById(R.id.useful_nums_item_value);
is executed on the activity, so the findViewById will always return the first item with that id, which is likely the first item in the list.
The best way to fix this would be to override the adapter and add a tag containing the phone number to the view. A quick way to fix this would be to tag along in the view hierarchy, like so:
public void callNumber(View view) {
if( view != null ) { // view is the button tapped
View parent = view.getParent(); // this should be the LinearLayout
if( parent instanceof LinearLayout ) {
TextView unItemVal = (TextView) ((LinearLayout)parent).findViewById(R.id.useful_nums_item_value);
if( unItemVal != null ) {
Intent callIntent = new Intent(Intent.ACTION_CALL);
String phoneNumber = unItemVal.getText().toString();
callIntent.setData(Uri.parse("tel:" + phoneNumber));
startActivity(callIntent);
}
}
}
}
This would find the parent for the button that was clicked, and then find the text-view containing the number within that ViewGroup.
Using findViewById() will return the first view in the activity or fragment with the specified id. If this is a ListView, it will correspond to the first row.
There are many ways to work around this problem. The quickest one (but certainly not the prettiest one, since it depends on the layout) would be to use findViewById() relative to the LinearLayout that contains the list item. Assuming that view is the ImageButton, it would be somthing like:
((View)view.getParent()).findViewById(R.id.useful_nums_item_value)
A more elegant solution would be to set a tag in the adapter's getView(), containing the data you need (in this case, the phone number to call).