Home » Android » java – Android – Writing a custom (compound) component

java – Android – Writing a custom (compound) component

Posted by: admin March 11, 2020 Leave a comment

Questions:

The Android app I’m currently developing has a main activity that has grown quite large. This is mainly because it contains a TabWidget with 3 tabs. Each tab has quite a few components. The activity has to control of all those components at once. So I think you can imagine that this Activity has like 20 fields (a field for almost every component). Also it contains a lot of logic (click listeners, logic to fill lists, etc).

What I normally do in component based frameworks is to split everything up into custom components. Each custom component would then have a clear responsibility. It would contain it’s own set of components and all other logic related to that component.

I tried to figure out how this can be done, and I found something in the Android documentation what they like to call a “Compound Control”. (See http://developer.android.com/guide/topics/ui/custom-components.html#compound and scroll to the “Compound Controls” section) I would like to create such a component based on an XML file defining the view structure.

In the documentation it says:

Note that just like with an Activity,
you can use either the declarative
(XML-based) approach to creating the
contained components, or you can nest
them programmatically from your code.

Well, that’s good news! The XML-based approach is exactly what I want! But it doesn’t say how to do it, except that it is “like with an Activity”… But what I do in an Activity is call setContentView(...) to inflate the views from XML. That method is not available if you for example subclass LinearLayout.

So I tried to inflate the XML manually like this:

public class MyCompoundComponent extends LinearLayout {

    public MyCompoundComponent(Context context, AttributeSet attributeSet) {
        super(context, attributeSet);
        LayoutInflater inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
        inflater.inflate(R.layout.my_layout, this);
    }
}

This works, except for the fact that the XML I’m loading has LinearLayout declared as the root element. This results in the inflated LinearLayout being a child of MyCompoundComponent which itself already is a LinearLayout!! So now we have a redundant LinearLayout in between MyCompoundComponent and the views it actually needs.

Can somebody please provide me with a better way to approach this, avoiding having a redundant LinearLayout instantiated?

How to&Answers:

Use merge tag as your XML root

<merge xmlns:android="http://schemas.android.com/apk/res/android">
<!-- Your Layout -->
</merge>

Check this article.

Answer:

I think the way you’re supposed to do it, is use your class name as the XML root element:

<com.example.views.MyView xmlns:....
       android:orientation="vertical" etc.>
    <TextView android:id="@+id/text" ... />
</com.example.views.MyView>

And then have your class derived from whichever layout you want to use. Note that if you are using this method you don’t use the layout inflater here.

public class MyView extends LinearLayout
{
    public ConversationListItem(Context context, AttributeSet attrs)
    {
        super(context, attrs);
    }
    public ConversationListItem(Context context, AttributeSet attrs, int defStyle)
    {
        super(context, attrs, defStyle);
    }


    public void setData(String text)
    {
        mTextView.setText(text);
    }

    private TextView mTextView;

    @Override
    protected void onFinishInflate()
    {
        super.onFinishInflate();

        mTextView = (TextView)findViewById(R.id.text);
    }
}

And then you can use your view in XML layouts as normal. If you want to make the view programmatically you have to inflate it yourself:

MyView v = (MyView)inflater.inflate(R.layout.my_view, parent, false);

Unfortunately this doesn’t let you do v = new MyView(context) because there doesn’t seem to be a way around the nested layouts problem, so this isn’t really a full solution. You could add a method like this to MyView to make it a bit nicer:

public static MyView create(Context context)
{
    return (MyView)LayoutInflater.fromContext(context).inflate(R.layout.my_view, null, false);
}

Disclaimer: I may be talking complete bollocks.