完整项目示例Git仓库
https://git.oschina.net/jokerlee/CustomFrameLayout.git
View拥有的默认属性中含有minHeight以及minWidth可以控制view在其父View布局计算宽高时,能够有一个最小的宽高的限定;在进行一些布局的时候能够利用该属性来限定最小的宽高,但能否自己定义maxHeight和maxWidth来实现同样的限定view的宽高上限?
View除了android赋予的默认属性可以使用外,还提供了自己定义属性的方法,即在在res/values文件下新建一个attrs.xml,加入自定义的属性:
在attrs.xml文件声明定义好自定义属性的名称和类型之后,在布局文件内加入:
xmlns:custom_attr="http://schemas.android.com/apk/res-auto"
xmlns:custom_attr="http://schemas.android.com/apk/res/[your package name]
新的官方文档建议使用第一个namespace声明(Android Studio仅支持该声明),旧版可使用第二种写法。
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
xmlns:custom_attr="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="com.example.jokerlee.custommaxsizeframelayout.MainActivity">
<com.example.jokerlee.custommaxsizeframelayout.CustomFramlayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<FrameLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_gravity="center"
custom_attr:layout_maxWidth="50dp"
custom_attr:layout_maxHeight="30dp">
<ImageView
android:layout_width="350dp"
android:layout_height="330dp"
android:layout_gravity="center"
android:background="@color/material_blue_grey_800"/>
FrameLayout>
com.example.jokerlee.custommaxsizeframelayout.CustomFramlayout>
RelativeLayout>
自定义LayoutParams类,用于自定义的view的布局,从属性集合内读取出maxWidth和maxHeight:
public static class LayoutParams extends FrameLayout.LayoutParams {
@ViewDebug.ExportedProperty(category = "layout")
public int maxWidth;
@ViewDebug.ExportedProperty(category = "layout")
public int maxHeight;
public LayoutParams(ViewGroup.LayoutParams other) {
super(other);
}
public LayoutParams(LayoutParams other) {
super(other);
maxWidth = other.maxWidth;
maxHeight = other.maxHeight;
}
public LayoutParams(Context c, AttributeSet attrs) {
super(c, attrs);
final TypedArray a = c.obtainStyledAttributes(attrs,
R.styleable.CustomFrameLayoutAttr, 0, 0);
maxWidth = a.getDimensionPixelSize(
R.styleable.CustomFrameLayoutAttr_layout_maxWidth, 0);
maxHeight = a.getDimensionPixelSize(
R.styleable.CustomFrameLayoutAttr_layout_maxHeight, 0);
a.recycle();
}
}
重载ViewGroup的layoutParams生成函数,这几个函数负责将view的属性集合或者现有的ViewGroup.LayoutParams转换为适用于当前view类型的布局参数。
@Override
public LayoutParams generateLayoutParams(AttributeSet attrs) {
return new LayoutParams(getContext(),attrs);
}
@Override
protected ViewGroup.LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) {
return p instanceof LayoutParams ? new LayoutParams((LayoutParams)p):new LayoutParams(p);
}
@Override
protected boolean checkLayoutParams(ViewGroup.LayoutParams p) {
return p instanceof LayoutParams;
}
核心的子view布局宽高控制为onMeasure函数,该函数计算并设置所有child view包括自身的大小,保证child view不会超过max值
@Override
protected void onMeasure(int widthSpec, int heightSpec) {
final int widthMode = MeasureSpec.getMode(widthSpec);
final int heightMode = MeasureSpec.getMode(heightSpec);
if (DEBUG && widthMode != MeasureSpec.AT_MOST) {
Log.w(TAG, "onMeasure: widthSpec " + MeasureSpec.toString(widthSpec) +
" should be AT_MOST");
}
if (DEBUG && heightMode != MeasureSpec.AT_MOST) {
Log.w(TAG, "onMeasure: heightSpec " + MeasureSpec.toString(heightSpec) +
" should be AT_MOST");
}
final int widthSize = MeasureSpec.getSize(widthSpec);
final int heightSize = MeasureSpec.getSize(heightSpec);
int maxWidth = widthSize;
int maxHeight = heightSize;
final int count = getChildCount();
for (int i = 0; i < count; i++) {
final View child = getChildAt(i);
final LayoutParams lp = (LayoutParams) child.getLayoutParams();
if (lp.maxWidth > 0 && lp.maxWidth < maxWidth) {
maxWidth = lp.maxWidth;
}
if (lp.maxHeight > 0 && lp.maxHeight < maxHeight) {
maxHeight = lp.maxHeight;
}
}
final int wPadding = getPaddingLeft() + getPaddingRight();
final int hPadding = getPaddingTop() + getPaddingBottom();
maxWidth -= wPadding;
maxHeight -= hPadding;
int width = widthMode == MeasureSpec.EXACTLY ? widthSize : 0;
int height = heightMode == MeasureSpec.EXACTLY ? heightSize : 0;
for (int i = 0; i < count; i++) {
final View child = getChildAt(i);
final LayoutParams lp = (LayoutParams) child.getLayoutParams();
final int childWidthSpec = makeChildMeasureSpec(maxWidth, lp.width);
final int childHeightSpec = makeChildMeasureSpec(maxHeight, lp.height);
child.measure(childWidthSpec, childHeightSpec);
width = Math.max(width, Math.min(child.getMeasuredWidth(), widthSize - wPadding));
height = Math.max(height, Math.min(child.getMeasuredHeight(), heightSize - hPadding));
}
setMeasuredDimension(width + wPadding, height + hPadding);
}
private int makeChildMeasureSpec(int maxSize, int childDimen) {
final int mode;
final int size;
switch (childDimen) {
case LayoutParams.WRAP_CONTENT:
mode = MeasureSpec.AT_MOST;
size = maxSize;
break;
case LayoutParams.MATCH_PARENT:
mode = MeasureSpec.EXACTLY;
size = maxSize;
break;
default:
mode = MeasureSpec.EXACTLY;
size = Math.min(maxSize, childDimen);
break;
}
return MeasureSpec.makeMeasureSpec(size, mode);
}