PercentRelativeLayout 源码解析

泡在网上的日子 发表于 2015-08-08 12:50 次阅读 百分比

文章出处:http://www.jianshu.com/p/05bc040841cd 

com.android.support:percent:22.2.0 :google 出了个包来支持按父控件百分比的显示.
我们Gradle 导入

compile 'com.android.support:percent:22.2.0'

看一下,其实这个库特别简单只有3个 class 类


declare-styleable 有:

1 简述

我们能用的控件?

有 PrecentFrameLayout 和 PrecentRelativeLayout
从这个名称上面我们可以很清楚的知道PrecentFrameLayout 继承 FrameLayout PrecentRelativeLayout 继承 RelativeLayout 的.

PrecentLayoutHelper 是什么?

其实你可以看出PrecentFrameLayout 和PrecentRelativeLayout 中实现效果类似,由此可以看出两个类有着太多相似的代码,所以将操作放在Helper中,不仅使逻辑更清晰,同时可以进行统一的维护,一次修改.两边生效.

2 源码分析

我们查看PercentRelativeLayout 这个类.我们直接使用 as 的 ctrl + 鼠标左键,直接进入源码去查看 (as 真棒)

PercentRelativeLayout.class

public class PercentRelativeLayout extends RelativeLayout {
    private final PercentLayoutHelper mHelper = new PercentLayoutHelper(this);
    public PercentRelativeLayout(Context context) {
        super(context);
    }
    public PercentRelativeLayout(Context context, AttributeSet attrs) {
        super(context, attrs);
    }
    public PercentRelativeLayout(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
    }
    public PercentRelativeLayout.LayoutParams generateLayoutParams(AttributeSet attrs) {
        return new PercentRelativeLayout.LayoutParams(this.getContext(), attrs);
    }
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        //修改childView 的 LayoutParams
        this.mHelper.adjustChildren(widthMeasureSpec, heightMeasureSpec);
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        if(this.mHelper.handleMeasuredStateTooSmall()) {
            //that indicates the measured size is smaller that the space the view would like to have.
            super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        }
    }
    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
        super.onLayout(changed, left, top, right, bottom);
        //还原上一个 子类的的 LayoutParams?  这边我有个问题?会在下面提出.
        this.mHelper.restoreOriginalParams();
    }
    public static class LayoutParams extends android.widget.RelativeLayout.LayoutParams implements PercentLayoutParams {
        private PercentLayoutInfo mPercentLayoutInfo;
        public LayoutParams(Context c, AttributeSet attrs) {
            super(c, attrs);
            //主要是获取我们之前在 xml 设置的比例
            this.mPercentLayoutInfo = PercentLayoutHelper.getPercentLayoutInfo(c, attrs);
        }
        public LayoutParams(int width, int height) {
            super(width, height);
        }
        public LayoutParams(android.view.ViewGroup.LayoutParams source) {
            super(source);
        }
        public LayoutParams(MarginLayoutParams source) {
            super(source);
        }
        public PercentLayoutInfo getPercentLayoutInfo() {
             //实现接口返回mPercentLayoutInfo 
            return this.mPercentLayoutInfo;
        }
        protected void setBaseAttributes(TypedArray a, int widthAttr, int heightAttr) {
            PercentLayoutHelper.fetchWidthAndHeight(this, a, widthAttr, heightAttr);
        }
    }
}

主要的的内容:

复写了generateLayoutParams 方法,这个方法系统自动调用.在添加 childView 的调用.同时
这个方法是可以获取到子类的AttributeSet, 而我们设置的比例就在AttributeSet中.
我们可以看到generateLayoutParams 方法返回了一个新的LayoutParams ,这个LayoutParams 继承了android.widget.RelativeLayout.LayoutParams 实现了PercentLayoutParams这个接口.

为什么要实现这个接口?

为了多态,它将我们设置的条件放在mPercentLayoutInfo这个类中,通过一个接口返回给我们.因为我们有多个不同类型的的LayoutParams,通过这个接口可以使用多态,同时有利于我们基于 Help 进行扩展.

PercentLayoutHelper.class

这个方法有点长.不太可能把所有的代码贴出来,我贴重要的.

 public void adjustChildren(int widthMeasureSpec, int heightMeasureSpec) {
        if(Log.isLoggable("PercentLayout", 3)) {
            Log.d("PercentLayout", "adjustChildren: " + this.mHost + " widthMeasureSpec: " + MeasureSpec.toString(widthMeasureSpec) + " heightMeasureSpec: " + MeasureSpec.toString(heightMeasureSpec));
        }
       //获取自身的宽度的大小
        int widthHint = MeasureSpec.getSize(widthMeasureSpec);
       //获取自身高度的大小
        int heightHint = MeasureSpec.getSize(heightMeasureSpec);
        int i = 0;
        for(int N = this.mHost.getChildCount(); i < N; ++i) {
            View view = this.mHost.getChildAt(i);
            LayoutParams params = view.getLayoutParams();
            if(Log.isLoggable("PercentLayout", 3)) {
                Log.d("PercentLayout", "should adjust " + view + " " + params);
            }
            if(params instanceof PercentLayoutHelper.PercentLayoutParams) {
                //获取到PercentLayoutInfo 我们设置的比例就在里面.
                PercentLayoutHelper.PercentLayoutInfo info = ((PercentLayoutHelper.PercentLayoutParams)params).getPercentLayoutInfo();
                if(Log.isLoggable("PercentLayout", 3)) {
                    Log.d("PercentLayout", "using " + info);
                }
                if(info != null) {
                    //这变为什么要判断LayoutParam 的类型?
                   //方法能走到这里的时候已经说明了info != null ,就是说 params 这个实现了PercentLayoutParams这个接口,而我们的 PercentLayoutParams 的实现类都是 MarginLayoutParams 的子类.那我们为什么还要做这个判断呢?为了扩展.如果你的要实现的不是基本布局类型,而是 ViewGrop,它的 layoutParam 是ViewGroup.LayoutParams,而你要实现这个这个效果.那么你如果直接强转MarginLayoutParams 是会崩掉的.
                    if(params instanceof MarginLayoutParams) {
                        info.fillMarginLayoutParams((MarginLayoutParams)params, widthHint, heightHint);
                    } else {
                        info.fillLayoutParams(params, widthHint, heightHint);
                    }
                }
            }
        }
    }

adjustChildren 方法:

通过修改fillMarginLayoutParams 和 fillLayoutParams 修改childView 的 LayoutParam 的宽高,边距,并将修改前的值保存在PercentLayoutInfo 的mPreservedParams中.

    public boolean handleMeasuredStateTooSmall() {
        boolean needsSecondMeasure = false;
        int i = 0;
        for(int N = this.mHost.getChildCount(); i < N; ++i) {
            View view = this.mHost.getChildAt(i);
            LayoutParams params = view.getLayoutParams();
            if(Log.isLoggable("PercentLayout", 3)) {
                Log.d("PercentLayout", "should handle measured state too small " + view + " " + params);
            }
            if(params instanceof PercentLayoutHelper.PercentLayoutParams) {
                PercentLayoutHelper.PercentLayoutInfo info = ((PercentLayoutHelper.PercentLayoutParams)params).getPercentLayoutInfo();
                if(info != null) {
                    if(shouldHandleMeasuredWidthTooSmall(view, info)) {
                        needsSecondMeasure = true;
                        params.width = -2;
                    }
                    if(shouldHandleMeasuredHeightTooSmall(view, info)) {
                        needsSecondMeasure = true;
                        params.height = -2;
                    }
                }
            }
        }
        if(Log.isLoggable("PercentLayout", 3)) {
            Log.d("PercentLayout", "should trigger second measure pass: " + needsSecondMeasure);
        }
        return needsSecondMeasure;
    }
   private static boolean shouldHandleMeasuredWidthTooSmall(View view, PercentLayoutInfo info)
    {
        int state = ViewCompat.getMeasuredWidthAndState(view) & ViewCompat.MEASURED_STATE_MASK;
        return state == ViewCompat.MEASURED_STATE_TOO_SMALL && info.widthPercent.percent >= 0 &&
                info.mPreservedParams.width == ViewGroup.LayoutParams.WRAP_CONTENT;
    }
  private static boolean shouldHandleMeasuredHeightTooSmall(View view, PercentLayoutInfo info)
    {
        int state = ViewCompat.getMeasuredHeightAndState(view) & ViewCompat.MEASURED_STATE_MASK;
        return state == ViewCompat.MEASURED_STATE_TOO_SMALL && info.heightPercent.percent >= 0 &&
                info.mPreservedParams.height == ViewGroup.LayoutParams.WRAP_CONTENT;
    }

handleMeasuredStateTooSmall 方法 :

这个方法是检查 childView 大小计算是否不合理.
这边ViewCompat.getMeasuredWidthAndState(view)返回值有3个


我的疑惑:

问题1:

我的问题是用3个返回值分别去跟MEASURED_STATE_MASK相与发现只有MEASURED_STATE_TOO_SMALL 可以使 state == ViewCompat.MEASURED_STATE_TOO_SMALL,
那它为什么还要跟MEASURED_STATE_TOO_SMALL 相与?

问题2:

在PercentRelativeLayout中的onLayout 中调用了 this.mHelper.restoreOriginalParams()方法,从方法名和跳转进去看源码你发现它是将之前的保存在PercentLayoutInfo 的mPreservedParams 重新赋值给 childView 的想法.但是真的有用吗,因为当它连续2次的调用onMeasure()方法的时候,PercentLayoutInfo 的mPreservedParams 会被替换掉,导致PercentLayoutInfo 的mPreservedParams和 childView 中的 layoutParam是一样的.所以这个真的有用吗.

onMeasure 为什么会调用多次?
看 How Android Draws Views 或 中文版


收藏 赞 (0) 踩 (0)
上一篇:类似 Instagram 径向羽化透露手指底部内容的 FingerTransparentView
原文出处: http://drakeet.me/fingertransparentview 前不久群里有人说有木有人知道怎么实现「类似 Instagram 径向模糊透露手指底部内容」的效果,用过 Ps 的我,当时就知道这其实叫羽化,因为要实时用算法羽化其实挺麻烦的,效率也不见得好,于是我当即很
下一篇:base-adapter-helper的RecyclerView版
前几天我写了一篇分析base-adapter-helper的文章,文章中提到了尝试用和base-adapter-helper相同的api实现RecyclerView适配器。现在已经实现了。 在这期间也看了简书作者轻微的一篇文章: RecyclerView适配器的超省写法 。发现他的实现原理其实和base-adapte