Android自定义控件<二>

来源:互联网 发布:js placeholder颜色 编辑:程序博客网 时间:2024/06/05 06:26

PercentFrameLayout源码分析

开始学习继承ViewGroup的自定义控件,还是从分析源码开始,这次挑了一个谷歌官方的PercentFrameLayout来学习。
首先 PercentFrameLayout 继承FrameLayout,重写了以下方法:

   @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) {        mHelper.adjustChildren(widthMeasureSpec, heightMeasureSpec);        super.onMeasure(widthMeasureSpec, heightMeasureSpec);        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);        mHelper.restoreOriginalParams();    }

generateDefaultLayoutParams() 和generateLayoutParams(AttributeSet attrs) 是来生成自己所定义扩展的LayoutParams,再看它实现方法中的内部类LayoutParams代码:

 public static class LayoutParams extends FrameLayout.LayoutParams            implements PercentLayoutHelper.PercentLayoutParams {        private PercentLayoutHelper.PercentLayoutInfo mPercentLayoutInfo;        public LayoutParams(Context c, AttributeSet attrs) {            super(c, attrs);            mPercentLayoutInfo = PercentLayoutHelper.getPercentLayoutInfo(c, attrs);        }        public LayoutParams(int width, int height) {            super(width, height);        }        public LayoutParams(int width, int height, int gravity) {            super(width, height, gravity);        }        public LayoutParams(ViewGroup.LayoutParams source) {            super(source);        }        public LayoutParams(MarginLayoutParams source) {            super(source);        }        public LayoutParams(FrameLayout.LayoutParams source) {            super((MarginLayoutParams) source);            gravity = source.gravity;        }        public LayoutParams(LayoutParams source) {            this((FrameLayout.LayoutParams) source);            mPercentLayoutInfo = source.mPercentLayoutInfo;        }        @Override        public PercentLayoutHelper.PercentLayoutInfo getPercentLayoutInfo() {            if (mPercentLayoutInfo == null) {                mPercentLayoutInfo = new PercentLayoutHelper.PercentLayoutInfo();            }            return mPercentLayoutInfo;        }        @Override        protected void setBaseAttributes(TypedArray a, int widthAttr, int heightAttr) {            PercentLayoutHelper.fetchWidthAndHeight(this, a, widthAttr, heightAttr);        }    }

通过源码看出LayoutParams 除了继承父类的FrameLayout.LayouParams还实现了自己定义的一个接口PercentLayoutHelper.PercentLayoutParams。方法中setBaseAttributes是重写ViewGroup的方法,这个方法的意思就是从属性中提取Params; getPercentLayoutInfo是实现PercentLayoutParams的方法,目的是返回一个自定义的PercentLayoutInfo,PercentLayoutInfo这个类定义了PercentLayout的各种属性,以及设置这些属性:

  public static class PercentLayoutInfo {        /** The decimal value of the percentage-based width. */        public float widthPercent;        /** The decimal value of the percentage-based height. */        public float heightPercent;        /** The decimal value of the percentage-based left margin. */        public float leftMarginPercent;        /** The decimal value of the percentage-based top margin. */        public float topMarginPercent;        /** The decimal value of the percentage-based right margin. */        public float rightMarginPercent;        /** The decimal value of the percentage-based bottom margin. */        public float bottomMarginPercent;        /** The decimal value of the percentage-based start margin. */        public float startMarginPercent;        /** The decimal value of the percentage-based end margin. */        public float endMarginPercent;        /** The decimal value of the percentage-based aspect ratio. */        public float aspectRatio;        /* package */ final PercentMarginLayoutParams mPreservedParams;        public PercentLayoutInfo() {            widthPercent = -1f;            heightPercent = -1f;            leftMarginPercent = -1f;            topMarginPercent = -1f;            rightMarginPercent = -1f;            bottomMarginPercent = -1f;            startMarginPercent = -1f;            endMarginPercent = -1f;            mPreservedParams = new PercentMarginLayoutParams(0, 0);        }        /**         * Fills the {@link ViewGroup.LayoutParams#width} and {@link ViewGroup.LayoutParams#height}         * fields of the passed {@link ViewGroup.LayoutParams} object based on currently set         * percentage values.         */        public void fillLayoutParams(ViewGroup.LayoutParams params, int widthHint,                int heightHint) {            // Preserve the original layout params, so we can restore them after the measure step.            mPreservedParams.width = params.width;            mPreservedParams.height = params.height;            // We assume that width/height set to 0 means that value was unset. This might not            // necessarily be true, as the user might explicitly set it to 0. However, we use this            // information only for the aspect ratio. If the user set the aspect ratio attribute,            // it means they accept or soon discover that it will be disregarded.            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);            }            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;                }            }            if (DEBUG) {                Log.d(TAG, "after fillLayoutParams: (" + params.width + ", " + params.height + ")");            }        }        /**         * @deprecated Use         * {@link #fillMarginLayoutParams(View, ViewGroup.MarginLayoutParams, int, int)}         * for proper RTL support.         */        @Deprecated        public void fillMarginLayoutParams(ViewGroup.MarginLayoutParams params,                int widthHint, int heightHint) {            fillMarginLayoutParams(null, params, widthHint, heightHint);        }        /**         * Fills the margin fields of the passed {@link ViewGroup.MarginLayoutParams} object based         * on currently set percentage values and the current layout direction of the passed         * {@link View}.         */        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));            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)) {                // Force the resolve pass so that start / end margins are propagated to the                // matching left / right fields                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);        }        /**         * 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));        }        /**         * 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;        }    }

还剩下最后一个类PercentLayoutHelper,这个类就是完成百分比布局的类:

public class PercentLayoutHelper {    private static final String TAG = "PercentLayout";    private static final boolean DEBUG = false;    private static final boolean VERBOSE = false;    private final ViewGroup mHost;    public PercentLayoutHelper(@NonNull ViewGroup host) {        if (host == null) {            throw new IllegalArgumentException("host must be non-null");        }        mHost = host;    }    /**     * Helper method to be called from {@link ViewGroup.LayoutParams#setBaseAttributes} override     * that reads layout_width and layout_height attribute values without throwing an exception if     * they aren't present.     */    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);    }    /**     * Iterates over children and changes their width and height to one calculated from percentage     * values.     * @param widthMeasureSpec Width MeasureSpec of the parent ViewGroup.     * @param heightMeasureSpec Height MeasureSpec of the parent ViewGroup.     */    public void adjustChildren(int widthMeasureSpec, int heightMeasureSpec) {        if (DEBUG) {            Log.d(TAG, "adjustChildren: " + mHost + " widthMeasureSpec: "                    + View.MeasureSpec.toString(widthMeasureSpec) + " heightMeasureSpec: "                    + View.MeasureSpec.toString(heightMeasureSpec));        }        // Calculate available space, accounting for host's paddings        int widthHint = View.MeasureSpec.getSize(widthMeasureSpec) - mHost.getPaddingLeft()                - mHost.getPaddingRight();        int heightHint = View.MeasureSpec.getSize(heightMeasureSpec) - mHost.getPaddingTop()                - mHost.getPaddingBottom();        for (int i = 0, N = mHost.getChildCount(); i < N; i++) {            View view = mHost.getChildAt(i);            ViewGroup.LayoutParams params = view.getLayoutParams();            if (DEBUG) {                Log.d(TAG, "should adjust " + view + " " + params);            }            if (params instanceof PercentLayoutParams) {                PercentLayoutInfo info =                        ((PercentLayoutParams) params).getPercentLayoutInfo();                if (DEBUG) {                    Log.d(TAG, "using " + info);                }                if (info != null) {                    if (params instanceof ViewGroup.MarginLayoutParams) {                        info.fillMarginLayoutParams(view, (ViewGroup.MarginLayoutParams) params,                                widthHint, heightHint);                    } else {                        info.fillLayoutParams(params, widthHint, heightHint);                    }                }            }        }    }    /**     * Constructs a PercentLayoutInfo from attributes associated with a View. Call this method from     * {@code LayoutParams(Context c, AttributeSet attrs)} constructor.     */    public static PercentLayoutInfo getPercentLayoutInfo(Context context,            AttributeSet attrs) {        PercentLayoutInfo info = null;        TypedArray array = context.obtainStyledAttributes(attrs, R.styleable.PercentLayout_Layout);        float value = array.getFraction(R.styleable.PercentLayout_Layout_layout_widthPercent, 1, 1,                -1f);        if (value != -1f) {            if (VERBOSE) {                Log.v(TAG, "percent width: " + value);            }            info = info != null ? info : new PercentLayoutInfo();            info.widthPercent = value;        }        value = array.getFraction(R.styleable.PercentLayout_Layout_layout_heightPercent, 1, 1, -1f);        if (value != -1f) {            if (VERBOSE) {                Log.v(TAG, "percent height: " + value);            }            info = info != null ? info : new PercentLayoutInfo();            info.heightPercent = value;        }        value = array.getFraction(R.styleable.PercentLayout_Layout_layout_marginPercent, 1, 1, -1f);        if (value != -1f) {            if (VERBOSE) {                Log.v(TAG, "percent margin: " + value);            }            info = info != null ? info : new PercentLayoutInfo();            info.leftMarginPercent = value;            info.topMarginPercent = value;            info.rightMarginPercent = value;            info.bottomMarginPercent = value;        }        value = array.getFraction(R.styleable.PercentLayout_Layout_layout_marginLeftPercent, 1, 1,                -1f);        if (value != -1f) {            if (VERBOSE) {                Log.v(TAG, "percent left margin: " + value);            }            info = info != null ? info : new PercentLayoutInfo();            info.leftMarginPercent = value;        }        value = array.getFraction(R.styleable.PercentLayout_Layout_layout_marginTopPercent, 1, 1,                -1f);        if (value != -1f) {            if (VERBOSE) {                Log.v(TAG, "percent top margin: " + value);            }            info = info != null ? info : new PercentLayoutInfo();            info.topMarginPercent = value;        }        value = array.getFraction(R.styleable.PercentLayout_Layout_layout_marginRightPercent, 1, 1,                -1f);        if (value != -1f) {            if (VERBOSE) {                Log.v(TAG, "percent right margin: " + value);            }            info = info != null ? info : new PercentLayoutInfo();            info.rightMarginPercent = value;        }        value = array.getFraction(R.styleable.PercentLayout_Layout_layout_marginBottomPercent, 1, 1,                -1f);        if (value != -1f) {            if (VERBOSE) {                Log.v(TAG, "percent bottom margin: " + value);            }            info = info != null ? info : new PercentLayoutInfo();            info.bottomMarginPercent = value;        }        value = array.getFraction(R.styleable.PercentLayout_Layout_layout_marginStartPercent, 1, 1,                -1f);        if (value != -1f) {            if (VERBOSE) {                Log.v(TAG, "percent start margin: " + value);            }            info = info != null ? info : new PercentLayoutInfo();            info.startMarginPercent = value;        }        value = array.getFraction(R.styleable.PercentLayout_Layout_layout_marginEndPercent, 1, 1,                -1f);        if (value != -1f) {            if (VERBOSE) {                Log.v(TAG, "percent end margin: " + value);            }            info = info != null ? info : new PercentLayoutInfo();            info.endMarginPercent = value;        }        value = array.getFraction(R.styleable.PercentLayout_Layout_layout_aspectRatio, 1, 1, -1f);        if (value != -1f) {            if (VERBOSE) {                Log.v(TAG, "aspect ratio: " + value);            }            info = info != null ? info : new PercentLayoutInfo();            info.aspectRatio = value;        }        array.recycle();        if (DEBUG) {            Log.d(TAG, "constructed: " + info);        }        return info;    }    /**     * Iterates over children and restores their original dimensions that were changed for     * percentage values. Calling this method only makes sense if you previously called     * {@link PercentLayoutHelper#adjustChildren(int, int)}.     */    public void restoreOriginalParams() {        for (int i = 0, N = mHost.getChildCount(); i < N; i++) {            View view = mHost.getChildAt(i);            ViewGroup.LayoutParams params = view.getLayoutParams();            if (DEBUG) {                Log.d(TAG, "should restore " + view + " " + params);            }            if (params instanceof PercentLayoutParams) {                PercentLayoutInfo info =                        ((PercentLayoutParams) params).getPercentLayoutInfo();                if (DEBUG) {                    Log.d(TAG, "using " + info);                }                if (info != null) {                    if (params instanceof ViewGroup.MarginLayoutParams) {                        info.restoreMarginLayoutParams((ViewGroup.MarginLayoutParams) params);                    } else {                        info.restoreLayoutParams(params);                    }                }            }        }    }    /**     * Iterates over children and checks if any of them would like to get more space than it     * received through the percentage dimension.     *     * If you are building a layout that supports percentage dimensions you are encouraged to take     * advantage of this method. The developer should be able to specify that a child should be     * remeasured by adding normal dimension attribute with {@code wrap_content} value. For example     * he might specify child's attributes as {@code app:layout_widthPercent="60%p"} and     * {@code android:layout_width="wrap_content"}. In this case if the child receives too little     * space, it will be remeasured with width set to {@code WRAP_CONTENT}.     *     * @return True if the measure phase needs to be rerun because one of the children would like     * to receive more space.     */    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 (DEBUG) {                Log.d(TAG, "should handle measured state too small " + view + " " + params);            }            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;                    }                }            }        }        if (DEBUG) {            Log.d(TAG, "should trigger second measure pass: " + needsSecondMeasure);        }        return needsSecondMeasure;    }    private static boolean shouldHandleMeasuredWidthTooSmall(View view, PercentLayoutInfo info) {        int state = ViewCompat.getMeasuredWidthAndState(view) & ViewCompat.MEASURED_STATE_MASK;        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;        return state == ViewCompat.MEASURED_STATE_TOO_SMALL && info.heightPercent >= 0 &&                info.mPreservedParams.height == ViewGroup.LayoutParams.WRAP_CONTENT;    }

现在挨个说他的方法:

/**     * Helper method to be called from {@link ViewGroup.LayoutParams#setBaseAttributes} override     * that reads layout_width and layout_height attribute values without throwing an exception if     * they aren't present.     */    public static void fetchWidthAndHeight(ViewGroup.LayoutParams params, TypedArray array,            int widthAttr, int heightAttr) {    }

我理解英文的意思是:这个方法被ViewGroup.LayoutParams#setBaseAttributes调用,从属性读取宽和高属性。

 /**     * Iterates over children and changes their width and height to one calculated from percentage     * values.     * @param widthMeasureSpec Width MeasureSpec of the parent ViewGroup.     * @param heightMeasureSpec Height MeasureSpec of the parent ViewGroup.     */    public void adjustChildren(int widthMeasureSpec, int heightMeasureSpec)

作用:计算调整子View的宽和高

  /**     * Constructs a PercentLayoutInfo from attributes associated with a View. Call this method from     * {@code LayoutParams(Context c, AttributeSet attrs)} constructor.     */    public static PercentLayoutInfo getPercentLayoutInfo(Context context,            AttributeSet attrs) {

作用:从属性中读取参数,设置给PercentLayoutInfo

  /**     * Iterates over children and restores their original dimensions that were changed for     * percentage values. Calling this method only makes sense if you previously called     * {@link PercentLayoutHelper#adjustChildren(int, int)}.     */    public void restoreOriginalParams()

作用:保存属性
handleMeasuredStateTooSmall() 作用:重新测量子View的空间,判断子View是否需要调整。

0 0
原创粉丝点击