AutoLayout学习

2018-07-21  本文已影响181人  BooQin

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,重写以下两个方法:

    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值。

需要注意的是,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说明文档

参考文章

上一篇 下一篇

猜你喜欢

热点阅读