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

完整项目示例Git仓库
https://git.oschina.net/jokerlee/CustomFrameLayout.git

Android View的宽高属性

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

    

Android自定义attr属性

View除了android赋予的默认属性可以使用外,还提供了自己定义属性的方法,即在在res/values文件下新建一个attrs.xml,加入自定义的属性:
Android自定义可控最大宽高的Layout_第1张图片

在布局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);
    }

你可能感兴趣的:(Android)