Search code examples
androidandroid-custom-viewcustom-componentlayout-inflater

Android custom component doesn't display its children


Hi guys I want to create a FieldSet component in Android. (similar to html) so something like that. So I want my custom component to have children and to have a board. The board part is done already, but I have some issues with display the component. I'm following this answer. Here're my parts:

FieldSet.java:

public class FieldSet extends ViewGroup {

  //... 3 constructors

  @Override
  protected void onFinishInflate()
    int index = getChildCount();
    // Collect children declared in XML.
    View[] children = new View[index];
    while(--index >= 0) {
      children[index] = getChildAt(index);
    }
    // Pressumably, wipe out existing content (still holding reference to it).
    this.detachAllViewsFromParent();
    // Inflate new "template".
    final View template = LayoutInflater.from(getContext()).inflate(R.layout.field_set, this, true);
    // Obtain reference to a new container within "template".
    final ViewGroup vg = (ViewGroup)template.findViewById(R.id.field_set_content);
    index = children.length;
    // Push declared children into new container.
    while(--index >= 0) {
      vg.addView(children[index]);
    }
  }

field_set.xml

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:duplicateParentState    ="true"
    android:layout_height="wrap_content">
    <RelativeLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_margin="15dp"
        android:id="@+id/field_set_content"
        android:duplicateParentState    ="true"
        android:background="@drawable/field_set_frame"
        android:orientation="vertical"
        android:padding="20dp">
    </RelativeLayout>
    <!--  This is the title label -->
    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginLeft="50dp"
        android:paddingLeft="50dp"
        android:paddingRight="50dp"
        android:layout_marginRight="50dp"
        android:layout_centerHorizontal="true"
        android:text="Label"
        android:background="@color/colorLoginBlue"
        android:padding="5dp"
        android:id="@+id/field_set_label"
        android:textColor="@color/colorSmallTxt" />
</RelativeLayout>

Android studio above's layout preview (seems like component's layout is completely fine):

enter image description here

Here's how I add it:

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    xmlns:fs="http://schemas.android.com/apk/res-auto"
    tools:context="my.package.DeleteThis">
    <my.package.FieldSet
        android:layout_width="match_parent"
        fs:label="Injected Label"
        android:layout_height="wrap_content"
        android:gravity="bottom">
        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:textSize="50sp"
            android:text="Inner text" />
    </my.package.FieldSet>
<TextView
    android:layout_width="match_parent"
    android:text="BELOW"
    android:layout_height="wrap_content" />
</RelativeLayout>

Android studio preview is below. (So the layout takes all space that children need. Also note that component tree shows that component TextView is added as to Layout as it should be . Nevertheless children have 0 height and width:

enter image description here

field_set_frame.xml:

<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
    <item>
        <shape android:shape="rectangle" >
            <corners android:radius="7dp" />
            <padding android:bottom="2dp"/>
            <padding android:right="2dp"/>
            <padding android:left="2dp"/>
            <padding android:top="2dp"/>
            <stroke android:width="1dp" android:color="@color/colorFieldSetBorder" />
        </shape>
    </item>
</selector>

RESULT: Just nothing:(, no comments)

enter image description here

I also read this answer and tried it, but addView didnt help - it causes recursion and it's not needed when I use addToRoot=True. I tried LinearLayout, instead of Relative - doesnt change anything. I think I screw up somewhere in LayoutInflater.from(getContext()) .inflate(R.layout.field_set, this, true);. It's also worth to point out that children occupy place. So basically if I increase the height of content that I put into my FieldSet component, it will take more height and push all components that are beneath down. I smeel this kinda of Visibility issue. Or some layer goes below another one, still height and width of chidren are zeros. Any suggestions?

Any kind of help is greatly appreciated.

Best regards,


Solution

  • When extending ViewGroup directly, you need to handle measuring and laying out the child Views yourself, or they never get sized or positioned correctly. Oftentimes, it's simpler to extend a ViewGroup subclass that already handles that stuff. In this case, you might as well extend RelativeLayout, since that's what's actually holding the internal Views, and the ViewGroup wrapping it is rather unnecessary.

    The parent RelativeLayout in the internal layout XML is then redundant, and can be replaced with <merge> tags, which will cause the child Views to be added directly to your RelativeLayout subclass when inflated.

    <merge xmlns:android="http://schemas.android.com/apk/res/android">
    
        <RelativeLayout
            android:id="@+id/field_set_content"
            ... />
    
        <TextView
            android:id="@+id/field_set_label"
            ... />
    
    </merge>
    

    We can further simplify things by setting up the internal structure in the constructor(s), and putting the child Views in the right place as they're added, rather than juggling all of that around in onFinishInflate(). For example:

    public class FieldSet extends RelativeLayout {
    
        final ViewGroup vg;
    
        public FieldSet(Context context, AttributeSet attrs) {
            super(context, attrs);
    
            LayoutInflater.from(context).inflate(R.layout.field_set, this, true);
            vg = (ViewGroup) findViewById(R.id.field_set_content);
        }
    
        @Override
        public void addView(View child, int index, ViewGroup.LayoutParams params) {
            final int id = child.getId();
            if (id == R.id.field_set_content || id == R.id.field_set_label) {
                super.addView(child, index, params);
            }
            else {
                vg.addView(child, index, params);
            }
        }
    }