zoukankan      html  css  js  c++  java
  • 怎样实现动态加入布局文件(避免 The specified child already has a parent的问题)

    首先扯点别的:我应经连续上了两个星期的班了,今天星期一。是第三个周。这个班上的也是没谁了。近期老是腰疼。

    预计是累了。近期也没跑步。今天下班继续跑起。

    这篇文章讲一讲怎样在一个布局文件里动态加在一个布局文件。

    避免出现The specified child already has a parent. You must call removeView() on the child’s parent first.的问题。

    先看一看效果再说。
    这里写图片描写叙述

    接下来是实现过程 首先是 activity_add_view.xml文件

    <?xml version="1.0" encoding="utf-8"?>
    <android.support.design.widget.CoordinatorLayout 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"
        android:fitsSystemWindows="true"
        tools:context="com.example.administrator.learnaddview.AddViewActivity">
    
    <!--我们要在LinearLayout里面动态加入布局 如今这个LinearLayout里面仅仅有三个textView-->
        <LinearLayout
            android:id="@+id/linearLayout"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:gravity="center_horizontal"
            android:layout_marginTop="30dp"
            android:orientation="vertical">
    
            <TextView
                android:id="@+id/textView1"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:gravity="center"
                android:text="textView1"
                android:textSize="30dp" />
    
            <TextView
                android:id="@+id/textView2"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:gravity="center"
                android:text="textView2"
                android:textSize="30dp" />
    
            <TextView
                android:id="@+id/textView3"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:gravity="center"
                android:text="textView3"
                android:textSize="30dp" />
        </LinearLayout>
    
    <!--一个button用来加入布局-->
        <android.support.design.widget.FloatingActionButton
            android:id="@+id/fab"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_gravity="bottom|end"
            android:layout_margin="@dimen/fab_margin"
            android:src="@android:drawable/ic_input_add" />
    
    </android.support.design.widget.CoordinatorLayout>
    

    然后是AddViewActivity.java代码

    package com.example.administrator.learnaddview;
    
    import android.os.Bundle;
    import android.support.design.widget.FloatingActionButton;
    import android.support.v7.app.AppCompatActivity;
    import android.view.LayoutInflater;
    import android.view.View;
    import android.view.ViewGroup;
    import android.widget.ImageButton;
    import android.widget.TextView;
    
    public class AddViewActivity extends AppCompatActivity {
    
        private ViewGroup parentViewGroup;//父布局
        /**
         * A static list of country names.
         */
        private static final String[] COUNTRIES = new String[]{
                "Belgium", "France", "Italy", "Germany", "Spain",
                "Austria", "Russia", "Poland", "Croatia", "Greece",
                "Ukraine",
        };
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_add_view);
            //找到想动态加入子view的布局容器就是上面布局中的LinearLayout
            parentViewGroup = (ViewGroup) findViewById(R.id.linearLayout);
    
            //找到浮动button并加入监听事件
            FloatingActionButton fab = (FloatingActionButton) findViewById(R.id.fab);
            if (fab != null) {
                fab.setOnClickListener(new View.OnClickListener() {
                    @Override
                    public void onClick(View view) {
                        //要被加入的子布局
     final ViewGroup childViewGroup = (ViewGroup)
     LayoutInflater.from(AddViewActivity.this).inflate(R.layout.beaddlayout, parentViewGroup, false);
                        //子布局中的TextView控件
                    TextView textView = (TextView) childViewGroup.findViewById(R.id.text1);
                        //给textview随机设置一个文本
                    textView.setText(COUNTRIES[(int) (Math.random() * COUNTRIES.length)]);
                        //子布局中的ImageButton控件
     ImageButton imageButton= (ImageButton) childViewGroup.findViewById(R.id.delete_button);
                        //给imageButton设置监听事件,当点击的时候就把这个刚加入的子布局从其父布局中删除掉
                        imageButton.setOnClickListener(new View.OnClickListener() {
                            @Override
                            public void onClick(View v) {
                                parentViewGroup.removeView(childViewGroup);
                            }
                        });
                        parentViewGroup.addView(childViewGroup);
    
                    }
                });
            }
        }
    
    }
    

    上面的beaddlayout.xml文件

    <?xml version="1.0" encoding="utf-8"?>
    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
        android:layout_width="match_parent"
        android:layout_height="?android:listPreferredItemHeightSmall"
        android:divider="?android:dividerVertical"
        android:dividerPadding="8dp"
        android:gravity="center"
        android:orientation="horizontal"
        android:showDividers="middle">
    
        <!-- 随机显示一个字符串 -->
        <TextView
            android:id="@+id/text1"
            style="?android:textAppearanceMedium"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:paddingLeft="?android:listPreferredItemPaddingLeft" />
    
        <!-- 当button点击的时候会把这个布局文件从其父布局中移除-->
        <ImageButton
            android:id="@+id/delete_button"
            android:layout_width="48dp"
            android:layout_height="match_parent"
            android:background="?

    android:selectableItemBackground" android:contentDescription="remove" android:src="@drawable/ic_list_remove" /> </LinearLayout>

    以下来细致解说一下实现步骤
    1,找到想加入布局的父布局

    //找到想动态加入子view的布局容器就是上面布局中的LinearLayout
     parentViewGroup = (ViewGroup) findViewById(R.id.linearLayout);

    2过滤将要被加入的布局文件到父布局中。父布局就是第一步骤中的parentViewGroup

                      //要被加入的子布局
    
     /*inflate方法有三个參数
     第一个參数:R.layout.beaddlayout 要被载入的布局
     第二个參数:parentViewGroup 要被载入到那里
     第三个參数:取值有true和false两种,等会我们试一试取值为true的情况*/
     final ViewGroup childViewGroup = (ViewGroup)
     LayoutInflater.from(AddViewActivity.this).inflate(R.layout.beaddlayout, parentViewGroup, false);

    3:这一步是可选的。

    通过childViewGroup找到当中的iew。并加入监听事件等等操作。

    /*子布局中的TextView控件*/
                        TextView textView = (TextView) childViewGroup.findViewById(R.id.text1);
                        textView.setText(COUNTRIES[(int) (Math.random() * COUNTRIES.length)]);
    
                        //子布局中的ImageButton控件
      ImageButton imageButton= (ImageButton)childViewGroup.findViewById(R.id.delete_button);
                     //给imageButton设置监听事件。当点击的时候就把这个刚加入的子布局从其父布局中删除掉
                        imageButton.setOnClickListener(new View.OnClickListener() {
                            @Override
                            public void onClick(View v) {
                                //parentViewGroup.removeView(childViewGroup);
                                parentViewGroup.removeView(childViewGroup);
                            }
                        });

    4:把子布局加入到父布局中。大功告成。

     parentViewGroup.addView(childViewGroup);

    在上面的第二步中

    /*假设把最后一个參数取值为true。调用addView()的时候就会出现 the specified child already has a parent ,you must call the removeView() ....的问题*/
    final ViewGroup childViewGroup = (ViewGroup)
    //第三个參数取值为true
     LayoutInflater.from(AddViewActivity.this).inflate(R.layout.beaddlayout, parentViewGroup, true);

    我们进入addView()方法,经过辗转反側。我们会进入一个方法叫 addViewInner(child, index, params, false);异常就是在这里抛出的。可是这个child.getParent() != null我是真的不是非常理解。

    我的一个尝试性的理解是这种

    1. 当LayoutInflater.from(AddViewActivity.this).inflate(R.layout.beaddlayout, parentViewGroup, true);//第三个參数取值为true的时候,这种方法返回childViewGroup 是在我们的R.layout.beaddlayout外层套上一层布局(就是我们本来打算加入的布局parentViewGroup)的布局。

    2. 取值为false的时候就直接返回我们的R.layout.beaddlayout。

    3. 所以当我们调用addView()方法方法的时候,由于我们的子布局已经套在parentViewGroup里面了。

      我们调用child.getParent() 得到的就是一个LinaerLayout(我们的父布局parentViewGroup).不为空。所以这时候,就会抛出一个异常。

    private void addViewInner(View child, int index, LayoutParams params,
                boolean preventRequestLayout) {
    
          ......//省略前面的代码
            if (child.getParent() != null) {
                throw new IllegalStateException("The specified child already has a parent. " +
                        "You must call removeView() on the child's parent first.");
            }
            ....//省略后面的代码

    关于这个取值为true或者false的问题解释,我在网上找了非常多说明,也没整明确,看了看这种方法的源代码也还是难以理解,所以我把这种方法的源代码贴出来。大家自己推敲一下。

      /**
         * Inflate a new view hierarchy from the specified xml resource. Throws
         * {@link InflateException} if there is an error.
         * 
         * @param resource ID for an XML layout resource to load (e.g.,
         *        <code>R.layout.main_page</code>)
         * @param root Optional view to be the parent of the generated hierarchy (if
         *        <em>attachToRoot</em> is true), or else simply an object that
         *        provides a set of LayoutParams values for root of the returned
         *        hierarchy (if <em>attachToRoot</em> is false.)
         * @param attachToRoot Whether the inflated hierarchy should be attached to
         *        the root parameter? If false, root is only used to create the
         *        correct subclass of LayoutParams for the root view in the XML.
         * @return The root View of the inflated hierarchy. If root was supplied and
         *         attachToRoot is true, this is root; otherwise it is the root of
         *         the inflated XML file.
         */
        public View inflate(@LayoutRes int resource, @Nullable ViewGroup root, boolean attachToRoot) {
            final Resources res = getContext().getResources();
            if (DEBUG) {
                Log.d(TAG, "INFLATING from resource: "" + res.getResourceName(resource) + "" ("
                        + Integer.toHexString(resource) + ")");
            }
    
            final XmlResourceParser parser = res.getLayout(resource);
            try {
                return inflate(parser, root, attachToRoot);
            } finally {
                parser.close();
            }
        }

    在第四步中,我们使用的是 parentViewGroup.addView(childViewGroup);来加入布局。这种方法还有其它几个重载的方法。

    例如以下

    /*index 參数用来指出在父布局中的什么位置加入这个子view,取值是有讲究的。*/
     addView(View child, int index);
     /* 这种方法能够用来明确指出子view的宽高*/
     addView(View child, int width, int height);
     /*给子布局明确提供一个布局參数*/
     addView(View child, LayoutParams params);
     /*给子view提供位置信息。和布局參数*/
     addView(View child, int index, LayoutParams params)

    接下来说一说addView(View child, int index);这种方法余下的几个方法就不说了。

    在代码中我们把 parentViewGroup.addView(childViewGroup);改成 parentViewGroup.addView(childViewGroup,0);看一看有什么效果。

    这里写图片描写叙述

    我们发现子布局被动态加入到了父布局的上面。

    我们再改成 parentViewGroup.addView(childViewGroup,1);看一看有什么效果。

    这里写图片描写叙述

    我们再改成 parentViewGroup.addView(childViewGroup,3);看一看有什么效果。


    这里写图片描写叙述

    我们再改成 parentViewGroup.addView(childViewGroup。4);看一看有什么效果。
    不用看了。当我们点击button的时候,程序直接崩溃了。我们看一看Log
    java.lang.IndexOutOfBoundsException: index=4 count=3
    at android.view.ViewGroup.addInArray(ViewGroup.java:3653)
    at android.view.ViewGroup.addViewInner(ViewGroup.java:3584)
    at android.view.ViewGroup.addView(ViewGroup.java:3415)
    at android.view.ViewGroup.addView(ViewGroup.java:3360)

    看到这里我想大家或许明确了点什么。

    我们的父布局LinearLayout中,原本仅仅有三个TextView控件。我们index从0到2.当绘制子view的时候从0到index。而由于我们直接把index设为了4。

    我们这时父布局中仅仅有4个view。从0到3,所以当遍历到4的时候就出现数组越界。

    贴一下ViewGroup.java 3653的代码

      /**
         * By default, children are clipped to their bounds before drawing. This
         * allows view groups to override this behavior for animations, etc.
         *
         * @param clipChildren true to clip children to their bounds,
         *        false otherwise
         * @attr ref android.R.styleable#ViewGroup_clipChildren
         */
        public void setClipChildren(boolean clipChildren) {
            boolean previousValue = (mGroupFlags & FLAG_CLIP_CHILDREN) == FLAG_CLIP_CHILDREN;
            if (clipChildren != previousValue) {
                setBooleanFlag(FLAG_CLIP_CHILDREN, clipChildren);
                for (int i = 0; i < mChildrenCount; ++i) {
                //这是3653行。我怀疑数组越界肯定和mChildrenCount有必定的联系。继续寻找原因。
                    View child = getChildAt(i);
                    if (child.mRenderNode != null) {
                        child.mRenderNode.setClipToBounds(clipChildren);
                    }
                }
                invalidate(true);
            }
        }

    1:我们进入addView(View child, int index)的源代码找一找这个mChildrenCount在哪里。

     public void addView(View child, int index) {
            if (child == null) {
                throw new IllegalArgumentException("Cannot add a null child view to a ViewGroup");
            }
            LayoutParams params = child.getLayoutParams();
            if (params == null) {
            //得到默认的布局參数,这个是父布局的布局參数
                params = generateDefaultLayoutParams();
                if (params == null) {
                    throw new IllegalArgumentException("generateDefaultLayoutParams() cannot return null");
                }
            }
            //看着一行。调用这种方法加入view,并使用一个默认的布局params參数
            addView(child, index, params);
        }

    2 我们接着跳进addView(child, index, params);源代码

    public void addView(View child, int index, LayoutParams params) {
            if (DBG) {
                System.out.println(this + " addView");
            }
    
            if (child == null) {
                throw new IllegalArgumentException("Cannot add a null child view to a ViewGroup");
            }
    
            // addViewInner() will call child.requestLayout() when setting the new LayoutParams
            // therefore, we call requestLayout() on ourselves before, so that the child's request
            // will be blocked at our level
            requestLayout();
            invalidate(true);
            //注意这一行。我们继续跟踪
            addViewInner(child, index, params, false);
        }

    3进入addViewInner(child, index, params, false);方法。这种方法有点长。当中有一行代码太显眼了

    addInArray(child, index);

    4.我们继续跳入这种方法

    private void addInArray(View child, int index) {
            View[] children = mChildren;
            final int count = mChildrenCount;//把mChildrenCount赋给count
            final int size = children.length;
            //假设index==count
            if (index == count) {
                if (size == count) {
                    mChildren = new View[size + ARRAY_CAPACITY_INCREMENT];
                    System.arraycopy(children, 0, mChildren, 0, size);
                    children = mChildren;
                }
                children[mChildrenCount++] = child;
            } //假设index<count
            else if (index < count) {
                if (size == count) {
                    mChildren = new View[size + ARRAY_CAPACITY_INCREMENT];
                    System.arraycopy(children, 0, mChildren, 0, index);
                    System.arraycopy(children, index, mChildren, index + 1, count - index);
                    children = mChildren;
                } else {
                    System.arraycopy(children, index, children, index + 1, count - index);
                }
                children[index] = child;
                mChildrenCount++;
                if (mLastTouchDownIndex >= index) {
                    mLastTouchDownIndex++;
                }
            } 
            //这个else也是没谁了,就是这里了。
            else {
                throw new IndexOutOfBoundsException("index=" + index + " count=" + count);
            }
        }

    源代码中定义的mChildrenCount是代表mChildren数组的长度。也就是当前布局中view的个数

     // Child views of this ViewGroup
        private View[] mChildren;
        // Number of valid children in the mChildren array, the rest should be null or not
        // considered as children
        private int mChildrenCount;

    这个追踪也是欠妥。可是大家应该大致明确了是怎么回事,有兴趣的能够自己看看ViewGroup的源代码自己找一找。

    结尾:我的文章写得比較菜。欢迎大家提出疑问和指出错误。

    行,歇一歇,喝杯水。

  • 相关阅读:
    百度地图-放大地图
    haroxy hdr
    haproxy path_beg
    haproxy /admin跳转 不会在接口上再次加上admin
    api 跳转规则
    如何利用BI搭建电商数据分析平台
    如何利用BI搭建电商数据分析平台
    北向接口与南向接口
    perl 传递对象到模块
    mysql 监控 大批量的插入,删除,和修改
  • 原文地址:https://www.cnblogs.com/wzzkaifa/p/7224853.html
Copyright © 2011-2022 走看看