自定义百分比布局RelatvieLayout

作者: 逝我 | 来源:发表于2016-08-02 21:55 被阅读800次
    PercentRelativeLayout.png

    自定义布局系列之自定义RelativeLayout

    众所周知 , Android碎片化非常严重 , 全球那么多机型 , 数以千计的分辨率 , 给我们的开发带来的不小的难度 , 那么机型都需要配置 , 谷歌也做了一些努力 , 退出了与分辨率无关的dp , sp等等单位 , 但这些单位在不同的机型上面还是显示各异 , 谷歌在15年的时候推出了百分比布局 , 可以在一定程度上 , 解决我们的布局差异问题 , 但却只有两种百分比布局 , PercentRelativeLayout 和 PercentFrameLayout 。

     下面我们就深究其理 , 写一个我们自己的百分比布局 。
    

    在写自定义布局之前 , 我们首先要弄明白的一点是 , 怎样进行百分比布局 , 只有明白其中原理 , 代码就可以随之写出了 。我们知道 , View最重要的三个方法是onMeasure ,onLayout , onDraw 这三个方法决定了View的宽高,在什么位置 , 以及呈现的形态 , 这和我们绘画是一致的 , 都需要明确 , 画什么(确定宽高属性) , 在什么地方画 (确定绘画的位置), 怎样画(用什么颜色的画笔,尺子等)等问题 。

    明确了上述问题 , 接下来就是分析我们的百分比布局 , 用到了哪些方法 。 我们的百分比布局 , 主要改变的是子控件的宽高,以及外边距的属性 , 也就是画什么 , 需要多宽多高 , 所以我们需要在onMeasure方法里面做工作 , 去改变子控件的属性值 。

    我们都写过LayoutInflater.infalte()这个方法 , 将我们的xml文件布局inflate成一个View对象 , 我们在写自定义控件的时候 , 也都需要实现一个带属性参数的构造函数,PercentRelativeLayout(Context context, AttributeSet attrs) ,如果没有 , 则在xml中使用中会报错 。 在xml文件中写的View控件标签 , 最后都会inflate成一个View对象 ,而inflate里面 , 使用的pull解析 , 将xml标签解析成一个个View对象 。

    
        final Resources res = getContext().getResources();
            if (DEBUG) {
                Log.d(TAG, "INFLATING from resource: \"" + res.getResourceName(resource) + "\" ("
                        + Integer.toHexString(resource) + ")");
            }
    
            final XmlResourceParser parser = res.getLayout(resource);
            try {
                return inflate(parser, root, attachToRoot);
            } finally {
                parser.close();
            }
    
    

    在解析View标签的时候 , 必然也会将标签的属性一并解析 , 并将标签属性设置到LayoutParams对象中 , 所以我们才可以通过子控件拿到布局属性 。

    
        // 拿到根布局的View
        final View temp = createViewFromTag(root, name, inflaterContext, attrs);
    
        ViewGroup.LayoutParams params = null;
    
        if (root != null) {
            if (DEBUG) {
                System.out.println("Creating params from root: " +
                        root);
            }
            // 创建一个布局参数对象
            params = root.generateLayoutParams(attrs);
            if (!attachToRoot) {
                // Set the layout params for temp if we are not
                // attaching. (If we are, we use addView, below)
                temp.setLayoutParams(params);
            }
        }
    
        /**
         * Returns a new set of layout parameters based on the supplied attributes set.
         *
         * @param attrs the attributes to build the layout parameters from
         *
         * @return an instance of {@link android.view.ViewGroup.LayoutParams} or one
         *         of its descendants
         */
        public LayoutParams generateLayoutParams(AttributeSet attrs) {
            return new LayoutParams(getContext(), attrs);
        }
    
    

    知道了上面两点 , 我们就可以自定义一个我们自己的布局参数 , 通过generateLayoutParams() 返回我们的自定义布局参数对象 。之后我们就可以通过判断是否是我们自定义的布局参数 , 来进行子控件的宽高改变 。

    首先我们将自定义的布局属性得到 , 并实例化自定义布局属性对象:

        
        /**
         * 自定义布局参数类 , 因为只是拓展RelativeLayout属性 , 所以继承自RelativeLayout.LayoutParams类
         * ,保留原有的RelativeLayout属性 。
         */
        static class PercentLayoutParams extends RelativeLayout.LayoutParams {
    
            public PercentLayoutInfo info = null;
    
            public PercentLayoutParams(Context c, AttributeSet attrs) {
                super(c, attrs);
    
                /* 初始化布局自定义布局属性信息对象 */
                info = info != null ? info : new PercentLayoutInfo();
    
                /* 得到自定义布局属性值 */
                TypedArray typedArray = c.obtainStyledAttributes(attrs, R.styleable.PercentLayout);
                info.setPercentHeight(typedArray.getFloat(R.styleable.PercentLayout_layout_percentHeight, 0));
                info.setPercentWidth(typedArray.getFloat(R.styleable.PercentLayout_layout_percentWidth, 0));
    
                info.setPercentMarginOfWidth(typedArray.getFloat(R.styleable.PercentLayout_layout_percentMarginOfWidth, 0));
                info.setPercentMarginOfHeight(typedArray.getFloat(R.styleable.PercentLayout_layout_percentMarginOfHeight, 0));
                info.setPercentMarginLeft(typedArray.getFloat(R.styleable.PercentLayout_layout_percentMarginLeft, 0));
                info.setPercentMarginTop(typedArray.getFloat(R.styleable.PercentLayout_layout_percentMarginTop, 0));
                info.setPercentMarginRight(typedArray.getFloat(R.styleable.PercentLayout_layout_percentMarginRight, 0));
                info.setPercentMarginBottom(typedArray.getFloat(R.styleable.PercentLayout_layout_percentMarginBottom, 0));
    
                typedArray.recycle();
            }
    
        }
    
    

    自己的LayoutParams类 , 是继承自RelativeLayout的 , 因为我们只是做扩展 , 所有保留原有的RelativeLayout的属性。接下来我们将PercentLayoutParams对象 ,作为返回值返给我们的generateLayoutParams方法 , 这样我们就可以在测量的时候 , 进行LayoutParams对象的判断了。

        
        /*
        * 生成布局参数对象
        * */
        @Override
        public LayoutParams generateLayoutParams(AttributeSet attrs) {
    
            /* 将返回的布局参数对象设置成自定义的参数对象 */
            return new PercentLayoutParams(getContext(), attrs);
        }
    
    

    测量的时候 , 我们对LayoutParams对象进行判断 , 然后将我们自定义的属性 , 设置到子控件相应的属性 :

    
        /* 测量View的宽高 */
        @Override
        protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
            /* 测量RelativeLayout的宽高 */
            int viewGroupWidth = MeasureSpec.getSize(widthMeasureSpec);
            int viewGroupHeight = MeasureSpec.getSize(heightMeasureSpec);
    
            /* 得到RelativeLayout中的子控件个数 */
            int childCount = this.getChildCount();
    
            /* 循环取得子控件 */
            for (int i = 0; i < childCount; i++) {
    
                View child = this.getChildAt(i);
                /* 得到子控件的布局参数 */
                ViewGroup.LayoutParams layoutParams = child.getLayoutParams();
    
                /* 判断LayoutParams对象是不是自定义的Params对象 */
                if (layoutParams instanceof PercentRelativeLayout.PercentLayoutParams) {
    
                    /* 得到布局参数信息对象 */
                    PercentLayoutInfo info = ((PercentLayoutParams) layoutParams).info;
                    if (info != null) {
                        /* 得到自定义的宽高比 */
                        float percentWidth = info.getPercentWidth();
                        float percentHeight = info.getPercentHeight();
    
                        /* 如果宽高比大于0 ,则与父容器宽高进行计算 , 并将结果赋值给子控件 */
                        if (percentHeight > 0) {
                            layoutParams.height = (int) (percentHeight * viewGroupHeight);
                        }
    
                        if (percentWidth > 0) {
                            layoutParams.width = (int) (percentWidth * viewGroupWidth);
                        }
    
                        /* 设置外边距百分比 */
                        setLayoutPercentMargin(layoutParams, info, viewGroupWidth, viewGroupHeight);
    
                    }
    
                }
            }
    
            super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        }
    
    

    设置外边距百分比:

    
        /**
         * 设置子控件外边距百分比
         * @param layoutParams 子控件的布局参数
         * @param info  自定义属性对象
         * @param parentWidth 父容器的宽度
         * @param parentHeight 父容器的高度
         */
        private void setLayoutPercentMargin(ViewGroup.LayoutParams layoutParams, PercentLayoutInfo info, int parentWidth, int parentHeight) {
            /* 得到自定义属性的值 */
            float percentMarginOfWidth = info.getPercentMarginOfWidth();
            float percentMarginOfHeight = info.getPercentMarginOfHeight();
            float percentMarginLeft = info.getPercentMarginLeft();
            float percentMarginTop = info.getPercentMarginTop();
            float percentMarginRight = info.getPercentMarginRight();
            float percentMarginBottom = info.getPercentMarginBottom();
    
            /* 判断子控件是否设置了外边距的参数 */
            if (layoutParams instanceof MarginLayoutParams) {
    
                if (percentMarginOfWidth > 0) {
                    setLayoutMarginParams(layoutParams, percentMarginOfWidth, parentWidth);
                }
    
                if (percentMarginOfHeight > 0 ) {
                    setLayoutMarginParams(layoutParams, percentMarginOfHeight, parentHeight);
                }
    
                if (percentMarginLeft > 0) {
                    ((MarginLayoutParams) layoutParams).leftMargin = (int) (percentMarginLeft * parentWidth);
                }
    
                if (percentMarginTop > 0) {
                    ((MarginLayoutParams) layoutParams).topMargin = (int) (percentMarginTop * parentHeight);
                }
    
                if (percentMarginRight > 0) {
                    ((MarginLayoutParams) layoutParams).rightMargin = (int) (percentMarginRight * parentWidth);
                }
    
                if (percentMarginBottom > 0) {
                    ((MarginLayoutParams) layoutParams).bottomMargin = (int) (percentMarginBottom * parentHeight);
                }
            }
    
        }
    
        /**
         * 设置子控件的外边距 , 根据父容器的某个宽度和高度的百分比
         * @param layoutParams 子控件的布局参数对象
         * @param percent 自定义属性百分比
         * @param parent 父容器的宽度或高度
         */
        private void setLayoutMarginParams(ViewGroup.LayoutParams layoutParams, float percent, int parent) {
            ((MarginLayoutParams) layoutParams).leftMargin = (int) (percent * parent);
            ((MarginLayoutParams) layoutParams).topMargin = (int) (percent * parent);
            ((MarginLayoutParams) layoutParams).rightMargin = (int) (percent * parent);
            ((MarginLayoutParams) layoutParams).bottomMargin = (int) (percent * parent);
        }
    
    

    控件属性XML:

    
        <declare-styleable name="PercentLayout">
            <!-- 宽高比 -->
            <attr name="layout_percentWidth" format="float"/>
            <attr name="layout_percentHeight" format="float"></attr>
    
            <!-- 外边距百分比 -->
    
            <!-- 按照父容器的宽度来设置子控件的四个外边距的百分比 -->
            <attr name="layout_percentMarginOfWidth" format="float"></attr>
            <!-- 按照父容器的高度来设置子控件的四个外边距的百分比 -->
            <attr name="layout_percentMarginOfHeight" format="float"></attr>
    
            <!-- 四个外边距的百分比 -->
            <attr name="layout_percentMarginLeft" format="float"></attr>
            <attr name="layout_percentMarginRight" format="float"></attr>
            <attr name="layout_percentMarginTop" format="float"></attr>
            <attr name="layout_percentMarginBottom" format="float"></attr>
    
        </declare-styleable>
    
    

    自定义RelativeLayout就到这里 , 其他的百分比布局 , 原理都是类似的 , 这里就不赘述了 , 如果写多个百分比布局 , 可以将那些属性处理 , 属性设置 , 抽取一个帮助类出来 。

    相关文章

      网友评论

      本文标题:自定义百分比布局RelatvieLayout

      本文链接:https://www.haomeiwen.com/subject/bxcqsttx.html