AddView () and XML layout files are used to configure the UI in a ViewGroup.

preface

Because the project needs to decide to customize a ViewGroup, but after selecting a solution, the whole web search can not find a addView() + XML layout file mixed to addView usage explained. There are only a few articles that mention mixing (in fact there is one, which is all over Ctrl CV, but this article is just a research on mixing), but I haven’t seen any tutorials that actually explain how to mix.

I had no choice but to slowly explore the TextInputLayout with 20% similarity to my requirements provided by Google. Finally, I managed to get it out. Think about it, on the one hand to summarize and record; On the other hand, I also want to fill this gap, so that future people do not have to take a detour like ME, I hereby write this article, hoping to have a role.


demand

Recent projects need a variety of prompt input box to replace the existing registered login box, the position of the tooltip text, the mobile phone number involved in the international area code of choice, and considering the layout of late may become more complicated, the custom EditText cannot meet the demand, can only be decided to adopt the way of custom ViewGroup to implementation, The ViewGroup controls the prompt text, input box state, status indicator, and so on.

Demand model

The final result


Scheme selection

XML to use custom viewgroups

  1. Add the required controls to the ViewGroup using the addView() method

  2. By configuring the controls you want to include in the XML layout file

  3. Mixed layout: 1+2

Comparison of advantages and disadvantages of schemes

  • The advantage of Scheme 1 is that it is simple and does not require any additional configuration of an XML layout file, but because of this, it does not have the extensibility of dynamically configuring sub-views in an XML layout file

  • The advantage of scheme 2 is that it is completely configurable and can be configured arbitrarily in an XML layout file, but the disadvantages are also obvious:

    1. Gm’s sonViewStill need to use this in eachViewGrouptheXML layout fileThe configuration is too tedious

    2. inXML layout fileThe configuration inViewGroupThe childViewThey’re all going to be addedViewGroupNo further allocation can be made

  • Solution 3 has the configurability of Solution 2, and can solve the first shortcoming of solution 2 by way of solution 1, and can solve the second shortcoming of solution 2 by overwriting the addView() method of ViewGroup.

conclusion

Due to the above requirements, I need a generic TextView to serve as the prompt text and a ViewGroup to store the sub-views loaded from the XML layout file. These two correspond to the two problems of scheme 2, and Scheme 3 just meets the requirements.


Project implementation

TextInputLayout is a control that allows you to move the Hint text when used with EditText.

TextInputLayout overwrites the addView() method:

public void addView(View child, int index, LayoutParams params) {
    if (child instanceof EditText) {
        FrameLayout.LayoutParams flp = new FrameLayout.LayoutParams(params);
        flp.gravity = 16 | flp.gravity & -113;
        this.inputFrame.addView(child, flp);
        this.inputFrame.setLayoutParams(params);
        this.updateInputLayoutMargins();
        this.setEditText((EditText)child);
   } else {
        super.addView(child, index, params); }}Copy the code

I took a bit of a detour here, and when I started referring to the TextInputLayout source code, I fell into two pits:

  1. addView(View child, int index, LayoutParams params)Method may be compiled,OverrideThe flag is missing. I mistakenly thought this method was not overwritten.
  2. paramsThe full specific type is not marked becauseTextInputLayoutIt’s inherently inheritedLinearLayoutI took it for grantedLinearLayout.LayoutParamsAnd it always has beenViewGroup.LayoutParams.

Error 2 was easy to spot, but with the help of error 1, the problem was covered up and I spent hours working on the wrong method. Instead, use the onLayout() method to achieve the desired effect visually. This solution is obviously worse, both in feel and performance, than configuring the View tree on demand while loading the View directly.

The good news is that the next day I tried the addView() method again, and found the two holes mentioned above. I successfully used the addView() method to achieve the desired effect.

Here to talk aboutTextInputLayoutTwo important lessons for me:

  1. The role of the addView() method in converting a control from an XML layout file to a View

  2. SetAddStatesFromChildren () method

We’ll look at the addView() method first, and the setAddStatesFromChildren() method will be explained later.

AddView () method

Here I’m rewriting addView(View Child, int index, LayoutParams params) with three arguments, because I need the third argument, each argument representing:

  • childI’m going to addView
  • index      childWill be added toposition-1 indicates to add to the end
  • paramsWill be inchildSet on theLayoutParamsThe parameter (actually containschildinXML layout fileProperty configured inLayoutParams)

A quick look at the source code and online parsing articles gives you a rough idea of the addView() method’s role in UI creation.

To put it simply: When the Android system parses the XML layout file into a View, it calls the addView() method in the ViewGroup currently being parsed, Add the views or viewgroups contained in the ViewGroup in the XML layout file.


Next comes the code parsing phase:


1. Look at the constructor first:

public ColorTextInputLayout(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr, 0);
        setOrientation(VERTICAL);
        setWillNotDraw(true);
        mTvHint = new TextView(context);
        mTvHint.setTag(TAG_HINT);

        mFlInputPanel = new FrameLayout(context);
        mFlInputPanel.setTag(TAG_PANEL);
        mFlInputPanel.setAddStatesFromChildren(true);
        mFlInputPanel.setBackgroundResource(R.drawable.selector_color_hint_panel);

        mIvIndicator = new ImageView(context);
        FrameLayout.LayoutParams indicatorLp = new FrameLayout.LayoutParams(FrameLayout.LayoutParams.WRAP_CONTENT, FrameLayout.LayoutParams.MATCH_PARENT);
        mFlInputPanel.addView(mIvIndicator, 0, indicatorLp);

        LinearLayout.LayoutParams rootLp = new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.WRAP_CONTENT);
        addView(mTvHint, 0, rootLp);
        addView(mFlInputPanel, 1, rootLp); . Omit the OnGlobalFocusChangeListener listening code}Copy the code

Code parsing:

This is the constructor for ColorTextInputLayout, I give setTag() to the new TextView(hint text mTvHint) and FrameLayout(input mFlInputPanel) constructor of ColorTextInputLayout. To distinguish them from the views in the XML layout file in the addView() method. At the same time new ImageView(status indicator) and add to mFlInputPanel. Finally, add the mTvHint and mFlInputPanel to the ColorTextInputLayout.

Note here mFlInputPanel. SetAddStatesFromChildren (true) this line, here I am, regardless of the role of the left behind to another use to place it again said.


2. Let’s look at the rewriteaddView()methods

@Override
public void addView(View child, int index, ViewGroup.LayoutParams params) {
    String tag = (String) child.getTag();
    // Add the hints and input panels to the ViewGroup, and the others to the container
    if (TextUtils.equals(tag, TAG_HINT) || TextUtils.equals(tag, TAG_PANEL)) {
        super.addView(child, index, params);
    } else {
        // The input panel already has an icon indicator, and can only add up to 1 child control
        if (mFlInputPanel.getChildCount() > 1) {
            throw new IllegalStateException("ColorTextInputLayout can host only one child");
        }
        FrameLayout.LayoutParams flp = new FrameLayout.LayoutParams(params);
        MarginLayoutParams marginLp = (MarginLayoutParams) params;
        flp.leftMargin = marginLp.leftMargin == 0 ? marginLp.getMarginStart() : marginLp.leftMargin;
        flp.rightMargin = marginLp.rightMargin == 0 ? marginLp.getMarginEnd() : marginLp.rightMargin;
        flp.topMargin = marginLp.topMargin;
        flp.bottomMargin = marginLp.bottomMargin;
        mFlInputPanel.addView(child, flp);
        if (child instanceof EditText) {
            setEditText((EditText) child);
        } else if (child instanceof ViewGroup) {
            EditText edt = getEditTextFromViewGroup((ViewGroup) child);
            if (edt == null) {
                throw new IllegalStateException("The ViewGroup in ColorTextInputLayout must have one EditText");
            }
            setEditText(edt);

        } else {
            throw new IllegalStateException("ColorTextInputLayout can host only an EditText or a ViewGroup containing EditText"); }}}Copy the code

Code parsing:

The if branch indicates that the controls are still added to ColorTextInputLayout by the addView() method of ColorTextInputLayout’s parent class. I don’t need to tell you about the else branch. Yes, this is the code to add a child View wrapped in ColorTextInputLayout from the XML layout file to the mFlInputPanel.


  • if (mFlInputPanel.getChildCount() > 1)Is used to defineXML layout fileCan contain only onechild, similar toScrollView. Why is it greater than 1 and not greater than 0? becausemIvIndicatorAlready added in the constructormFlInputPanel.

What does this qualification do? We looked at the possible uses of the project, the simplest of which is a single EditText, and the other is multiple controls. However, THERE is no way for me to know what kind of placement I want when using multiple controls, and different places may need different placement methods. In addition, the placement of multiple controls is too complicated. Considering the above factors, I decided to follow the example of ScrollView: only one child is allowed. Either it contains only one EditText; You can either have a ViewGroup that contains EditText, but I don’t care what the layout inside the ViewGroup is.


  • And then we go down tomFlInputPanel.addView(child, flp)So far, this part right here is the subViewThe configuration inXML layout fileIn theparamsRemove themarinXXXProperties, and then re-handle according to those propertiesViewAdd to themFlInputPanelIn the.


  • All the way down to the end is takeEditTextIf the XML layout file,ColorTextInputLayoutThe current package isEditTextdirectlysetEditText(); If the package isViewGroupFirst, through thegetEditTextFromViewGroup()Methods take outViewGroupIn theEditTextAgain,setEditText(); If neither of the two conditions is met, an exception is thrown, promptingXML layout fileThe child wrapped inViewType error.setEditText()Is a person who willViewAssign to a global variablemEditTextI won’t say much here.


3. Let’s look at the getEditTextFromViewGroup() method

/** * Use recursion to traverse the View tree and fetch the EditText from the ViewGroup (it is strongly recommended to include only one EditText) **@param viewGroup
 * @returnEditText fetched from a ViewGroup. If the ViewGroup contains more than one EditText, only the first EditText fetched is always returned; * /
private EditText getEditTextFromViewGroup(ViewGroup viewGroup) {
    // This method must be added to the ViewGroup, otherwise the method set by the input panel will not take effect
    viewGroup.setAddStatesFromChildren(true);
    EditText editText = null;
    int childCount = viewGroup.getChildCount();
    for (int i = 0; i < childCount; i++) {
        View child = viewGroup.getChildAt(i);
        // The higher the EditText is, the lower the EditText is in the View tree
        if(! (childinstanceof ViewGroup)) {
            if (child instanceof EditText) {
                editText = (EditText) child;
                break; }}else{ editText = getEditTextFromViewGroup((ViewGroup) child); }}return editText;
}
Copy the code

Code parsing:

Basically, we’re getting the EditText out of the ViewGroup recursively. Here are two points to mention:


1. In ViewGroup, EditText comes first as much as possible

In the case of nested multi-layer viewgroups, assuming the total number of views on the View tree is constant, the more advanced the EditText is, the more views on the same level as the EditText, but below it, the more views it has to deal with, which theoretically improves performance. Of course, this is due to the imperfection of my recursive method. In theory, the best way is to go from the root node to the leaf node layer by layer, rather than finding a ViewGroup and entering. Computational law is not very good, here later to optimize.


  1. You must giveViewGroupAdd this method, otherwise in the constructormFlInputPanelThe set method does not take effect.

During the implementation of the project, I mentioned that TextInputLayout gives me two inspirations. SetAddStatesFromChildren () is another one. Its functions are as follows: It sets the parent View’s background linkage with the child View, which essentially means that when you build a ViewGroup’s drawableState, you merge all of the child View’s drawableState into the parent View, And notify the parent View when the child View refreshes the drawable.


What does that mean? Is the childViewthedrawableStateWhen changes occur,ViewGroupIt’s going to sync up heredrawableStateState.

For my current needs, I need to set the Background of the mFlInputPanel to the state diagram that gets focus when EditText gets focus. According to the taste of turtle

No, the general practice is to set focus listening events to the EditText and change the mFlInputPanel background when the focus changes. But that can be tedious; In addition, if the outside of ColorTextInputLayout also needs to listen for the EditText focus state, the two will conflict.

With the setAddStatesFromChildren() method, everything is simple: MFlInputPanel first sets a drawable selector_XX.xml background and then calls the setAddStatesFromChildren() method. This way, when the EditText’s drawableState (including focus state) changes, the mFlInputPanel will be notified and automatically select the background for the corresponding state in selector_XX.xml.

It is important to note that setDuplicateParentStateEnabled () method with setAddStatesFromChildren () the reverse, both can not be used together, otherwise may cause collapse. In addition, in my own use, if there are nested viewgroups between the responding ViewGroup and the View I want to listen to, I need to call the setAddStatesFromChildren() method on each layer of ViewGroup. The ViewGroup of the response takes effect. So, I also called the setAddStatesFromChildren() method at the beginning of the getEditTextFromViewGroup() method.


The end of the

ViewGroup XML layout file + addView() + addView() + addView() If there is a need to reprint, please indicate the source, thank you.