1.前言
对于Android的单线程模型来说,把控好主线程中的操作时是至关重要的。布局复杂了,很容易引起卡顿,或者某些情况下界面展示慢的情况,我们都知道ViewStub能实现懒加载,减少布局渲染时间,那么它究竟用了什么方法来实现的,这里做一个学习记录,加深印象。
2.如何优化布局
ViewStub是不可见,宽高为0的View,当调用inflate()或者setVisibility(int visibility)后进行加载。这样就可以在需要的时候加载,不用再布局绘制的时候就一次性就全部加载渲染出来,可以缓存主线程的压力。
3.如何使用
//1.在布局中将原本需要懒加载的layout用下面这种方式替换,layout属性指定的是将要加载的布局,inflatedId是layout指定的布局被加载替换ViewStub后指定的id。
<ViewStub android:id="@+id/stub"
android:inflatedId="@+id/subTree"
android:layout="@layout/mySubTree"
android:layout_width="120dip"
android:layout_height="40dip" />
//2.在需要加载的时机调用
ViewStub stub = findViewById(R.id.stub);
View inflated = stub.inflate();
如上面的代码,调用stub.inflate()后layout指向的布局将被加载到ViewStub所在的位置,也就是ViewStub的父布局中。
4.源码分析
public final class ViewStub extends View {
private int mInflatedId;
private int mLayoutResource;
private WeakReference<View> mInflatedViewRef;
private LayoutInflater mInflater;
private OnInflateListener mInflateListener;
...
public ViewStub(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context);
//(1)
final TypedArray a = context.obtainStyledAttributes(attrs,
R.styleable.ViewStub, defStyleAttr, defStyleRes);
mInflatedId = a.getResourceId(R.styleable.ViewStub_inflatedId, NO_ID);
mLayoutResource = a.getResourceId(R.styleable.ViewStub_layout, 0);
mID = a.getResourceId(R.styleable.ViewStub_id, NO_ID);
a.recycle();
setVisibility(GONE);
setWillNotDraw(true);
}
...
//(2)
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
setMeasuredDimension(0, 0);
}
//(3)
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");
}
}
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;
}
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);
}
}
//(4)
public void setOnInflateListener(OnInflateListener inflateListener) {
mInflateListener = inflateListener;
}
}
(1)从frameworks\base\core\res\res\values\attrs.xml文件中可以看出,这里是在解析布局中的inflatedId和layout属性值。
<declare-styleable name="ViewStub">
<attr name="id" />
<attr name="layout" format="reference" />
<attr name="inflatedId" format="reference" />
</declare-styleable>
(2)测量时宽高都设置为0了,所以不可见宽高为0(3)inflate是ViewStub实现延迟加载的关键,调用时会先获取父布局,然后用LayoutInflator加载布局,并没有什么特殊操作,加载完了会用布局中配置的inflateId作为指定layout的id。replaceSelfWithView()方法也是简单粗暴,直接从父布局中将ViewStub移除掉,然后直接addView()方式加上来,因为addView()中会再调用requestLayout(),这样就能addView()后布局被更新。(4)如果要监听ViewStub被加载完成的回调,可以setOnInflateListener():
viewStub.setOnInflateListener(new ViewStub.OnInflateListener() {
@Override
public void onInflate(ViewStub stub, View inflated) {
}
});
5.需要注意的问题
1.如果定义了ViewStub的inflateId属性,那么在inflate()后layout属性指定的layout的id会被动态的替换成inflateId的值;2.ViewStub的layout布局中不允许使用merge标签,具体原因待后面分析