Android百分比布局源代码解析

本文为原创,转载请注明出自小妖森的博客

http://blog.csdn.net/u011367679/article/details/46707773

自己写博客的数量真是屈指可数啊,最近工作忙,并且也还不太会写博客。所以开始着重写博客了。

昨天发现了Google推出了百分比布局,刚好自己的工作做完了,于是更新了一下sdk来看看。

我们来看看这个类库在哪

aar文件呢是Google为了解决jar包中不能加入资源的问题的产物。

*.aar:包含所有资源,class以及res资源文件

但是Eclipse不认识这个文件,所以还是用依赖工程的方式来使用。我已经把eclipse下的依赖工程库上传到github了,飞机直达

首先我们来看一下Google为我们提供了哪些可使用的百分比属性

    <declare-styleable name="PercentLayout_Layout">
        <attr name="layout_widthPercent" format="fraction" />
        <attr name="layout_heightPercent" format="fraction" />
        <attr name="layout_marginPercent" format="fraction" />
        <attr name="layout_marginLeftPercent" format="fraction" />
        <attr name="layout_marginTopPercent" format="fraction" />
        <attr name="layout_marginRightPercent" format="fraction" />
        <attr name="layout_marginBottomPercent" format="fraction" />
        <attr name="layout_marginStartPercent" format="fraction" />
        <attr name="layout_marginEndPercent" format="fraction" />
    </declare-styleable>
关于marginLeft 与marginStart的区别 看这里
值得注意的是这些百分比都是相对于父布局来说的。

下面开始看源代码以PercentRelativeLayout为例

package android.support.percent;

import android.content.Context;
import android.content.res.TypedArray;
import android.util.AttributeSet;
import android.view.ViewGroup;
import android.widget.RelativeLayout;

public class PercentRelativeLayout extends RelativeLayout {
    private final PercentLayoutHelper mHelper;

    public PercentRelativeLayout(Context context) {
        super(context);
        mHelper = new PercentLayoutHelper(this);
    }

    public PercentRelativeLayout(Context context, AttributeSet attrs) {
        super(context, attrs);
        mHelper = new PercentLayoutHelper(this);
    }

    public LayoutParams generateLayoutParams(AttributeSet attrs) {
        return new LayoutParams(getContext(), attrs);
    }

    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        this.mHelper.adjustChildren(widthMeasureSpec, heightMeasureSpec);
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        if (this.mHelper.handleMeasuredStateTooSmall())
            super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    }

    protected void onLayout(boolean changed, int left, int top, int right,
            int bottom) {
        super.onLayout(changed, left, top, right, bottom);
        this.mHelper.restoreOriginalParams();
    }

    public static class LayoutParams extends RelativeLayout.LayoutParams
            implements PercentLayoutHelper.PercentLayoutParams {
        private PercentLayoutHelper.PercentLayoutInfo mPercentLayoutInfo;

        public LayoutParams(Context c, AttributeSet attrs) {
            super(c, attrs);
            this.mPercentLayoutInfo = PercentLayoutHelper.getPercentLayoutInfo(
                    c, attrs);
        }

        public LayoutParams(int width, int height) {
            super(width, height);
        }

        public LayoutParams(ViewGroup.LayoutParams source) {
            super(source);
        }

        public LayoutParams(MarginLayoutParams source) {
            super(source);
        }

        public PercentLayoutHelper.PercentLayoutInfo getPercentLayoutInfo() {
            return this.mPercentLayoutInfo;
        }

        protected void setBaseAttributes(TypedArray a, int widthAttr,
                int heightAttr) {
            PercentLayoutHelper.fetchWidthAndHeight(this, a, widthAttr,
                    heightAttr);
        }
    }
}
代码不是很多,可以看到 PercentRelativeLayout继承自RelativeLayout,但是由于加入了百分比特性,所以也要自定义LayoutParams,其继承自相应Layout的LayoutParams。我们按照view的绘制流程就行分析。

首先是onMeasure方法

我们看到首先调用了mHelper.adjustChildren(widthMeasureSpec, heightMeasureSpec)我们跟进源码

    public void adjustChildren(int widthMeasureSpec, int heightMeasureSpec) {
        int widthHint = View.MeasureSpec.getSize(widthMeasureSpec);
        int heightHint = View.MeasureSpec.getSize(heightMeasureSpec);
        int i = 0;
        for (int N = this.mHost.getChildCount(); i < N; i++) {
            View view = this.mHost.getChildAt(i);
            ViewGroup.LayoutParams params = view.getLayoutParams();
            if ((params instanceof PercentLayoutParams)) {
                PercentLayoutInfo info = ((PercentLayoutParams) params).getPercentLayoutInfo();

                if (info != null)
                    if ((params instanceof ViewGroup.MarginLayoutParams)) {
                        info.fillMarginLayoutParams((ViewGroup.MarginLayoutParams) params, widthHint, heightHint);
                    } else
                        info.fillLayoutParams(params, widthHint, heightHint);
            }
        }
    }
这里是先拿到自己的宽和高放在widthHint和heightHint中备用,然后遍历每个子view拿到LayoutParams,之后取得PercentLayoutInfo,有同学可能会问 LayoutParams以及PercentLayoutInfo是在哪里初始化的,我们去看上面的generateLayoutParams方法,这个方法是系统回调方法,在这时我们初始化了 LayoutParams,在其构造方法中调用了PercentLayoutHelper.getPercentLayoutInfo(c, attrs)为PercentLayoutInfo赋值,我们接着跟进查看源码。

    public static PercentLayoutInfo getPercentLayoutInfo(Context context, AttributeSet attrs) {
        PercentLayoutInfo info = null;
        TypedArray array = context.obtainStyledAttributes(attrs,
                R.styleable.PercentLayout_Layout);
        float value = array.getFraction(
                R.styleable.PercentLayout_Layout_layout_widthPercent, 1, 1, -1.0F);

        if (value != -1.0F) {
            info = info != null ? info : new PercentLayoutInfo();
            info.widthPercent = value;
        }
        value = array.getFraction(
                R.styleable.PercentLayout_Layout_layout_heightPercent, 1, 1, -1.0F);
        if (value != -1.0F) {
            info = info != null ? info : new PercentLayoutInfo();
            info.heightPercent = value;
        }
        value = array.getFraction(
                R.styleable.PercentLayout_Layout_layout_marginPercent, 1, 1, -1.0F);
        if (value != -1.0F) {
            info = info != null ? info : new PercentLayoutInfo();
            info.leftMarginPercent = value;
            info.topMarginPercent = value;
            info.rightMarginPercent = value;
            info.bottomMarginPercent = value;
        }
        value = array.getFraction(
                R.styleable.PercentLayout_Layout_layout_marginLeftPercent, 1, 1, -1.0F);

        if (value != -1.0F) {
            info = info != null ? info : new PercentLayoutInfo();
            info.leftMarginPercent = value;
        }
        value = array.getFraction(
                R.styleable.PercentLayout_Layout_layout_marginTopPercent, 1, 1, -1.0F);

        if (value != -1.0F) {
            info = info != null ? info : new PercentLayoutInfo();
            info.topMarginPercent = value;
        }
        value = array.getFraction(
                R.styleable.PercentLayout_Layout_layout_marginRightPercent, 1, 1, -1.0F);

        if (value != -1.0F) {
            info = info != null ? info : new PercentLayoutInfo();
            info.rightMarginPercent = value;
        }
        value = array.getFraction(
                R.styleable.PercentLayout_Layout_layout_marginBottomPercent, 1, 1, -1.0F);

        if (value != -1.0F) {
            info = info != null ? info : new PercentLayoutInfo();
            info.bottomMarginPercent = value;
        }
        value = array.getFraction(
                R.styleable.PercentLayout_Layout_layout_marginStartPercent, 1, 1, -1.0F);

        if (value != -1.0F) {
            info = info != null ? info : new PercentLayoutInfo();
            info.startMarginPercent = value;
        }
        value = array.getFraction(
                R.styleable.PercentLayout_Layout_layout_marginEndPercent, 1, 1, -1.0F);

        if (value != -1.0F) {
            info = info != null ? info : new PercentLayoutInfo();
            info.endMarginPercent = value;
        }
        array.recycle();
        return info;
    }
对于经常自定义view的同学来说,代码是不是很熟悉?就是把百分比属性封装到 PercentLayoutInfo中了,我们回过头去接着看adjustChildren方法,接下来就是对params判断,是否包含margin属性,如果包含则调用fillMarginLayoutParams方法,否则fillLayoutParams方法。先来看 fillMarginLayoutParams方法。

public void fillMarginLayoutParams(ViewGroup.MarginLayoutParams params, int widthHint, int heightHint) {
    fillLayoutParams(params, widthHint, heightHint);

    this.mPreservedParams.leftMargin = params.leftMargin;
    this.mPreservedParams.topMargin = params.topMargin;
    this.mPreservedParams.rightMargin = params.rightMargin;
    this.mPreservedParams.bottomMargin = params.bottomMargin;
    MarginLayoutParamsCompat.setMarginStart(this.mPreservedParams,
            MarginLayoutParamsCompat.getMarginStart(params));

    MarginLayoutParamsCompat.setMarginEnd(this.mPreservedParams,
            MarginLayoutParamsCompat.getMarginEnd(params));

    if (this.leftMarginPercent >= 0.0F) {
        params.leftMargin = ((int) (widthHint * this.leftMarginPercent));
    }
    if (this.topMarginPercent >= 0.0F) {
        params.topMargin = ((int) (heightHint * this.topMarginPercent));
    }
    if (this.rightMarginPercent >= 0.0F) {
        params.rightMargin = ((int) (widthHint * this.rightMarginPercent));
    }
    if (this.bottomMarginPercent >= 0.0F) {
        params.bottomMargin = ((int) (heightHint * this.bottomMarginPercent));
    }
    if (this.startMarginPercent >= 0.0F) {
        MarginLayoutParamsCompat.setMarginStart(params,
                (int) (widthHint * this.startMarginPercent));
    }
    if (this.endMarginPercent >= 0.0F) {
        MarginLayoutParamsCompat.setMarginEnd(params,
                (int) (widthHint * this.endMarginPercent));
    }

}
在一开始先调用了 fillLayoutParams方法,这个方法在下面会进行分析。fillMarginLayoutParams先把xml中写的marginLeft、marginTop等信息保存到了mPreservedParams中,然后根据百分比重新设置margin。
再看 fillLayoutParams
public void fillLayoutParams(ViewGroup.LayoutParams params, int widthHint, int heightHint) {
    this.mPreservedParams.width = params.width;
    this.mPreservedParams.height = params.height;

    if (this.widthPercent >= 0.0F) {
        params.width = ((int) (widthHint * this.widthPercent));
    }
    if (this.heightPercent >= 0.0F) {
        params.height = ((int) (heightHint * this.heightPercent));
    }
}
这个和 fillMarginLayoutParams方法的思路是一样的,把xml中的layout_width、layout_height保存到mPreservedParams中,然后根据百分比重新设置width和height。现在我们可以知道实现百分比特性的关键部分就是这。widthHint * this.widthPercent其中widthHint是PercentRelativeLayout的宽度widthPercent是你在xml中为子view设置的百分比。

adjustChildren方法分析完,我们回到onMeasure方法中,之后又调用了super.onMeasure(widthMeasureSpec, heightMeasureSpec),对每个子view进行测量,接下来又做了一个操作我们看

if (mHelper.handleMeasuredStateTooSmall()) {
            super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
跟进handleMeasuredStateTooSmall方法中

public boolean handleMeasuredStateTooSmall() {
    boolean needsSecondMeasure = false;
    int i = 0;
    for (int N = this.mHost.getChildCount(); i < N; i++) {
        View view = this.mHost.getChildAt(i);
        ViewGroup.LayoutParams params = view.getLayoutParams();
        if ((params instanceof PercentLayoutParams)) {
            PercentLayoutInfo info = ((PercentLayoutParams) params).getPercentLayoutInfo();
            if (info != null) {
                if (shouldHandleMeasuredWidthTooSmall(view, info)) {
                    needsSecondMeasure = true;
                    params.width = ViewGroup.LayoutParams.WRAP_CONTENT;
                }
                if (shouldHandleMeasuredHeightTooSmall(view, info)) {
                    needsSecondMeasure = true;
                    params.height = ViewGroup.LayoutParams.WRAP_CONTENT;
                }
            }
        }
    }
    return needsSecondMeasure;
}
这个方法的作用是对于设置了百分比的view,如果测量得到的宽度或者高度太小并且在布局文件中你的layout_width或者layout_height设置的是 WRAP_CONTENT的话,就会把这params的宽或者高设置成WRAP_CONTENT,然后对所有的子view进行重新测量。

shouldHandleMeasuredWidthTooSmall方法的代码。

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 >= 0.0F)
            && (info.mPreservedParams.width == ViewGroup.LayoutParams.WRAP_CONTENT);
}
onMeasure方法已经分析完成,接下来就是onLayout方法了。

protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
        super.onLayout(changed, left, top, right, bottom);
        this.mHelper.restoreOriginalParams();
}
在onLayout方法中,除了调用super之外,还调用了restoreOriginalParams()方法,从字面的意思看,恢复原来的params
    public void restoreOriginalParams() {
        int i = 0;
        for (int N = this.mHost.getChildCount(); i < N; i++) {
            View view = this.mHost.getChildAt(i);
            ViewGroup.LayoutParams params = view.getLayoutParams();
            if ((params instanceof PercentLayoutParams)) {
                PercentLayoutInfo info = ((PercentLayoutParams) params).getPercentLayoutInfo();

                if (info != null)
                    if ((params instanceof ViewGroup.MarginLayoutParams))
                        info.restoreMarginLayoutParams((ViewGroup.MarginLayoutParams) params);
                    else
                        info.restoreLayoutParams(params);
            }
        }
    }
再看restoreMarginLayoutParams

    public void restoreMarginLayoutParams(ViewGroup.MarginLayoutParams params) {
        restoreLayoutParams(params);
        params.leftMargin = this.mPreservedParams.leftMargin;
        params.topMargin = this.mPreservedParams.topMargin;
        params.rightMargin = this.mPreservedParams.rightMargin;
        params.bottomMargin = this.mPreservedParams.bottomMargin;
        MarginLayoutParamsCompat.setMarginStart(params,
                MarginLayoutParamsCompat.getMarginStart(this.mPreservedParams));

        MarginLayoutParamsCompat.setMarginEnd(params,
                MarginLayoutParamsCompat.getMarginEnd(this.mPreservedParams));
    }
restoreLayoutParams

    public void restoreLayoutParams(ViewGroup.LayoutParams params) {
        params.width = this.mPreservedParams.width;
        params.height = this.mPreservedParams.height;
    }
我们看,这里就是把params恢复成之前存储在mPreservedParams中的信息。对此不太理解这样做的目的是什么。


好了。PercentRelativeLayout的代码已经分析完成,其实我们可以仿照这个来写自己的ViewGroup,在Google为我们提供的percetn-supprot-lib中只有PercentRelativeLayout和PercentFrameLayout两个布局,留给我们自己发挥的余地还很大。在我的github中我加入了PercentLinearLayout布局。

最后上一张效果图,摘自Github


结束。。。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值