64

I want to inflate a XML-Layout-File in a custom ViewGroup Class, my Problem is that it produces just a empty screen. Doing the same in the Activity Class works fine. Here is my simple XML-Layout-File:

shownumberlayout.xml:

    <RelativeLayout 
        xmlns:android="http://schemas.android.com/apk/res/android"
        android:layout_width="fill_parent" 
        android:layout_height="fill_parent"
        android:background="#FFFFFF" 
        android:id="@+id/layoutForNumber">

        <TextView 
            android:layout_width="wrap_content"
            android:layout_height="wrap_content" 
            android:id="@+id/tvNumber"
            android:layout_centerHorizontal="true" 
            android:textColor="#000000" 
            android:text="Test" 
            android:layout_centerVertical="true" 
            android:textSize="30dip">
        </TextView>

    </RelativeLayout>

Here is the working Version, inflating the shownumberlayout.xml in the Activity ShowNumber:

ShowNumber.class

public class ShowNumber extends Activity {
    /** Called when the activity is first created. */

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        LayoutInflater inflater = (LayoutInflater)getSystemService(Context.LAYOUT_INFLATER_SERVICE);
        ViewGroup vg = (ViewGroup) inflater.inflate(R.layout.shownumberlayout, null);
        setContentView(vg);
    }
}

This shows a White Background with the black Text “Test” centered.

Now the Version inflating the xml in the Custom ViewGroup-Class:

ViewGroup.class
public class ViewNumber extends ViewGroup {

    private LayoutInflater inflater;

    public ViewNumber(Context context) {
        super(context);
        // TODO Auto-generated constructor stub
        initView(context);
    }

    public ViewNumber(Context context, AttributeSet attrs) {
        super(context, attrs);
        // TODO Auto-generated constructor stub
        initView(context);
    }

    public ViewNumber(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
        // TODO Auto-generated constructor stub
        initView(context);
    }

    private void initView(Context context){
        inflater = (LayoutInflater)context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
        inflater.inflate(R.layout.shownumberlayout, null);  
    }

    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        // TODO Auto-generated method stub
    }

}

ShowNumber.class public class ShowNumber extends Activity { /** Called when the activity is first created. */

@Override
public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    ViewGroup vg = new ViewNumber(this); 
    setContentView(vg);
}

}

Im doing it basically like in this Answer explained. This just produces a Empty Black Screen. What I am doing wrong?

UPDATE 1

@Konstantin I applied your changes but still just a blank screen, i also made a log-ouput for getting the number of children. It remains always 1, even i add one more Textview to the XML-Layout-File. Before the Changes it remains always 0.

public class ViewNumber extends RelativeLayout {
...
private void initView(Context context){
        //inflater = (LayoutInflater)context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
        //inflater.inflate(R.layout.shownumberlayout, null);
        View.inflate(context, R.layout.shownumberlayout,this); 
        Log.v("ViewNumber", "Number of Child: " + this.getChildCount());//output is 1,before it remains 0
    }
...
}

@Sankar This is the Logcat, after the Changes from Konstantin:

12-16 09:24:23.606: DEBUG/AndroidRuntime(8951): >>>>>>>>>>>>>> AndroidRuntime START <<<<<<<<<<<<<<
12-16 09:24:23.606: DEBUG/AndroidRuntime(8951): CheckJNI is OFF
12-16 09:24:23.606: DEBUG/dalvikvm(8951): creating instr width table
12-16 09:24:23.656: DEBUG/AndroidRuntime(8951): --- registering native functions ---
12-16 09:24:23.916: DEBUG/AndroidRuntime(8951): Shutting down VM
12-16 09:24:23.916: DEBUG/dalvikvm(8951): Debugger has detached; object registry had 1 entries
12-16 09:24:23.916: INFO/AndroidRuntime(8951): NOTE: attach of thread 'Binder Thread #3' failed
12-16 09:24:24.076: DEBUG/AndroidRuntime(8960): >>>>>>>>>>>>>> AndroidRuntime START <<<<<<<<<<<<<<
12-16 09:24:24.076: DEBUG/AndroidRuntime(8960): CheckJNI is OFF
12-16 09:24:24.076: DEBUG/dalvikvm(8960): creating instr width table
12-16 09:24:24.126: DEBUG/AndroidRuntime(8960): --- registering native functions ---
12-16 09:24:24.376: INFO/ActivityManager(78): Starting activity: Intent { act=android.intent.action.MAIN cat=[android.intent.category.LAUNCHER] flg=0x10000000 cmp=org.customview.harold/.ShowNumber }
12-16 09:24:24.426: DEBUG/AndroidRuntime(8960): Shutting down VM
12-16 09:24:24.426: DEBUG/jdwp(8960): Got wake-up signal, bailing out of select
12-16 09:24:24.426: DEBUG/dalvikvm(8960): Debugger has detached; object registry had 1 entries
12-16 09:24:24.456: INFO/AndroidRuntime(8960): NOTE: attach of thread 'Binder Thread #3' failed
12-16 09:24:24.456: VERBOSE/ViewNumber(8923): Number of Child: 1
12-16 09:24:24.496: VERBOSE/RenderScript_jni(164): surfaceDestroyed
12-16 09:24:24.526: INFO/ActivityManager(78): Displayed activity org.customview.harold/.ShowNumber: 104 ms (total 104 ms)
12-16 09:24:24.576: DEBUG/dalvikvm(158): GC_FOR_MALLOC freed 10631 objects / 526248 bytes in 52ms
12-16 09:24:34.606: DEBUG/dalvikvm(164): GC_EXPLICIT freed 1776 objects / 106960 bytes in 91ms

UPDATE 2

The Content is finally showing correctly up. The missing thing was to override the Method onLayout (thanks to Franco) in the RelativeLayout-Sublcass:

public class ViewNumber extends RelativeLayout {
...

    @Override
            protected void onLayout(boolean changed, int l, int t, int r, int b) {
                // TODO Auto-generated method stub
                for(int i = 0 ; i < getChildCount() ; i++){
                    getChildAt(i).layout(l, t, r, b);
                }
            }
...
}

Note: Later you should also override the Method onMeasurement(), but currently the content is also showing correctly without overriding it.

Now the solution for the Method initView from Franco do not align the TextView in the Center, but puts it in the top left corner. The solution from Konstantin puts it correctly in the Center of the View:

public class ViewNumber extends RelativeLayout {
...
private void initView(Context context){
        View.inflate(context, R.layout.shownumberlayout,this); 
    }
...
}
Community
  • 1
  • 1
Harold
  • 927
  • 2
  • 7
  • 13
  • @Harlod: Please See your Logcat and tell what Message you caught inj Your Logcat? – Sankar Ganesh PMP Dec 15 '10 at 10:32
  • yes Harold, you're right, i tried it and see it in the hierarchy view tool and the TextView is aligned in the top left corner with width and height = 0 – Franco Dec 16 '10 at 04:10

6 Answers6

49

I don't know what's going on in these answers.. I think calling onLayout manually is a bit crazy.

The LayoutInflater.inflate function is rather straight forward. If you're using the one that accepts the 3rd boolean argument (attachToRoot) and it's true, it will add the views from your XML to your parent view (the 2nd argument). If you're using the one that accepts only 2 arguments, it will pass true to attachToRoot by default if you provide a parent that isn't null.

In any case, most of the mess you're experiencing is because the views from your XML are added to your custom view. Since your XML has a RelativeLayout root and your custom view is also a RelativeLayout - you're getting a relative layout inside a relative layout.

This is probably not what you want.

The answer given by Konstantin makes sense, but you're wasting a view because you're placing a RelativeLayout inside a FrameLayout. Since Android can't hold too many nested views, it's a good idea to optimize and not add this unnecessary FrameLayout.

I suggest keeping your custom view as RelativeLayout and changing your XML root to <merge>. Then use the inflate form which adds the XML views to your parent - like View.inflate(context,int,this)

The purpose of the <merge> tag is to skip the root node in the XML.. look it up.

talkol
  • 12,564
  • 11
  • 54
  • 64
  • 3
    it is indeed. For everyone who is still confused about layout inflating here is great article which pretty well explains how it should be done: http://www.doubleencore.com/2013/05/layout-inflation-as-intended/ – fgeorgiew Nov 16 '13 at 11:50
  • 1
    @Ran This is not a real answer. This is only a complaint. A real answer would have be brief, straightforward and would have shown a code snippet like the accepted one does, or like Konstantin's one. _Yet +1 to that complaint._ – 1111161171159459134 Aug 22 '15 at 03:10
36

Your layout is inflated nowhere.. make your ViewNumber extend FrameLayout and inflate into it:

public class ViewNumber extends FrameLayout {

    public ViewNumber(Context context) {
        super(context);
        initView(context);
    }

    public ViewNumber(Context context, AttributeSet attrs) {
        super(context, attrs);
        initView(context);
    }

    public ViewNumber(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
        initView(context);
    }

    private void initView(Context context){
        View.inflate(context, R.layout.shownumberlayout, this);  //correct way to inflate..
    }
}

UPD: Also you do not need to override onLayout method, that looks very incorrect.. at least call super.onLayout() on the very start:

@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
    super.onLayout(changed, left, top, right, bottom);
}
Konstantin Burov
  • 68,980
  • 16
  • 115
  • 93
  • I applied your changes, but still a blank screen. See my updated post. – Harold Dec 16 '10 at 02:02
  • @Harold: I think that's because your implementation of onLayout is completely incorrect, see update. – Konstantin Burov Dec 16 '10 at 04:20
  • @Harold: oh I see that @Franco already pointed that out. But if you look carefully, my original snippet has no onLayout method in it, probably I should be more verbose next time :) – Konstantin Burov Dec 16 '10 at 04:26
  • @KonstantinBurov Your choice of variable for the parameter is hard to understand at first. Could you change them to Left, top, right, bottom instead of l,t,r,b. :) – codingbiz Mar 15 '13 at 21:35
  • 1
    @codingbiz Thx, updated. Though I believe the original code was generated by IDE based on the method signature. – Konstantin Burov Mar 16 '13 at 18:10
33

Did you try this?

private void initView(Context context){
        inflater = (LayoutInflater)context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
        this.addView(inflater.inflate(R.layout.shownumberlayout, null));  
    }

EDIT: I respond quickly... a ViewGroup has the responsibility to prepare the layout for their children, in the layout method try this:

        @Override
        protected void onLayout(boolean changed, int l, int t, int r, int b) {
            // TODO Auto-generated method stub
            for(int i = 0 ; i < getChildCount() ; i++){
                getChildAt(i).layout(l, t, r, b);
            }
        }
DavidDraughn
  • 1,110
  • 1
  • 8
  • 15
Franco
  • 7,385
  • 3
  • 27
  • 22
  • YES! Its working, the content is showing..Thank you very much! I will update my Post. – Harold Dec 16 '10 at 03:35
  • all right! be sure that you override both methods, onLayout and onMeasurement, always that you subclass the ViewGroup class. – Franco Dec 16 '10 at 03:37
  • 1
    This particular answer gave me a problem when instantiating several views from the same XML. I fixed this problem replacing the onLayout method in your answer with super(changed, l, t, r, b); Otherwise it's just great and I thank you for it! – Leo supports Monica Cellio Sep 17 '13 at 01:23
  • Hey, I have an XML-File with a LinearLayout and 2 nested TextViews inflated as Franco shows in his post. The inflate works but in the onLayout-method I get a NullpointerException. To make it precise: getChildAt(i) gives me the LinearLayout but when I call getChildAt(i).layout(l, t, r, b) I get a NullPointerException. Can anyone give me a hint why? – moony Oct 23 '14 at 13:05
2

Overriding onLayout is necessary.when you create a object of ViewNumber(ViewGroup vg = new ViewNumber(this)),it is like inflating xml below:

<ViewNumber
           I have no child :(
</ViewNumber>

so if you don't override onLayout,ViewNumber's onLayout can't find any child,then it will be blank.

@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
    super.onLayout(changed, left, top, right, bottom);
}

after overriding,it will use the onLayout method of ViewNumber's parent, if your codes is:

public class ViewNumber extends RelativeLayout {
...
private void initView(Context context){
        View.inflate(context, R.layout.shownumberlayout,this); 
    }
...
}

then it's like inflating xml below

  <RelativeLayout
    <RelativeLayout 
            your xml file
                   />
  </RelativeLayout>

there is one more RelativeLayout.To sovle this,you may write:

public class ViewNumber extends RelativeLayout {
...
private void initView(Context context){
        inflater.inflate( R.layout.login_view_layout, null );
              //change (..,this) to (..,null)
    }
...
}

However, something unexpected will happen.android:layout_xxx=".."of your xml file may change(this is why your text is put in the top left corner). To learn more and a better solution,you can see talkol' answer and fgeorgiew's comment under it :)

1

Try this one:

 LayoutInflater inflater = (LayoutInflater) context.getSystemService( Context.LAYOUT_INFLATER_SERVICE );
 inflater.inflate( R.layout.login_view_layout, null );
Coding Enthusiast
  • 3,865
  • 1
  • 27
  • 50
ediBersh
  • 1,135
  • 1
  • 12
  • 19
  • 1
    Could you please elaborate more your answer adding a little more description about the solution you provide? – abarisone Jun 22 '15 at 14:43
1

I come across the same question and answer my version in Kotlin:

You can add your customise views by xml or code. The difference is the constructor.

YourView.kt

class YourView(context: Context, attrs: AttributeSet) : LinearLayout(context, attrs) {

    init {
        LayoutInflater.from(context).inflate(R.layout.your_view, this, true)
     orientation = HORIZONTAL
    }
}

If you want to create your layout from code then just use:

class YourView(context: Context) : LinearLayout(context)

your_view.xml

<merge xmlns:android="http://schemas.android.com/apk/res/android">


        <TextView
            android:id="@+id/searchView"/>

        <Button
            android:id="@+id/button2"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="Cancel" />


</merge>

What's the <merge> here for?

If you use YourView in the layout like:

<LinearLayout> 
   <YourView>
   </YourView>
</LinearLayout>

Then the actual view hierarchy is:

with <merge>

   <LinearLayout>
      <TextView> </TextView>
      <Button> </Button>
   </LinearLayout >

without <merge> (You need to use ViewGroup like <LinearLayout>)

 <LinearLayout> 
       < LinearLayout >
          <TextView> </TextView>
          <Button> </Button>
       </LinearLayout >
    </LinearLayout>
William Hu
  • 15,423
  • 11
  • 100
  • 121