前提
上一篇我们分析了自定义像素的屏幕适配,它是以px为单位,设计的标准机型和显示机型的宽高比,动态缩放view。
现在,我们不用px为单位,而是直接以当前view在父容器中的比例,来缩放view。
比如,我定义一个view,它的宽为父容器的一半,高也为父容器的一半。
//xml中直接以float的形式定义子view的宽高
app:widthPercent="0.5"
app:heightPercent=“0.75”
准备工作
既然需要使用自定义属性,首先就需要添加attrs,声明自定义属性。
//attrs.xml中添加自定义属性
<declare-styleable name=“PercentLayout”>
<attr name=“widthPercent” format=“float” />
<attr name=“heightPercent” format=“float” />
<attr name=“marginLeftPercent" format="float" />
<attr name="marginRightPercent" format="float" />
<attr name=“marginTopPercent” format=“float” />
<attr name="marginBottomPercent" format="float" />
</declare-styleable>
我们实现的目的是子view以父容器的宽高为标准,按照百分比规定自身宽高,所以需要自定义个ViewGroup用来包裹子view,顺便计算出子view的宽高。
以RelativeLayout为例:
public class PercentLayout extends RelativeLayout {
public PercentLayout(Context context) {
this(context, null);
}
public PercentLayout(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public PercentLayout(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
}
继承了RelativeLayout后,我们不能丢失RelativeLayout原来的属性,还要增加我们自定义的属性。
源码的简单分析
原始RelativeLayout的属性被定义在RelativeLayout的内部静态类中(LayoutParams)。
该类中定义的如:above、below、center等属性,都是声明在子view中。同时百分比属性也是需要定义在子view中。
所以,我们的自定义百分比布局也需要继承该内部类,再添加自定义属性。
private static class CustomLayoutParam extends RelativeLayout.LayoutParams {
//声明自定义属性
private float widthPercent;
private float heightPercent;
private float marginTopPercent;
private float marginBottomPercent;
private float marginLeftPercent;
private float marginRightPercent;
public CustomLayoutParam(Context context, AttributeSet attrs) {
super(context, attrs);
TypedArray typedArray = context.obtainStyledAttributes(attrs,R.styleable.PercentLayout);
widthPercent = typedArray.getFloat(R.styleable.PercentLayout_widthPercent, 0f);
heightPercent = typedArray.getFloat(R.styleable.PercentLayout_heightPercent, 0f);
marginTopPercent = typedArray.getFloat(R.styleable.PercentLayout_marginTopPercent, 0f);
marginBottomPercent = typedArray.getFloat(R.styleable.PercentLayout_marginBottomPercent, 0f);
marginLeftPercent = typedArray.getFloat(R.styleable.PercentLayout_marginLeftPercent, 0f);
marginRightPercent = typedArray.getFloat(R.styleable.PercentLayout_marginRightPercent, 0f);
typedArray.recycle();
}
}
再分析下view的创建与LayoutParams的关系。
onCreateView()中始终需要调用setContentView(layoutID)。
最终在PhoneWindow的setContentView(int layoutResID)方法中,调用方法解析layoutResID
方法一路延伸最终来到LayoutInflater类的inflate()方法。
//inflate方法中
。。。
final View temp = createViewFromTag(root, name, inflaterContext, attrs);
ViewGroup.LayoutParams params = null;
if (root != null) {
if (DEBUG) {
System.out.println(“Creating params from root: “ +
root);
}
// Create layout params that match root, if supplied
//由父容器创建子view的layoutParams
params = root.generateLayoutParams(attrs);
if (!attachToRoot) {
// Set the layout params for temp if we are not
// attaching. (If we are, we use addView, below)
temp.setLayoutParams(params);
}
}
。。。
关键点:源码中,子view的LayoutParams是由父容器的generateLayoutParams()创建
//viewGroup中
public LayoutParams generateLayoutParams(AttributeSet attrs) {
return new LayoutParams(getContext(), attrs);
}
那我们的自定义LayoutParams如何被创建呢?
在父容器中重写generateLayoutParams方法,返回我们子view的LayoutParams。
//重写方法,返回自定义的LayoutParams
@Override
public LayoutParams generateLayoutParams(AttributeSet attrs) {
return new CustomLayoutParam(getContext(), attrs);
}
计算
最终在onMeasure方法中,根据百分比计算出子view的宽高。
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
//获取父容器的宽高
int widthSize = MeasureSpec.getSize(widthMeasureSpec);
int heightSize = MeasureSpec.getSize(heightMeasureSpec);
int childCount = getChildCount();
for (int i = 0; i < childCount; i++) {
View child = getChildAt(i);
ViewGroup.LayoutParams layoutParams = child.getLayoutParams();
if (layoutParams instanceof CustomLayoutParam) {
CustomLayoutParam lp = (CustomLayoutParam) layoutParams;
float widthPercent = lp.widthPercent;
float heightPercent = lp.heightPercent;
float marginTopPercent = lp.marginTopPercent;
float marginBottomPercent = lp.marginBottomPercent;
float marginLeftPercent = lp.marginLeftPercent;
float marginRightPercent = lp.marginRightPercent;
if (widthPercent > 0) {
lp.width = (int) (widthSize * widthPercent);
}
if (heightPercent > 0) {
lp.height = (int) (heightSize * heightPercent);
}
if (marginTopPercent > 0) {
lp.topMargin = (int) (heightSize * marginTopPercent);
}
if (marginBottomPercent > 0) {
lp.bottomMargin = (int) (heightSize * marginBottomPercent);
}
if (marginLeftPercent > 0){
lp.leftMargin= (int) (widthSize* marginLeftPercent);
}
if (marginRightPercent > 0){
lp.rightMargin = (int) (widthSize * marginRightPercent);
}
}
}
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
最终效果
1.png总结
整体思路
1、自定义属性。
2、自定义LayoutParams,继承自RelativeLayout.LayoutParams。
3、创建自定义LayoutParams,父容器用generateLayoutParams()方法创建子view的layoutParams。
4、在onMeasure中获取子view的LayoutParams,按比例计算出宽高并赋值。
注意:子view的LayoutParams是由父容器在generateLayoutParams()中创建。
网友评论