美文网首页
屏幕适配?不如手写个百分比布局

屏幕适配?不如手写个百分比布局

作者: 有没有口罩给我一个 | 来源:发表于2019-12-04 21:25 被阅读0次
    百分比布局方式的适配?

    屏幕适配无非就是适配各种机型上尺寸,尽可能让UI元素表现一致,这句话是没问题的,无非就是height、widhth和marginxx的尺寸,难道还有其他的吗?而百分比布局的适配就是,以父容器的尺寸作为参考,在View的加载过程中,根据父容器的实际尺寸换算出目标尺寸,然后作用于View,这句可能比较抽象,所以我们在手写百分比布局之前让我们来看看View的加载原理如何?就从setContentView开始吧。

    public void setContentView(int layoutResID) {
    
        if (mContentParent == null) {
            //创建DecorView和ContentView,并将ContentView添加到DecorView上,记住mContentParent就是DecorVIew的内容布局,就是开发者setContentView传进来的布局的父容器
            installDecor();
        } else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
            mContentParent.removeAllViews();
        }
    
           //将要加载的布局资源添加到mContentParent上
            mLayoutInflater.inflate(layoutResID, mContentParent);
    }
    

    为了简介所以省了很多代码,View的加载时从Activity的setContentView开始,然后在setContentView中调用mLayoutInflater.inflate(layoutResID, mContentParent);填充ContentView,所以我们一直跟踪最终到inflate的重载方法,代码如下。

    public View inflate(XmlPullParser parser, @Nullable ViewGroup root, boolean attachToRoot) {
        synchronized (mConstructorArgs) {
          
                      View result = root;
    
                    // temp就是xml中定义的根布局,而root就是DecorView的内容布局
                    final View temp = createViewFromTag(root, name, inflaterContext, attrs);
    
                    ViewGroup.LayoutParams params = null;
    
                    if (root != null) {
                        // 创建与根布局匹配的布局参数(如果提供),contentView生成布局参数,并设置给child
                        params = root.generateLayoutParams(attrs);
                        if (!attachToRoot) {                   
                          // 使用root提供的布局参数设置 child
                            temp.setLayoutParams(params);
                        }
                    }
                    // 这里的逻辑和上面的一样,上面的处理DecrView的contentView生成的LayoutParam做了处理,而这里则是开发者自己在xml中定义根View,逻辑也是根据根View生成LayoutParam提供根child。
                    rInflateChildren(parser, temp, attrs, true);
    }
    

    1、在inflate方法中主要是处理DecorView的contentView提供的LayoutParam,并将contentView生成的LayoutParam提供根child。
    2、在rInflateChildren方法的逻辑和inflate方法的逻辑一样的,不过rInflateChildren方法是处理的开发者在xml中定义的布局,而且这个方法是个递归方法。
    3、generateLayoutParams方法值ViewGroup的方法,用于生成LayoutParam的。

    rInflateChildren方法:

    final void rInflateChildren(XmlPullParser parser, View parent, AttributeSet attrs,
                                boolean finishInflate) throws XmlPullParserException, IOException {
        rInflate(parser, parent, parent.getContext(), attrs, finishInflate);
    }  
    

    rInflate方法

    void rInflate(XmlPullParser parser, View parent, Context context,
                  AttributeSet attrs, boolean finishInflate) throws XmlPullParserException, IOException {
    
               final View view = createViewFromTag(parent, name, context, attrs);
                final ViewGroup viewGroup = (ViewGroup) parent;
               // 生成LayoutParam
                final ViewGroup.LayoutParams params = viewGroup.generateLayoutParams(attrs);
              // 递归调用
                rInflateChildren(parser, view, attrs, true);
                viewGroup.addView(view, params);
    }
    

    最后结论就是子View的LayoutParam是有父View提供的,所以我们知道了这个原理就好办了,我们可以通过自定义LayoutParam,并在父容器中修改LayoutParam就可以满足我们今天要讲内容了。

    手写百分比布局
    public class PercentLayout extends RelativeLayout {
    
    public PercentLayout(Context context) {
        super(context);
    }
    
    public PercentLayout(Context context, AttributeSet attrs) {
        super(context, attrs);
    }
    
    public PercentLayout(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }
    
    
    @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 params = child.getLayoutParams();
    
            //如果是百分比布局属性
            if (checkLayoutParams(params)) {
                LayoutParam lp = (LayoutParam) params;
                float mLayoutHeightPercent = lp.mLayoutHeightPercent;
                float mLayoutWidthPercent = lp.mLayoutWidthPercent;
                float mLayoutMarginLeftPercent = lp.mLayoutMarginLeftPercent;
                float mLayoutMarginRightPercent = lp.mLayoutMarginRightPercent;
                float mLayoutMarginTopPercent = lp.mLayoutMarginTopPercent;
                float mLayoutMarginBottomPercent = lp.mLayoutMarginBottomPercent;
    
                if (mLayoutHeightPercent > 0) {
                    params.height = (int) (heightSize * mLayoutHeightPercent);
                }
    
                if (mLayoutWidthPercent > 0) {
                    params.width = (int) (widthSize * mLayoutWidthPercent);
                }
    
                if (mLayoutMarginLeftPercent > 0) {
                    ((LayoutParam) params).leftMargin = (int) (widthSize * mLayoutMarginLeftPercent);
                }
    
                if (mLayoutMarginRightPercent > 0) {
                    ((LayoutParam) params).rightMargin = (int) (widthSize * mLayoutMarginRightPercent);
                }
    
                if (mLayoutMarginTopPercent > 0) {
                    ((LayoutParam) params).topMargin = (int) (heightSize * mLayoutMarginTopPercent);
                }
    
                if (mLayoutMarginTopPercent > 0) {
                    ((LayoutParam) params).bottomMargin = (int) (heightSize * mLayoutMarginBottomPercent);
                }
            }
        }
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    }
    
    @Override
    public LayoutParams generateLayoutParams(AttributeSet attrs) {
        return new LayoutParam(getContext(), attrs);
    }
    
    @Override
    protected boolean checkLayoutParams(ViewGroup.LayoutParams p) {
        return p instanceof LayoutParam;
    }
    
    // 通过查看setContentView的源码,得知,child的布局属性都是根据父容器创建的。
    //为了保证还能够使用RelativeLayout的布局属性,所以需继承RelativeLayout.LayoutParams
    public static class LayoutParam extends RelativeLayout.LayoutParams {
    
        private float mLayoutHeightPercent;
        private float mLayoutWidthPercent;
        private float mLayoutMarginLeftPercent;
        private float mLayoutMarginRightPercent;
        private float mLayoutMarginTopPercent;
        private float mLayoutMarginBottomPercent;
    
        @SuppressLint("CustomViewStyleable")
        public LayoutParam(Context c, AttributeSet attrs) {
            super(c, attrs);
            TypedArray typedArray = c.obtainStyledAttributes(attrs, R.styleable.MyPercentLayout);
            mLayoutHeightPercent = typedArray.getFloat(R.styleable.MyPercentLayout_layout_heightPercent2, 0f);
            mLayoutWidthPercent = typedArray.getFloat(R.styleable.MyPercentLayout_layout_widthPercent2, 0f);
            mLayoutMarginLeftPercent = typedArray.getFloat(R.styleable.MyPercentLayout_layout_marginLeftPercent2, 0f);
            mLayoutMarginRightPercent = typedArray.getFloat(R.styleable.MyPercentLayout_layout_marginRightPercent2, 0f);
            mLayoutMarginTopPercent = typedArray.getFloat(R.styleable.MyPercentLayout_layout_marginTopPercent2, 0f);
            mLayoutMarginBottomPercent = typedArray.getFloat(R.styleable.MyPercentLayout_layout_marginBottomPercent2, 0f);
            typedArray.recycle();
        }
    }
     }
    

    代码很简单,我们通过自定义LayoutParam并继承了RelativeLayout.LayoutParams,主要是想保留RelativeLayout.LayoutParams的布局属性,就我们就是做了个拓展,然后我们还重写了generateLayoutParams方法,并在此方法中返回我们的自定义的LayoutParam,并在onMeasure中对所有child进行遍历修改child的LayoutParam,得以满足做到百分比适配方法,虽说百分比适配方法是已经过时了,它的缺点在于不能精确的做到是适配,比如我想这个Button距离左边20dp,那百分比就做不到,百分比就是通过父容器的width和height然后剩余指定的百分比,并不能作为任何场景的适配。

    相关文章

      网友评论

          本文标题:屏幕适配?不如手写个百分比布局

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