屏幕适配:百分比布局

作者: space0o0 | 来源:发表于2019-10-12 12:27 被阅读0次

    前提

    上一篇我们分析了自定义像素的屏幕适配,它是以px为单位,设计的标准机型和显示机型的宽高比,动态缩放view。

    现在,我们不用px为单位,而是直接以当前view在父容器中的比例,来缩放view。

    比如,我定义一个view,它的宽为父容器的一半,高也为父容器的一半。

    //xml中直接以float的形式定义子view的宽高
    app:widthPercent="0.5"
    app:heightPercent=“0.75”
    

    准备工作

    既然需要使用自定义属性,首先就需要添加attrs,声明自定义属性。

    //attrs.xml中添加自定义属性
    <declare-styleable name=“PercentLayout”>
        <attr name=“widthPercent” format=“float” />
        <attr name=“heightPercent” format=“float” />
        <attr name=“marginLeftPercent" format="float" />
        <attr name="marginRightPercent" format="float" />
        <attr name=“marginTopPercent” format=“float” />
        <attr name="marginBottomPercent" format="float" />
    </declare-styleable>
    

    我们实现的目的是子view以父容器的宽高为标准,按照百分比规定自身宽高,所以需要自定义个ViewGroup用来包裹子view,顺便计算出子view的宽高。

    以RelativeLayout为例:

    public class PercentLayout extends RelativeLayout {
        public PercentLayout(Context context) {
            this(context, null);
        }
        public PercentLayout(Context context, AttributeSet attrs) {
            this(context, attrs, 0);
        }
        public PercentLayout(Context context, AttributeSet attrs, int defStyleAttr) {
            super(context, attrs, defStyleAttr);
        }
    }
    

    继承了RelativeLayout后,我们不能丢失RelativeLayout原来的属性,还要增加我们自定义的属性。

    源码的简单分析

    原始RelativeLayout的属性被定义在RelativeLayout的内部静态类中(LayoutParams)。
    该类中定义的如:above、below、center等属性,都是声明在子view中。同时百分比属性也是需要定义在子view中。
    所以,我们的自定义百分比布局也需要继承该内部类,再添加自定义属性。

    private static class CustomLayoutParam extends RelativeLayout.LayoutParams {
        //声明自定义属性
        private float widthPercent;
        private float heightPercent;
        private float marginTopPercent;
        private float marginBottomPercent;
        private float marginLeftPercent;
        private float marginRightPercent;
    
        public CustomLayoutParam(Context context, AttributeSet attrs) {
            super(context, attrs);
    
            TypedArray typedArray = context.obtainStyledAttributes(attrs,R.styleable.PercentLayout);
    
            widthPercent = typedArray.getFloat(R.styleable.PercentLayout_widthPercent, 0f);
            heightPercent = typedArray.getFloat(R.styleable.PercentLayout_heightPercent, 0f);
            marginTopPercent = typedArray.getFloat(R.styleable.PercentLayout_marginTopPercent, 0f);
            marginBottomPercent = typedArray.getFloat(R.styleable.PercentLayout_marginBottomPercent, 0f);
            marginLeftPercent = typedArray.getFloat(R.styleable.PercentLayout_marginLeftPercent, 0f);
            marginRightPercent = typedArray.getFloat(R.styleable.PercentLayout_marginRightPercent, 0f);
    
            typedArray.recycle();
        }
    }
    

    再分析下view的创建与LayoutParams的关系。

    onCreateView()中始终需要调用setContentView(layoutID)。

    最终在PhoneWindow的setContentView(int layoutResID)方法中,调用方法解析layoutResID

    方法一路延伸最终来到LayoutInflater类的inflate()方法。

    //inflate方法中
    。。。
    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);
        }
        // Create layout params that match root, if supplied
          //由父容器创建子view的layoutParams
        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);
        }
    }
    。。。
    

    关键点:源码中,子view的LayoutParams是由父容器的generateLayoutParams()创建

    //viewGroup中
    public LayoutParams generateLayoutParams(AttributeSet attrs) {
        return new LayoutParams(getContext(), attrs);
    }
    

    那我们的自定义LayoutParams如何被创建呢?

    在父容器中重写generateLayoutParams方法,返回我们子view的LayoutParams。

    //重写方法,返回自定义的LayoutParams
    @Override
    public LayoutParams generateLayoutParams(AttributeSet attrs) {
        return new CustomLayoutParam(getContext(), attrs);
    }
    

    计算

    最终在onMeasure方法中,根据百分比计算出子view的宽高。

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        //获取父容器的宽高
        int widthSize = MeasureSpec.getSize(widthMeasureSpec);
        int heightSize = MeasureSpec.getSize(heightMeasureSpec);
    
        int childCount = getChildCount();
    
        for (int i = 0; i < childCount; i++) {
    
            View child = getChildAt(i);
            ViewGroup.LayoutParams layoutParams = child.getLayoutParams();
    
            if (layoutParams instanceof CustomLayoutParam) {
    
                CustomLayoutParam lp = (CustomLayoutParam) layoutParams;
    
                float widthPercent = lp.widthPercent;
                float heightPercent = lp.heightPercent;
                float marginTopPercent = lp.marginTopPercent;
                float marginBottomPercent = lp.marginBottomPercent;
                float marginLeftPercent = lp.marginLeftPercent;
                float marginRightPercent = lp.marginRightPercent;
    
                if (widthPercent > 0) {
                    lp.width = (int) (widthSize * widthPercent);
                }
    
                if (heightPercent > 0) {
                    lp.height = (int) (heightSize * heightPercent);
                }
    
                if (marginTopPercent > 0) {
                    lp.topMargin = (int) (heightSize * marginTopPercent);
                }
    
                if (marginBottomPercent > 0) {
                    lp.bottomMargin = (int) (heightSize * marginBottomPercent);
                }
    
                if (marginLeftPercent > 0){
                    lp.leftMargin= (int) (widthSize* marginLeftPercent);
                }
    
                if (marginRightPercent > 0){
                    lp.rightMargin = (int) (widthSize * marginRightPercent);
                }
            }
        }
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    }
    

    最终效果

    1.png

    总结

    整体思路

    1、自定义属性。
    2、自定义LayoutParams,继承自RelativeLayout.LayoutParams。
    3、创建自定义LayoutParams,父容器用generateLayoutParams()方法创建子view的layoutParams。
    4、在onMeasure中获取子view的LayoutParams,按比例计算出宽高并赋值。

    注意:子view的LayoutParams是由父容器在generateLayoutParams()中创建。

    相关文章

      网友评论

        本文标题:屏幕适配:百分比布局

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