自定义百分比布局RelatvieLayout

自定义百分比布局RelatvieLayout_第1张图片
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:


    
        
        
        

        

        
        
        
        

        
        
        
        
        

    

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

你可能感兴趣的:(自定义百分比布局RelatvieLayout)