布局优化是我们开发过程中最常见和最容易改善的地方,所以我们先从它开始入手。
开始详细分析之前,我们先说一下对布局优化的认识。
布局优化的目的是为了尽量减少布局层级,常用的方法包括:推荐使用相对布局,约束布局等构建布局文件,ViewStub, Merge, include。
减少布局层级的目的是什么呢?是为了减少View树的深度,相应的也就减少了测量和绘制的工作量。
如果布局中既可以使用LinearLayout也可以使用RelativeLayout,那么就采用LinearLayout,因为RelativeLayout功能复杂,布局需要花费更多CPU。如果功能需要嵌套才能完成,还是建议使用RelativeLayout。
<include>
开发过程中,我们可以提取布局文件中可复用的部分独立成一个xml文件,使用include进行复用。
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/app_bg"
android:gravity="center_horizontal">
<include layout="@layout/titlebar"/>
<TextView android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/hello"
android:padding="10dp" />
...
</LinearLayout>
include支持的属性不多,如果要声明其他属性,需要同时指定layout_width和layout_height,如果include标签不指定属性,则默认采用要include 布局的root view的属性。
如果include标签指定属性,则include的属性会覆盖 root view的属性。
比如:root view是gone的,而include指定为visible,则布局是visible。
具体原理可参考源码LayoutInflater#parseInclude
<merge>
merge标签在被引入布局中时会被忽略,也就是说相比于直接include普通布局文件,会少一层。
但是merge标签中的子控件,会被直接放在布局中,用来替换include。
<?xml version="1.0" encoding="utf-8"?>
<merge xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
xmlns:app="http://schemas.android.com/apk/res-auto"
tools:parentTag="androidx.constraintlayout.widget.ConstraintLayout">
<Button
android:layout_height="wrap_content"
android:layout_width="wrap_content"
android:text="btn"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"/>
</merge>
其中tools:parentTag可以让你在预览时查看它在父布局中显示样式。
ViewStub
ViewStub是一种延时加载的技术,只有在真正需要的时候才去加载布局文件。对于复杂且很少使用的布局来说,此种方式很有效,比如网络异常布局。
<ViewStub
android:id="@+id/stub_import"
android:inflatedId="@+id/panel_import"
android:layout="@layout/progress_overlay"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_gravity="bottom" />
通过inflate或者setVisibility可以加载ViewStub声明的布局。
findViewById(R.id.stub_import).setVisibility(View.VISIBLE);
// or
View importPanel = ((ViewStub) findViewById(R.id.stub_import)).inflate();
一旦setVisibility或inflate调用,ViewStub会被inflated布局替换,并且inflatedId就是inflated布局文件root view的ID,它会覆盖布局文件中定义的root view ID。
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
setMeasuredDimension(0, 0);
}
@Override
public void draw(Canvas canvas) {
}
从源码中看ViewStub的宽高为0,且不参与绘制过程。
public View inflate() {
final ViewParent viewParent = getParent();
if (viewParent != null && viewParent instanceof ViewGroup) {
if (mLayoutResource != 0) {
final ViewGroup parent = (ViewGroup) viewParent;
final View view = inflateViewNoAdd(parent);
replaceSelfWithView(view, parent);
mInflatedViewRef = new WeakReference<>(view);
if (mInflateListener != null) {
mInflateListener.onInflate(this, view);
}
return view;
} else {
throw new IllegalArgumentException("ViewStub must have a valid layoutResource");
}
} else {
throw new IllegalStateException("ViewStub must have a non-null ViewGroup viewParent");
}
}
ViewStub必须在ViewGroup中使用。
ViewStub的inflate方法只能被触发一次,否则会抛异常。
因为ViewStub#inflate从ViewGroup中移除了,导致第二次inflate时ViewGroup为空。
private View inflateViewNoAdd(ViewGroup parent) {
final LayoutInflater factory;
if (mInflater != null) {
factory = mInflater;
} else {
factory = LayoutInflater.from(mContext);
}
final View view = factory.inflate(mLayoutResource, parent, false);
if (mInflatedId != NO_ID) {
view.setId(mInflatedId);
}
return view;
}
可以看到,使用parent inflate布局之后会重新set id为inflatedId。
private void replaceSelfWithView(View view, ViewGroup parent) {
final int index = parent.indexOfChild(this);
parent.removeViewInLayout(this);
final ViewGroup.LayoutParams layoutParams = getLayoutParams();
if (layoutParams != null) {
parent.addView(view, index, layoutParams);
} else {
parent.addView(view, index);
}
}
使用 inflated view 替换ViewStub在parent view中的位置。
public void setVisibility(int visibility) {
if (mInflatedViewRef != null) {
View view = mInflatedViewRef.get();
if (view != null) {
view.setVisibility(visibility);
} else {
throw new IllegalStateException("setVisibility called on un-referenced view");
}
} else {
super.setVisibility(visibility);
if (visibility == VISIBLE || visibility == INVISIBLE) {
inflate();
}
}
}
setVisibility如果设置为VISIBLE或INVISIBLE实际也是调用的inflate方法。
在DataBinding中使用
ViewStub viewStub = binding.viewStub.getViewStub();
viewStub.inflate();
网友评论