本文为原创,转载请注明出自:小妖森的博客
http://blog.csdn.net/u011367679/article/details/46707773
自己写博客的数量真是屈指可数啊,最近工作忙,并且也还不太会写博客。所以开始着重写博客了。
昨天发现了Google推出了百分比布局,刚好自己的工作做完了,于是更新了一下sdk来看看。
我们来看看这个类库在哪
aar文件呢是Google为了解决jar包中不能加入资源的问题的产物。
但是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
结束。。。