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