美文网首页
AutoLayout学习

AutoLayout学习

作者: BooQin | 来源:发表于2018-07-21 20:29 被阅读181次

    AutoLayout的由来

    传统的Layout中的宽高属性只支持macth,wrap,以及常量大小,在linearlayout中还支持weight来确定大小,不过在android阵营中,存在着各式各样的尺寸和分辨率的机型,这就导致了一套layout布局在不同机型上出现不同效果的情况。因此,谷歌官方提供看一套百分比支持库android-percent-support-lib-sample。开发者可以使用该layout作为父容器实现view的百分比设置。但是,在实际的工作中,我们的视觉设计师们喜欢给我们如下的设计图,以iphone为模板的设计图,以px为单位,如果要使用百分比进行适配,又要进行一轮计算。先上一张常见的视觉设计图:

    test.png

    为了更好的配合我们的设计师工作,鸿洋大牛根据这套百分比支持库设计了一个自适配布局库AntoLayout。以下将从使用以及原理两方面来理解AutoLayout。

    相关知识

    AutoLayout的实现过程是通过在onMeasure中设置LayoutParams实现的,所以需要掌握View的measure的过程,以及LayoutParams在measure过程中扮演的角色,由于这方面设计到View的绘制原理,这里不展开讲,下次写View原理的时候再整理。

    Autolayout的原理

    AutoXXX通过继承android传统的layout,重写以下两个方法:

    • generateLayoutParams(AttributeSet attrs)
    • onMeasure(int widthMeasureSpec, int heightMeasureSpec)
        public class AutoRelativeLayout extends RelativeLayout
        {
            private final AutoLayoutHelper mHelper = new AutoLayoutHelper(this);
    
            public AutoRelativeLayout(Context context)
            {
                super(context);
            }
            ……
    
            @Override
            public LayoutParams generateLayoutParams(AttributeSet attrs)
            {
                return new LayoutParams(getContext(), attrs);
            }
    
            @Override
            protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec)
            {
                if (!isInEditMode())
                    mHelper.adjustChildren();
                super.onMeasure(widthMeasureSpec, heightMeasureSpec);
            }
            ……
        }
    

    重写generateLayoutParams

    该方法中主要是生成了该layout下的layoutparams,应用于该布局下的所有子View,对于容器LayoutParams与子View和其它容器之间的关系如下图:

    LayoutParams传递

    关于generateLayoutParams方法的官方描述如下,即返回一个包含新的attr属性组的layoutparams:

    Returns a new set of layout parameters based on the supplied attributes set.

    方法很简单,返回了一个自定义的layoutparams,此时并不做其他处理:

        @Override
        public LayoutParams generateLayoutParams(AttributeSet attrs){    //获取自定义的布局参数,替换默认LayoutParams    
            return new LayoutParams(getContext(), attrs);
        }
    

    LayoutParams类中持有一个AutoLayoutInfo成员变量,该对象保存着从attr中读取到的一些自定义参数,如layout_auto_basewidth,layout_auto_baseheight以及一些需要处理的参数layout_height,layout_margin等,代码如下:

    
        public static class LayoutParams extends RelativeLayout.LayoutParams
                implements AutoLayoutHelper.AutoLayoutParams
        {
            private AutoLayoutInfo mAutoLayoutInfo;
    
            public LayoutParams(Context c, AttributeSet attrs)
            {
                super(c, attrs);
                //获取attrs参数
                mAutoLayoutInfo = AutoLayoutHelper.getAutoLayoutInfo(c, attrs);
            }
            ……
        }
    
    

    AutoLayoutHelper主要负责AutoLayoutInfo相关的一些操作。这里分析下AutoLayoutInfo,该类持有一个AutoAttr列表,AutoAttr为一个抽象类,其子类对应着各个需要自适配的属性,如WidthAttrs等,其继承关系如下图:

    AutoAttr子类

    而AutoLayoutInfo会将所有需要自适配的属性通过addAttr方法添加到列表中以及通过fillAttrs的方法进行更新LayoutParams,其代码主要如下:

        public class AutoLayoutInfo
        {
            //属性数据保存在一个列表中,autoAttrs为一个抽象类,其子类对应着各个需要自适配的属性,如WidthAttrs等,在apply方法中会将数据赋值到layoutparams中。
            private List<AutoAttr> autoAttrs = new ArrayList<>();
    
            public void addAttr(AutoAttr autoAttr)
            {
                autoAttrs.add(autoAttr);
            }
    
            //更新view的layoutparams
            public void fillAttrs(View view)
            {
                for (AutoAttr autoAttr : autoAttrs)
                {
                    autoAttr.apply(view);
                }
            }
            ……
        }
    

    fillAttrs方法中会遍历所有的AutoAttr并执行apply方法,该方法会调用一个由具体的子类实现的抽象方法execute,以WidthAttr为例,apply方法中会对判断是更新width还是height,然后获取一个px值通过execute方法去更新LayoutParams:

        public void apply(View view)
        {
    
            ……
            int val;
            if (useDefault())
            {
                val = defaultBaseWidth() ? getPercentWidthSize() : getPercentHeightSize();
                if (log)
                {
                    L.e(" useDefault val= " + val);
                }
            } else if (baseWidth())
            {
                val = getPercentWidthSize();
                if (log)
                {
                    L.e(" baseWidth val= " + val);
                }
            } else
            {
                val = getPercentHeightSize();
                if (log)
                {
                    L.e(" baseHeight val= " + val);
                }
            }
    
            if (val > 0)
                val = Math.max(val, 1);//for very thin divider
            execute(view, val);
        }
    

    以宽为例,大致的原理表达式如下:

    y = pxVal*screenWidth/designWidth

    getPercentHeightSize方法最终会调用到AutoUtils.getPercentHeightSizeBigger,方法如下,代码中做了取余判断,得到接近完整比例的值:

    
        public static int getPercentWidthSizeBigger(int val)
        {
            int screenWidth = AutoLayoutConifg.getInstance().getScreenWidth();
            int designWidth = AutoLayoutConifg.getInstance().getDesignWidth();
    
            int res = val * screenWidth;
            if (res % designWidth == 0)
            {
                return res / designWidth;
            } else
            {
                return res / designWidth + 1;
            }
    
        }
    
    

    最后看WidthAttr的execute方法,只是简单的更新LayoutParams中的width参数:

        protected void execute(View view, int val)
        {
            ViewGroup.LayoutParams lp = view.getLayoutParams();
            lp.width = val;
        }
    

    onMeasure(int widthMeasureSpec, int heightMeasureSpec)

    通过该方法可以对测量过程的进行一些数据处理,并且在ViewGroup中会通过该方法对子View进行测量。而在Auto模式下,onMeasure进行了如下预处理:

        protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec)
        {
            if (!isInEditMode())
        //设置子View的LayoutParams
                mHelper.adjustChildren();
            super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        }
    

    主要看一下adjustChildren的代码:

        public void adjustChildren()
        {
            AutoLayoutConifg.getInstance().checkParams();
    
            for (int i = 0, n = mHost.getChildCount(); i < n; i++)
            {
                View view = mHost.getChildAt(i);
                ViewGroup.LayoutParams params = view.getLayoutParams();
    
                if (params instanceof AutoLayoutParams)
                {
                    AutoLayoutInfo info =
                            ((AutoLayoutParams) params).getAutoLayoutInfo();
                    if (info != null)
                    {
                        info.fillAttrs(view);
                    }
                }
            }
    
        }
    

    在该方法很简单,如果检测到LayoutParams为AutoLayout的话,就会获取LayoutParams中保存的AutoLayoutInfo,并通过AutoLayoutInfo对象中的fillAttrs方法来获取根据屏幕分辨率计算后得到的值,并将该结果更新到子View的LayoutParams中,整个过程可以参考上面对AutoLayoutInfo类的分析。所以总结一下AutoLayout的设计思想就是通过改变View的LayoutParams来实现屏幕的自适应。

    AutoLayout的使用

    使用前需要设置视觉设计图的分辨率模板,在manifest里的application添加设计相关参数,同时自定义一个Application,在Application中初始化AutoLayout的配置参数,代码如下:

        public class UseDeviceSizeApplication extends Application
        {
            @Override
            public void onCreate()
            {
                super.onCreate();
                AutoLayoutConifg.getInstance().useDeviceSize().init(this);
            }
        }
    

    配置清单中记得设置该Application和模板参数:

        <manifest xmlns:android="http://schemas.android.com/apk/res/android"
            package="com.example.administrator.autolayoutdemo" >
    
            <application
                android:allowBackup="true"
                android:icon="@mipmap/ic_launcher"
                android:label="@string/app_name"
                android:theme="@style/AppTheme"
                android:name=".UseDeviceSizeApplication" >
                ……
    
                <meta-data
                    android:name="design_width"
                    android:value="1080" />
                <meta-data
                    android:name="design_height"
                    android:value="1920" />
            </application>
    
        </manifest>
    

    接下来就可以在自己的项目中使用了,实现自适配有以下两套方法,前者是作者通过重写Activity中onCreateView方法来动态替换LayoutView来完成的。最后就可以工具设计图来设置对应属性的px值。

    • 继承AutoXXXActivity,布局文件直接使用传统的Layout设计界面.
    • 在布局文件中使用AntoXXXLayout来设计界面。

    需要注意的是,ListView和RecyclerView等属于特殊的ViewGroup,无法直接使用AutoXXX来实现自适配化,可以通过AutoUtils.autoSize方法来更新宽高值,原理同AutoLayout。以List为例,在Adapter创建View时调用一下AutoUtils.autoSize方法:

        @Override
        public View getView(int position, View convertView, ViewGroup parent)
        {
            ViewHolder holder = null;
            if (convertView == null)
            {
                holder = new ViewHolder();
                convertView = LayoutInflater.from(mContext).inflate(R.layout.list_item, parent, false);
                convertView.setTag(holder);
                //对于listview,注意添加这一行,即可在item上使用高度
                AutoUtils.autoSize(convertView);
             } else
            {
                holder = (ViewHolder) convertView.getTag();
            }
    
            return convertView;
        }
    

    ListView继承于AdapterView,和RecyclerView一样需要通过Adapter来创建itemView,所以无法直接使用AutoLayout
      具体完整的使用说明可以参考github上的readme说明文档

    参考文章

    相关文章

      网友评论

          本文标题:AutoLayout学习

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