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

来源:互联网 发布:dg恢复数据 编辑:程序博客网 时间:2024/06/06 03:14

完整项目示例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);    }
0 0