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的自定义属性高大上了很多。

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

  • 相关阅读:
    python flask 环境安装
    pymongo的说明,挺详细的
    Mysql加锁过程详解
    MySQL Group Replication 介绍
    Linux 下方便的ssh非交互工具sshpass的安装与使用
    我要拿Offer之AQS条件队列及中断机制
    ThreadLocal解析
    AQS源码分析总结
    动手实现一个同步器(AQS)
    java线程池解析
  • 原文地址:https://www.cnblogs.com/taofudemo/p/6228933.html
Copyright © 2011-2022 走看看