您的位置:首页 > 移动开发 > Android开发

Android自定义可控最大宽高的Layout

2016-03-16 16:13 411 查看
完整项目示例Git仓库

https://git.oschina.net/jokerlee/CustomFrameLayout.git

Android View的宽高属性

View拥有的默认属性中含有minHeight以及minWidth可以控制view在其父View布局计算宽高时,能够有一个最小的宽高的限定;在进行一些布局的时候能够利用该属性来限定最小的宽高,但能否自己定义maxHeight和maxWidth来实现同样的限定view的宽高上限?

<Button android:id="@+id/button"
android:minHeight="155dp"
android:minWidth="240dp"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Select"/>


Android自定义attr属性

View除了android赋予的默认属性可以使用外,还提供了自己定义属性的方法,即在在res/values文件下新建一个attrs.xml,加入自定义的属性:



在布局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仅支持该声明),旧版可使用第二种写法。

<?xml version="1.0" encoding="utf-8"?>
<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);
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  maxHeight maxWidth