I have made a layout (say my_layout.xml) which includes programmatically two other XML layout files, say some_layout.xml and another_layout.xml. my_layout.xml is drawn using setContentView(R.layout.my_layout)
.
Now I have a checkbox with ID some_checkbox
, which is defined inside some_layout.xml, and I want to give the checkbox an OnCheckedChangeListener
using setOnCheckedChangeListener()
, just like this:
CheckBox cb = (CheckBox) findViewById(R.id.some_checkbox);
cb.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
...
});
But now a NullPointerException
is thrown, because cb
is null
. I guess that's because the layout containing some_checkbox
(which is some_layout) is not loaded using setContentView(R.layout.some_layout)
.
R.id.some_checkbox
null
? some_layout really is visible.some_layout
into a variable, like I tried in the code snippet above?I've finally solved it, using the following, suggested by stealthjong:
One solution could be initialising the
checkedChangeListener
, adding a listener to your expandable listview, and when a child of the expandable listview is opened, checking if yourCheckbox
is one of the inflated children, and adding thecheckedchangeListener
if so.
I've created a method called setCheckedChangeListenerToElement(int resourceId, OnCheckedChangeListener listener)
, which saves the given resourceId (which is the ID of the element where to attach a listener to), and the given listener. Once the inflater calls the method getChildView(int groupPosition, int childPosition, boolean isLastChild, View convertView, ViewGroup parent)
, the View can be retrieved. Then, on that View, findViewById(int resourceId)
can be called, where resourceId
is the ID of the checkbox.
There are two approaches for this.
The first being adding child layouts through XML:
1. XML approach
my_layout.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical" >
<include
android:layout_width="match_parent"
android:layout_height="match_parent"
layout="@layout/child_layout1" >
</include>
<include
android:layout_width="match_parent"
android:layout_height="match_parent"
layout="@layout/child_layout2" />
</LinearLayout>
And child_layout1
and child_layout2
are just two other layouts, the second having a Button
with id mbutton
.
Your application entry should look like this:
public class MainActivity extends Activity {
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.my_layout);
View v = findViewById(R.id.mbutton);
System.out.println(v == null);
System.out.println(v.getClass().getName());
}
}
this nets me a false
and a android.widget.Button
, exactly as I suspected.
2. Programmatical approach
The other approach is the programmatical approach, and since you described it as such, I suspect this is what you did (or at least tried to):
noinclude_layout.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical" >
<RelativeLayout
android:id="@+id/inclusionlayout"
android:layout_width="match_parent"
android:layout_height="match_parent" >
</RelativeLayout>
</LinearLayout>
And a slightly larger application entrypoint:
public class MainActivity extends Activity {
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.noinclude_layout);
ViewGroup inclusionViewGroup = (ViewGroup)findViewById(R.id.inclusionlayout);
View child1 = LayoutInflater.from(this).inflate(
R.layout.child_layout1, null);
View child2 = LayoutInflater.from(this).inflate(
R.layout.child_layout2, null);
inclusionViewGroup.addView(child1);
inclusionViewGroup.addView(child2);
View v = findViewById(R.id.mbutton);
System.out.println(v == null);
System.out.println(v.getClass().getName());
}
}
Which also nets me a false
and a android.widget.Button
.
Either solution should work just fine.
UPDATE
Your expandableListView
is the problem, since the childLayout is not inflated until you actually open/expand the section (see code below for small piece of code to check current viewtree).
One solution could be initialising the checkedChangeListener
, adding a listener to your expandable listview, and when a child of the expandable listview is opened, checking if your Checkbox
is one of the inflated children, and adding the checkedchangeListener
if so.
A probably more straightforward approach would be this one:
<CheckBox
...
android:onClick="checkclicked" />
and in your Activity
, add the method public void checkclicked(View view){ ... }
ViewTree printer
private void printFullTree() {
printTree(this.getWindow().getDecorView(),0);
}
private void printTree(View view, int indent) {
System.out.print(indent(indent) + view.getClass().getName() + "; " + view.getId());
if (view instanceof ViewGroup) {
ViewGroup vg = (ViewGroup)view;
System.out.print("; children = " + vg.getChildCount() + "\n");
for (int i = 0; i< vg.getChildCount(); i++) {
printTree(vg.getChildAt(i), indent++);
}
}
else
System.out.print("\n");
}
private String indent(int indent) {
String result = "";
for (int i = 0; i < indent; i++) {
result += " ";
}
return result;
}
If some_layout.xml is included in my_layout.xml using the include tag, such as:-
<include layout="@layout/some_layout" android:id="@+id/someLayoutId" />
Then you can do the following:-
View someLayoutView = findViewById(R.id.someLayoutId);
CheckBox cb = (CheckBox) someLayoutView.findViewById(R.id.some_checkbox);
If you inflated some_layout.xml programatically, such as:-
View someLayoutView = LayoutInflater.from(mContext).inflate(
R.layout.some_layout, null);
Then you can still do as above:-
CheckBox cb = (CheckBox) someLayoutView.findViewById(R.id.some_checkbox);
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With