0

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,

Community
  • 1
  • 1
deathangel908
  • 8,601
  • 8
  • 47
  • 81
  • You're extending `ViewGroup`, so you'll have to take care of measuring and laying out the child `View`s yourself. Alternatively, you could instead extend a `ViewGroup` subclass that already handles its children like you want; e.g., `FrameLayout`, `LinearLayout`, `RelativeLayout`, etc. – Mike M. Dec 08 '16 at 23:11
  • Thanks for your response @Mike. I tried extending different classes even before posting this question: `View`, `ViewGroup`, `LinearLayout`, `RelativeLayout`, `FrameLayout`. It changes nothing. Should I implement some more methods that handle drawing? (If yes which ones and what should they contain) – deathangel908 Dec 09 '16 at 09:15
  • Not at this point, probably. Measuring and laying out the children can be a pain. If I were you, I'd first get this working in a class that already handles that stuff. E.g., there's really no reason not to extend `RelativeLayout`, at least, then get rid of the redundant one that's currently the parent in the inflated layout, and wrap the inner `RelativeLayout` and `TextView` in `` tags instead. Also, juggling the `View`s like that after inflation is a little inefficient, and not really necessary. – Mike M. Dec 09 '16 at 09:49
  • You could instead inflate the inner layout in the constructor(s), intercept the addition of the children, and stick them in the right spot from the get go. [This post](http://stackoverflow.com/q/36946173) would be of some help, if you wanna give that a shot. Beyond that, for custom `ViewGroup`s like this, I'd recommend not wrapping the width or height when testing, possibly using definite measurements for those, also using contrasting background colors, ensuring text colors aren't blending, etc. Anything that makes it easier to see what's going on. – Mike M. Dec 09 '16 at 09:49
  • 1
    @Mike, thank you a lot for your help, you just made my day. So I made 3 mistakes here: 1) I should inflate layout in constructor. 2) I should used merged as a parent element in my xml 3) I should override `addView` method for adding children instead of reattaching them manually, if you post your answer below I mark it as accepted and Vote it up. – deathangel908 Dec 09 '16 at 10:50

1 Answers1

8

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);
        }
    }
}
Mike M.
  • 38,532
  • 8
  • 99
  • 95