zoukankan      html  css  js  c++  java
  • Android之探究viewGroup自定义子属性参数的获取流程

    通常会疑惑,当使用不同的布局方式时,子view得布局属性就不太一样,比如当父布局是LinearLayout时,子view就能有效的使用它的一些布局属性如layout_weight、weightSum、等;当使用的是RelativeLayout时,其子view就能使用属于它的有效的属性;当然使用FrameLayout时,其属性集就少多了,也就常说的FrameLayout是最简单的布局方式了。

    简单的说我们就是想要实现自定的属性可以再子view里面使用。

    那么我们先来探索下ViewGroup中的LayoutParams吧。

    viewGroup也是继承自view,主要是实现布局容器。

    那么它有个内部基类LayoutParams。当然,这个viewGroup中的LayoutParams是所有的FrameLayout、RelativeLayout、、、的LayoutParams的基础类。当继承后,子layoutParams就拥有了父亲的所有的属性集合

    这个类注释了,是用于view去告诉他们的父布局他们想咋滴,也就是说这个是子view和父布局的通讯方式吧

    现在来看看ViewGroup中的addView的实现流程

    public void addView(View child, int index) {
            ...
            LayoutParams params = child.getLayoutParams();
            if (params == null) {
                params = generateDefaultLayoutParams();
             ....
            }
            addView(child, index, params);
        } 
    public void addView(View child, int index, LayoutParams params) {
    ...
    addViewInner(child, index, params, false);
    }

    private void addViewInner(View child, int index, LayoutParams params,
            boolean preventRequestLayout) {
      ...

    if (!checkLayoutParams(params)) {
    params = generateLayoutParams(params);
    }

    if (preventRequestLayout) {
    child.mLayoutParams = params;
    } else {
    child.setLayoutParams(params);
    }
    }

    通过整个addview的片段流程可以看到。

    首先是checkLayoutParams,目的是检测这个参数是否为空,如果为空的话就给它生成一个普通的LayoutParams;

        protected boolean checkLayoutParams(ViewGroup.LayoutParams p) {
            return  p != null;
        }
      protected LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) {
       return p;
      }
      public LayoutParams generateLayoutParams(AttributeSet attrs) {
       return new LayoutParams(getContext(), attrs);
      }

    可以得到前面两个方法protected主要是给子类去实现的,第三个方法是公开的。还是有些蹊跷的。所以要实现布局参数转换成自定义的参数,这三个方法就显得尤为重要了。

    最后把这个params赋予子view,那么params是谁实现的,子view中的mLayoutParams就是什么类型的。这就是为什么我们在代码中改变view的LayoutParams时,把它转换成其他类型了就会报错的原因。

    当然最为容易明白的还是先去研究下FrameLayout吧,因为这个类看下来不过300行代码。它其中的FrameLayout.LayoutParams中自定义了一个Gravity属性。

        public static class LayoutParams extends MarginLayoutParams {
         
            public int gravity = -1;
    
            public LayoutParams(Context c, AttributeSet attrs) {
                super(c, attrs);
    
                TypedArray a = c.obtainStyledAttributes(attrs, com.android.internal.R.styleable.FrameLayout_Layout);
                gravity = a.getInt(com.android.internal.R.styleable.FrameLayout_Layout_layout_gravity, -1);
                a.recycle();
            }
           
            public LayoutParams(int width, int height) {
                super(width, height);
            }
    
            public LayoutParams(int width, int height, int gravity) {
                super(width, height);
                this.gravity = gravity;
            }
          
            ....
            public LayoutParams(LayoutParams source) {
                super(source);
    
                this.gravity = source.gravity;
            }
        }
    

      并且也实现了add流程中所提的三个方法:

        @Override
        public LayoutParams generateLayoutParams(AttributeSet attrs) {
            return new FrameLayout.LayoutParams(getContext(), attrs);        
        }
    
        @Override
        protected boolean checkLayoutParams(ViewGroup.LayoutParams p) {
            return p instanceof LayoutParams;
        }
    
        @Override
        protected ViewGroup.LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) {
            return new LayoutParams(p);
        }

    最后FrameLayout才能在OnMeasure或者onLayout方法中对子view 进行大展身手:


    研究完了原理后,我也实现了一下这个动作,也得到了一些对方法的使用猜测

    代码如下:

    /**
     * 自定义测试viewgroup ,探究viewgroup中的addview的流程,已经子view中属性的获取
     * Created by taofuxn on 2016/12/27.
     */
    
    public class ViewPropertiesLayout extends ViewGroup {
    
        public ViewPropertiesLayout(Context context) {
            super(context);
        }
    
        public ViewPropertiesLayout(Context context, AttributeSet attrs) {
            super(context, attrs);
        }
    
        public ViewPropertiesLayout(Context context, AttributeSet attrs, int defStyleAttr) {
            super(context, attrs, defStyleAttr);
    
        }
    
        @Override
        protected boolean checkLayoutParams(LayoutParams p) {
            boolean b = p instanceof  LayoutParams;
            Log.i("AAA","checkLayoutParams  "+b);
            return b;
        }
    
        //没调用,当在xml布局中加载的时候不会调用这个方法
        @Override
        protected LayoutParams generateLayoutParams(LayoutParams p) {
            Log.i("AAA","generateLayoutParams p");
            return new MyLayoutParam(p);
        }
    
        //这个方法是通过传入的属性集合生成相应的LayouParams .在viewgroup中是找不到调用的地方,我估计是xml解析的时候调用这个方法并赋予属性集合来生成params.
        //因为通过addview的流程和这里的打印log可以看到是先调用这个方法生成了params,再去checklayout方法,最后再把转换的这个对象赋予给了子view的params
        @Override
        public LayoutParams generateLayoutParams(AttributeSet attrs) {
            Log.i("AAA","generateLayoutParams attrs");
            return new MyLayoutParam(getContext(),attrs);
        }
    
        //onmeasure,在viewgroup中是没有对子view进行测量的处理的,所有在onlayout中是获取不到子view的宽高
        @Override
        protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
            super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        }
    
        @Override
        protected void onLayout(boolean changed, int l, int t, int r, int b) {
    
            for (int i = 0 ; i < getChildCount() ; i++){
                View child = getChildAt(i);
                child.layout(l,t,r,100);
                //将子view的布局参数强行转换为本布局中自定义的LayoutParams,能这么做的原因是addview里面对子params进行了generate
                MyLayoutParam lp = (MyLayoutParam) child.getLayoutParams();
                child.setBackgroundColor(lp.color);
            }
        }
    
        public class MyLayoutParam extends LayoutParams{
    
            private int color ;
    
            public MyLayoutParam(Context c, AttributeSet attrs) {
                super(c, attrs);
                TypedArray a = c.obtainStyledAttributes(attrs,R.styleable.custom);
                color = a.getColor(R.styleable.custom_layout_bg, Color.CYAN);
                a.recycle();
            }
    
            public MyLayoutParam(int width, int height) {
                super(width, height);
            }
    
            public MyLayoutParam(MyLayoutParam source) {
                super(source);
                this.color = source.color ;
            }
    
            public MyLayoutParam(LayoutParams source){
                super(source);
            }
        }
    }
     1 <?xml version="1.0" encoding="utf-8"?>
     2 <com.example.administrator.viewproperties.ViewPropertiesLayout xmlns:android="http://schemas.android.com/apk/res/android"
     3     xmlns:app="http://schemas.android.com/apk/res-auto"
     4     android:id="@+id/activity_main"
     5     android:layout_width="match_parent"
     6     android:layout_height="match_parent"
     7   >
     8 
     9     <TextView
    10         android:layout_width="wrap_content"
    11         android:layout_height="wrap_content"
    12         app:layout_bg="@color/colorAccent"
    13         android:textSize="20sp"
    14         android:text="Hello World!" />
    15 </com.example.administrator.viewproperties.ViewPropertiesLayout>

    这个布局只在xml中应用,所有会调用public 的generateLayoutParams(atters)方法来给子view生成自定义的布局参数MyLayoutParam.那么他的执行顺序如下:

    只有当子view的layoutparams不是MyLayoutParams的实例时才会调用,generateLayoutParma(p)这个方法生成一个属于自定义布局属性

    这样的话我的这自定义布局就有了自己布局参数。感觉比自定义view的自定义属性高大上了很多。

    我的探索可能有些错误。希望大神盛情指导

  • 相关阅读:
    一个好的时间函数
    Codeforces 785E. Anton and Permutation
    Codeforces 785 D. Anton and School
    Codeforces 510 E. Fox And Dinner
    Codeforces 242 E. XOR on Segment
    Codeforces 629 E. Famil Door and Roads
    Codeforces 600E. Lomsat gelral(Dsu on tree学习)
    Codeforces 438D The Child and Sequence
    Codeforces 729E Subordinates
    【ATcoder】D
  • 原文地址:https://www.cnblogs.com/taofudemo/p/6228933.html
Copyright © 2011-2022 走看看