美文网首页
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学习

    iOS布局方式的演变 使用Rect的frame布局方式 autoresizingMask方式 AutoLayout...

  • AutoLayout学习

    AutoLayout的由来 传统的Layout中的宽高属性只支持macth,wrap,以及常量大小,在linear...

  • IOS autolayout详解

    autolayout简单动画推荐学习参考的博客链接 寒哥细谈之AutoLayout全解 动态计算Tableview...

  • 学习AutoLayout(VFL)

    NSLayoutConstraint的第二个类方法+ (NSArray *)constraintsWithVisu...

  • Autolayout学习笔记

    知识点一: 1、布局思维 传统布局思路中,一个view在哪里有多大,那就写清楚它的坐标位置和宽高就定了,平时用CG...

  • 学习AutoLayout(UIScrollView)

    概述 项目适配到第2个页面就遇到了问题,tableViewCell中添加了一个scrollView,scrollV...

  • 学习AutoLayout(NSLayoutConstraint)

    概述 以前一直听说autoLayout,跟xib、storybord无缝结合使用,设置各种约束条件来达到适配的目的...

  • AutoLayout 学习资料

    土土哥的系列 http://tutuge.me/2015/08/08/autolayout-example-wit...

  • 学习随记 - AutoLayout

    由于iOS运行在不同设备之上,因而作为代码搬运工的我们经常需要对不同的屏幕尺寸进行适配。我们常用的方法有NSLay...

  • 9.4 AutoLayout使用

    AutoLayout使用 AutoLayout使用.png

网友评论

      本文标题:AutoLayout学习

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