support-percent

来源:互联网 发布:页游 知乎 编辑:程序博客网 时间:2024/05/17 07:46

一、Android百分比支持库介绍

Android-percent-support这个库可以通过百分比控制控件的大小

在应用module中的build.gradle中添加

compile 'com.android.support:percent:25.1.0'
目前支持FrameLayout和RelativeLayout,封装的类叫PercentFrameLayout和PercentRelativeLayout.
支持的属性有

layout_widthPercent
layout_heightPercent
layout_marginPercent
layout_marginLeftPercent
layout_marginTopPercent
layout_marginRightPercent
layout_marginBottomPercent
layout_marginStartPercent
layout_marginEndPercent
layout_aspectRatio

可以看到支持宽高,以及margin。另外需要特别说明一下layout_aspectRatio

layout_aspectRatio表示设置计算比例,只需要定义宽或高,然后就可以根据aspectRatio自动计算出另一个尺寸,比如

<?xml version="1.0" encoding="utf-8"?><android.support.percent.PercentRelativeLayout    xmlns:android="http://schemas.android.com/apk/res/android"    xmlns:app="http://schemas.android.com/apk/res-auto"    android:layout_width="match_parent"    android:layout_height="match_parent"><TextViewandroid:layout_width="300dp"android:layout_height="0dp"app:layout_aspectRatio="178%"/></android.support.percent.PercentRelativeLayout>

面View指定了view的宽度为300dp,根据layout_aspectRatio="178%"自动计算出height。
"178%"表示width/height=1.78,如果是"78%"则表示width/height=0.78
由此可计算出height=300dp/1.78;
则根据比例1.78自动计算出height=300/1.78=169

注意:
1、如果设置了layout_widthPercent就不用具体设置layout_width的值,设置了layout_heightPercent就不用具体设置layout_height,
就算设置了值也会被忽略

<?xml version="1.0" encoding="utf-8"?><android.support.percent.PercentRelativeLayout    xmlns:android="http://schemas.android.com/apk/res/android"    xmlns:app="http://schemas.android.com/apk/res-auto"    android:layout_width="match_parent"    android:layout_height="match_parent">    <!-- 因为设置了layout_widthPercent和layout_heightPercent 所以不用指定layout_width和layout_height的值-->    <TextView        android:id="@+id/top_left"android:layout_width="0dp"android:layout_height="0dp"        android:layout_alignParentTop="true"        android:background="#ff44aacc"        app:layout_heightPercent="20%"        app:layout_widthPercent="70%" />    <!--  layout_width=0、layout_height=300dp       设置宽、高的值也会被忽略  -->    <View        android:id="@+id/top_right"        android:layout_width="0dp"        android:layout_height="300dp"        android:layout_alignParentTop="true"        android:layout_toRightOf="@+id/top_left"        android:background="#ffe40000"        app:layout_heightPercent="20%"        app:layout_widthPercent="30%" /></android.support.percent.PercentRelativeLayout>
2、更好的显示尺寸

虽然可以不指定layout_width或layout_height,但是如果view的尺寸不太确定,又不想让百分比尺寸限制内容显示完全,可以设置

layout_width/height="wrap_content"

这样当percentage size is too small for the View's content,it will be resized using wrap_content rule当百分比尺寸小于内容所需的宽高,可以重新计算尺寸以便显示完全.

二、源码解析

下面我们以PercentRelativelayout来分析一下源码

PercentRelativeLayout.java

public class PercentRelativeLayout extends RelativeLayout {    private final PercentLayoutHelper mHelper = new PercentLayoutHelper(this);    public PercentRelativeLayout(Context context) {        super(context);    }    public PercentRelativeLayout(Context context, AttributeSet attrs) {        super(context, attrs);    }    public PercentRelativeLayout(Context context, AttributeSet attrs, int defStyle) {        super(context, attrs, defStyle);    }    @Override    protected LayoutParams generateDefaultLayoutParams() {        return new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);    }    @Override    public LayoutParams generateLayoutParams(AttributeSet attrs) {        return new LayoutParams(getContext(), attrs);    }    @Override    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {        //根据子view中设置的百分比,计算宽高和margin值        mHelper.adjustChildren(widthMeasureSpec, heightMeasureSpec);        super.onMeasure(widthMeasureSpec, heightMeasureSpec);        //如果计算的百分比宽高值小于view的size,则设置layoutParam.width/height=wrap_content        if (mHelper.handleMeasuredStateTooSmall()) {            super.onMeasure(widthMeasureSpec, heightMeasureSpec);        }    }    @Override    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {        super.onLayout(changed, left, top, right, bottom);        //恢复原始的layoutparam        mHelper.restoreOriginalParams();    }    /**     * 继承RelativeLayout.LayoutParams 并实现PercentLayoutParams接口     */    public static class LayoutParams extends RelativeLayout.LayoutParams            implements PercentLayoutHelper.PercentLayoutParams {        private PercentLayoutHelper.PercentLayoutInfo mPercentLayoutInfo;        public LayoutParams(Context c, AttributeSet attrs) {            super(c, attrs);            /**             *  获取layoutparams中的百分比参数,然后封装到PercentLayoutInfo中,在onMeasure中遍历子View并判断计算宽高、margin值。             */            mPercentLayoutInfo = PercentLayoutHelper.getPercentLayoutInfo(c, attrs);        }        public LayoutParams(int width, int height) {            super(width, height);        }        public LayoutParams(ViewGroup.LayoutParams source) {            super(source);        }        public LayoutParams(MarginLayoutParams source) {            super(source);        }        @Override        public PercentLayoutHelper.PercentLayoutInfo getPercentLayoutInfo() {            if (mPercentLayoutInfo == null) {                mPercentLayoutInfo = new PercentLayoutHelper.PercentLayoutInfo();            }            return mPercentLayoutInfo;        }        @Override        protected void setBaseAttributes(TypedArray a, int widthAttr, int heightAttr) {            /**             * 重写setBaseAttributes,避免不设置layout_width或layout_height导致的异常发生。下面是父类中的写法:             width = a.getLayoutDimension(widthAttr, "layout_width");             height = a.getLayoutDimension(heightAttr, "layout_height");             */            PercentLayoutHelper.fetchWidthAndHeight(this, a, widthAttr, heightAttr);        }    }}
可以看到核心功能都在PercentLayoutHelper中。

PercentLayoutHelper.java

public class PercentLayoutHelper {    private static final String TAG = "PercentLayout";    public static  boolean DEBUG = true;    private static final boolean VERBOSE = true;    private final ViewGroup mHost; //百分比View容器,即PercentRelativeLayout或PercentFrameLayout    public PercentLayoutHelper(@NonNull ViewGroup host) {        if (host == null) {            throw new IllegalArgumentException("host must be non-null");        }        mHost = host;    }    /**     * 重写setBaseAttributes,避免不设置layout_width或layout_height导致的异常发生。下面是父类中的写法:     width = a.getLayoutDimension(widthAttr, "layout_width");     height = a.getLayoutDimension(heightAttr, "layout_height");     */    public static void fetchWidthAndHeight(ViewGroup.LayoutParams params, TypedArray array,            int widthAttr, int heightAttr) {        params.width = array.getLayoutDimension(widthAttr, 0);        params.height = array.getLayoutDimension(heightAttr, 0);        Log.d(TAG,"params.width="+params.width+",params.height="+params.height);    }    /**     * 迭代所有子View,根据LayoutParam中设置的百分比计算宽高、margin值     */    public void adjustChildren(int widthMeasureSpec, int heightMeasureSpec) {        // 获取容器的宽高        int widthHint = View.MeasureSpec.getSize(widthMeasureSpec) - mHost.getPaddingLeft()                - mHost.getPaddingRight();        int heightHint = View.MeasureSpec.getSize(heightMeasureSpec) - mHost.getPaddingTop()                - mHost.getPaddingBottom();        /**         * 遍历children,根据         */        for (int i = 0, N = mHost.getChildCount(); i < N; i++) {            View view = mHost.getChildAt(i);            ViewGroup.LayoutParams params = view.getLayoutParams();            if (params instanceof PercentLayoutParams) { //子view的layoutParams是父类容器的LayoutParams                PercentLayoutInfo info =                        ((PercentLayoutParams) params).getPercentLayoutInfo();                       if (info != null) {                    if (params instanceof ViewGroup.MarginLayoutParams) {                        info.fillMarginLayoutParams(view, (ViewGroup.MarginLayoutParams) params,                                widthHint, heightHint);                    } else {                        info.fillLayoutParams(params, widthHint, heightHint);                    }                }            }        }    }    /**     * 获取LayoutParams中属性值     */    public static PercentLayoutInfo getPercentLayoutInfo(Context context,            AttributeSet attrs) {        PercentLayoutInfo info = null;        TypedArray array = context.obtainStyledAttributes(attrs, R.styleable.PercentLayout_Layout);        //获取宽度百分比、margin百分比          .........          return info;    }    /**     * 重新恢复原本的尺寸值,也就是说onMeasure里面的对值进行了改变,测量完成后。在这个地方,将值又恢复成如果布局文件中的值     */    public void restoreOriginalParams() {        Log.d(TAG,"---------------------------restoreOriginalParams---------------------");        for (int i = 0, N = mHost.getChildCount(); i < N; i++) {            View view = mHost.getChildAt(i);            ViewGroup.LayoutParams params = view.getLayoutParams();                      if (params instanceof PercentLayoutParams) {                PercentLayoutInfo info =                        ((PercentLayoutParams) params).getPercentLayoutInfo();                               if (info != null) {                    if (params instanceof ViewGroup.MarginLayoutParams) {                        info.restoreMarginLayoutParams((ViewGroup.MarginLayoutParams) params);                    } else {                        info.restoreLayoutParams(params);                    }                }            }        }    }    /**     * 避免设置的百分比计算出来的尺寸过小,导致内容显示不全,可设置layout_width/height=WRAP_CONTENT,这样可重新测量     */    public boolean handleMeasuredStateTooSmall() {        boolean needsSecondMeasure = false;        for (int i = 0, N = mHost.getChildCount(); i < N; i++) {            View view = mHost.getChildAt(i);            ViewGroup.LayoutParams params = view.getLayoutParams();                      if (params instanceof PercentLayoutParams) {                PercentLayoutInfo info =                        ((PercentLayoutParams) params).getPercentLayoutInfo();                if (info != null) {                    if (shouldHandleMeasuredWidthTooSmall(view, info)) {                        needsSecondMeasure = true;                        params.width = ViewGroup.LayoutParams.WRAP_CONTENT;                    }                    if (shouldHandleMeasuredHeightTooSmall(view, info)) {                        needsSecondMeasure = true;                        params.height = ViewGroup.LayoutParams.WRAP_CONTENT;                    }                }            }        }               return needsSecondMeasure;    }    private static boolean shouldHandleMeasuredWidthTooSmall(View view, PercentLayoutInfo info) {        int state = ViewCompat.getMeasuredWidthAndState(view) & ViewCompat.MEASURED_STATE_MASK;        //设置了widthPercent并且layout_width=WRAP_CONTENT;        return state == ViewCompat.MEASURED_STATE_TOO_SMALL && info.widthPercent >= 0 &&                info.mPreservedParams.width == ViewGroup.LayoutParams.WRAP_CONTENT;    }    private static boolean shouldHandleMeasuredHeightTooSmall(View view, PercentLayoutInfo info) {        int state = ViewCompat.getMeasuredHeightAndState(view) & ViewCompat.MEASURED_STATE_MASK;        //设置了heightPercent并且layout_height=WRAP_CONTENT;        return state == ViewCompat.MEASURED_STATE_TOO_SMALL && info.heightPercent >= 0 &&                info.mPreservedParams.height == ViewGroup.LayoutParams.WRAP_CONTENT;    }    static class PercentMarginLayoutParams extends ViewGroup.MarginLayoutParams {        private boolean mIsHeightComputedFromAspectRatio;        private boolean mIsWidthComputedFromAspectRatio;        public PercentMarginLayoutParams(int width, int height) {            super(width, height);        }    }    /**     * Container for information about percentage dimensions and margins. It acts as an extension     * for {@code LayoutParams}.     */    public static class PercentLayoutInfo {        public float widthPercent;        public float heightPercent;        public float leftMarginPercent;        public float topMarginPercent;        public float rightMarginPercent;        public float bottomMarginPercent;        public float startMarginPercent;        public float endMarginPercent;        public float aspectRatio;         final PercentMarginLayoutParams mPreservedParams; //保存原始的尺寸信息,当onMeasure改变后尺寸后,在onLayout中恢复原始的参数信息。        public PercentLayoutInfo() {            widthPercent = -1f;            heightPercent = -1f;            leftMarginPercent = -1f;            topMarginPercent = -1f;            rightMarginPercent = -1f;            bottomMarginPercent = -1f;            startMarginPercent = -1f;            endMarginPercent = -1f;            mPreservedParams = new PercentMarginLayoutParams(0, 0);        }        /**         * 设置layoutparams的宽、高         */        public void fillLayoutParams(ViewGroup.LayoutParams params, int widthHint,                int heightHint) {            // 保留原始的layout params, 以便onMeasure后在onLayout中恢复.            mPreservedParams.width = params.width;            mPreservedParams.height = params.height;            final boolean widthNotSet =                    (mPreservedParams.mIsWidthComputedFromAspectRatio                            || mPreservedParams.width == 0) && (widthPercent < 0);            final boolean heightNotSet =                    (mPreservedParams.mIsHeightComputedFromAspectRatio                            || mPreservedParams.height == 0) && (heightPercent < 0);             //根据宽度百分比计算宽度            if (widthPercent >= 0) {                params.width = Math.round(widthHint * widthPercent);            }            //根据高度百分比计算高度            if (heightPercent >= 0) {                params.height = Math.round(heightHint * heightPercent);            }            //如果只指定了宽或高,根据aspectRatio计算另一个值            if (aspectRatio >= 0) {                if (widthNotSet) {                    params.width = Math.round(params.height * aspectRatio);                    // Keep track that we've filled the width based on the height and aspect ratio.                    mPreservedParams.mIsWidthComputedFromAspectRatio = true;                }                if (heightNotSet) {                    params.height = Math.round(params.width / aspectRatio);                    // Keep track that we've filled the height based on the width and aspect ratio.                    mPreservedParams.mIsHeightComputedFromAspectRatio = true;                }            }        }        /**         * 根据百分比设置margin值         */        public void fillMarginLayoutParams(View view, ViewGroup.MarginLayoutParams params,                int widthHint, int heightHint) {            //设置宽高            fillLayoutParams(params, widthHint, heightHint);            // Preserve the original margins, so we can restore them after the measure step.            mPreservedParams.leftMargin = params.leftMargin;            mPreservedParams.topMargin = params.topMargin;            mPreservedParams.rightMargin = params.rightMargin;            mPreservedParams.bottomMargin = params.bottomMargin;            MarginLayoutParamsCompat.setMarginStart(mPreservedParams,                    MarginLayoutParamsCompat.getMarginStart(params));            MarginLayoutParamsCompat.setMarginEnd(mPreservedParams,                    MarginLayoutParamsCompat.getMarginEnd(params));            //设置margin            if (leftMarginPercent >= 0) {                params.leftMargin = Math.round(widthHint * leftMarginPercent);            }            if (topMarginPercent >= 0) {                params.topMargin = Math.round(heightHint * topMarginPercent);            }            if (rightMarginPercent >= 0) {                params.rightMargin = Math.round(widthHint * rightMarginPercent);            }            if (bottomMarginPercent >= 0) {                params.bottomMargin = Math.round(heightHint * bottomMarginPercent);            }            boolean shouldResolveLayoutDirection = false;            if (startMarginPercent >= 0) {                MarginLayoutParamsCompat.setMarginStart(params,                        Math.round(widthHint * startMarginPercent));                shouldResolveLayoutDirection = true;            }            if (endMarginPercent >= 0) {                MarginLayoutParamsCompat.setMarginEnd(params,                        Math.round(widthHint * endMarginPercent));                shouldResolveLayoutDirection = true;            }            if (shouldResolveLayoutDirection && (view != null)) {                MarginLayoutParamsCompat.resolveLayoutDirection(params,                        ViewCompat.getLayoutDirection(view));            }            if (DEBUG) {                Log.d(TAG, "after fillMarginLayoutParams: (" + params.width + ", " + params.height                        + ")");            }        }        @Override        public String toString() {            return String.format("PercentLayoutInformation width: %f height %f, margins (%f, %f, "                            + " %f, %f, %f, %f)", widthPercent, heightPercent, leftMarginPercent,                    topMarginPercent, rightMarginPercent, bottomMarginPercent, startMarginPercent,                    endMarginPercent);        }        /**         * 设置layouparams中的margin值         * Restores the original dimensions and margins after they were changed for percentage based         * values. You should call this method only if you previously called         * {@link PercentLayoutHelper.PercentLayoutInfo#fillMarginLayoutParams(View, ViewGroup.MarginLayoutParams, int, int)}.         */        public void restoreMarginLayoutParams(ViewGroup.MarginLayoutParams params) {            restoreLayoutParams(params);            params.leftMargin = mPreservedParams.leftMargin;            params.topMargin = mPreservedParams.topMargin;            params.rightMargin = mPreservedParams.rightMargin;            params.bottomMargin = mPreservedParams.bottomMargin;            MarginLayoutParamsCompat.setMarginStart(params,                    MarginLayoutParamsCompat.getMarginStart(mPreservedParams));            MarginLayoutParamsCompat.setMarginEnd(params,                    MarginLayoutParamsCompat.getMarginEnd(mPreservedParams));        }        /**         *  设置layouparams的原始宽高         * Restores original dimensions after they were changed for percentage based values.         * You should call this method only if you previously called         * {@link PercentLayoutHelper.PercentLayoutInfo#fillLayoutParams(ViewGroup.LayoutParams, int, int)}.         */        public void restoreLayoutParams(ViewGroup.LayoutParams params) {            if (!mPreservedParams.mIsWidthComputedFromAspectRatio) {                // Only restore the width if we didn't compute it based on the height and                // aspect ratio in the fill pass.                params.width = mPreservedParams.width;            }            if (!mPreservedParams.mIsHeightComputedFromAspectRatio) {                // Only restore the height if we didn't compute it based on the width and                // aspect ratio in the fill pass.                params.height = mPreservedParams.height;            }            // Reset the tracking flags.            mPreservedParams.mIsWidthComputedFromAspectRatio = false;            mPreservedParams.mIsHeightComputedFromAspectRatio = false;        }    }    public interface PercentLayoutParams {        PercentLayoutInfo getPercentLayoutInfo();    }}


注释已经写的很清楚了,就不做详解了。

0 0
原创粉丝点击