本文为原创,转载请注明出自:小妖森的博客
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
结束。。。
版权声明:本文为博主原创文章,未经博主允许不得转载。