1

Trying to write better-encapsulated custom views and composite controls.

The old way I would do this was to subclass a ViewGroup such as LinearLayout, like so...

public class RadioButton extends LinearLayout
{
    private RadioButtonView radioButtonView;
    private TextView        textView;

    public RadioButton(Context context)
    {
        super(context);
        commonInit(context, null, 0);
    }

    public RadioButton(Context context, AttributeSet attrs)
    {
        super(context, attrs);
        commonInit(context, attrs, 0);
    }

    public RadioButton(Context context, AttributeSet attrs, int defStyle)
    {
        super(context, attrs, defStyle);
        commonInit(context, attrs, defStyle);
    }

    private void commonInit(Context context, AttributeSet attrs, int defStyle)
    {
        View.inflate(context, R.layout.part_radio_button, this);

        radioButtonView = (RadioButtonView) findViewById(R.id.radioButtonView);
        textView        = (TextView)        findViewById(R.id.textView);

        final TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.RadioButton, defStyle, 0);

        String text = a.getString(R.styleable.RadioButton_text);
        setText(text != null ? text : "Unset");

        boolean isSelected = a.getBoolean(R.styleable.RadioButton_isSelected, false);
        setIsSelected(isSelected);

        a.recycle();
    }

    public String getText()
    {
        return textView.getText().toString();
    }
    public void setText(String newValue) { textView.setText(newValue); }

    public boolean getIsSelected()
    {
        return radioButtonView.getIsSelected();
    }
    public void setIsSelected(boolean newValue){ radioButtonView.setIsSelected(newValue);}
}

The layout R.layout.part_radio_button would look like this (note the Merge tag)...

<?xml version="1.0" encoding="utf-8"?>
<merge xmlns:android="http://schemas.android.com/apk/res/android">

    <com.citi.mobile.reusable.ui.RadioButtonView
        android:id="@+id/radioButtonView"
        style="@style/DefaultRadioButtonView" />

    <TextView
        android:id="@+id/textView"
        style="@style/DefaultRadioButtonText" />

</merge>

This worked really well ...at first! It looked like it properly handled automatic loading the layout so the user could simply 'new-up' the control, or just reference it in a layout file themselves.

But soon it became evident that there were issues with this approach.

  1. DataBinding which requires a root <layout /> tag.

  2. The <merge /> tag cannot be a child of the tag nor vice-versa.

  3. I couldn't define the default properties for the root control in the XML and had to do it programmatically in the constructor.

  4. Since I was setting them in the constructor, I also couldn't style them since the constructor was overriding their values. I couldn't simply set the constructor-set values first, because until they were loaded from the layout, there was nothing to set.

  5. I also realized by subclassing LinearLayout (or any other such control) I was also exposing that LinearLayout to whomever was using this, meaning they could change the orientation, etc.

Because of the above, especially #5, I realized I should do the following instead:

  1. Don't subclass LinearLayout directly as you're exposing it to the outside world when you do. Instead find a different root control to subclass.

  2. Because of the change in #1, you can now move LinearLayout back as the root of the layout portion of the layout.xml file (either directly, or under a <layout /> tag when data-binding

  3. Make the control you've chosen in #1 expose only the bare essentials (i.e. width, height, and whatever attributes you specifically want to expose.)

At this point, I'm starting to think what I need to do is subclass ViewGroup, or perhaps even View (as ViewGroup itself does) and enforce the one-child policy, but I'm not 100% sure, hence this question.

Note: In C#/WPF, they have a Panel class, which is similar to a ViewGroup, but they also have a ContentPresenter class which does exactly what I want... presents a single piece of content.

It's actually pretty slick in that not only can it present a UI component directly, but if you attempt to present something that isn't a UI component, like a (view)model object, it will actually apply a data-bound data template to that object based on its data type, which converts it to something for the UI. Even if there's no defined template for that specific data type, it will call toString() on it and spit out the equivalent of a TextView.

As cool as that automatic data-presentation capability is though, I'm really only after the first part... the ability to display a single child view(group) which is somewhat protected from the outside world.

Community
  • 1
  • 1
Mark A. Donohoe
  • 28,442
  • 25
  • 137
  • 286

0 Answers0