Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to load programmatically a layout XML file in Android?

Tags:

android

layout

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).

  • Question 1: Why? Why returns finding R.id.some_checkbox null? some_layout really is visible.
  • Question 2: How do I 'load' some_layout so I can capture some_layout into a variable, like I tried in the code snippet above?

UPDATE

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 your Checkbox is one of the inflated children, and adding the checkedchangeListener 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.

like image 434
MC Emperor Avatar asked Apr 15 '13 18:04

MC Emperor


2 Answers

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;
}
like image 62
stealthjong Avatar answered Oct 03 '22 22:10

stealthjong


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);
like image 32
Ryan Avatar answered Oct 03 '22 23:10

Ryan