您的位置:首页 > 其它

性能优化之布局优化篇一 使用ViewStub视图

2016-09-02 19:28 411 查看
一、概述

ViewStub是一个不显示且不占用布局空间的视图。ViewStub需要指定一个布局layout,在ViewStub初始化时,其指定的layout并未初始化。只有当ViewStub的setVisibility(VISIBLE/INVISIBLE)或inflate()方法被调用,ViewStub所指向的布局layout才会实例化,该layout会使用ViewStub的布局参数LayoutParam,被添加到ViewStub的父视图中来替代ViewStub,即ViewStub将不在布局中存在。

ViewStub类官方文档:

https://developer.android.com/reference/android/view/ViewStub.html

二、使用场景

在开发中,会遇到这样的情况,需要在运行时动态的决定显示某个视图。通常的做法是,将可能会用到的所有视图都添加到布局中,之后在代码中通过View.VISIBILITY和View.GONE来改变视图的可见性。即使在xml布局中为视图设置了android:visibility="gone",该视图仍然会被实例化,这样做带来的缺点是增加了视图加载的时间和内存的消耗。

ViewStub的一个典型的使用案例,当我们启动一个页面,如果请求到数据就显示内容视图,遇到网络出错时显示网络异常视图。

添加网络异常视图net_error_view.xml。

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
android:orientation="vertical">

<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/net_icon"/>

<Button
android:id="@+id/button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="网络异常\n点击屏幕重新加载"
android:textColor="#666666"
android:textSize="16sp"/>
</LinearLayout>


Activity的布局分两部分,一个内容视图,一个网络异常视图。网络异常视图使用ViewStub,并将layout指定为上面的net_error_view。

<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#ffffff">

<!-- 内容视图 -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
</LinearLayout>

<!-- 网络异常视图 -->
<ViewStub
android:id="@+id/viewstub"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:inflatedId="@+id/inflatedview"
android:layout="@layout/net_error_view"/>
</FrameLayout>


在Activity中,如果需要显示网络异常视图,使用如下代码。

private View netErrorView;

ViewStub viewStub = (ViewStub) findViewById(R.id.net_error);
netErrorView = viewStub.inflate();


viewStub.setVisibility(View.VISIBLE);
netErrorView = findViewById(R.id.inflatedview);


当ViewStub的inflate()方法被调用时,其指定的@layout/net_error_view被实例化并显示出来。此时,ViewStub的android:inflatedId即inflatedview,就成为net_error_view视图根布局的id。这里我们将网络异常视图对象保存到成员变量netErrorView中,之后可以直接操作netErrorView,不用再调用findViewById()。

三、源码分析

ViewStub继承自View类,所以本质上还是一个View。

public final class ViewStub extends View {
}


ViewStub视图在加载时,为何没有显示,且指定的layout没有被初始化,原因在于ViewStub重写了View类的onMeasure()和draw()方法。在onMeasure()方法中,调用setMeasuredDimension,传递的参数measuredWidth和measuredHeight都是0。draw()方法的内部实现被置空,也就不会绘制了。

@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
setMeasuredDimension(0, 0);
}

@Override
public void draw(Canvas canvas) {
}


ViewStub除了拥有父类View的属性外,新增了android:inflatedId和android:layout,这两个属性在构造方法中完成初始化。

private int mInflatedId;
private int mLayoutResource;

public ViewStub(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context);

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);
}
获取到属性inflatedId和layout的值后分别赋值给成员变量mInflatedId和mLayoutResource。并在最后调用了setWillNotDraw(true),只有View就不会绘制自己。

当ViewStub的inflate()方法被调用时,指定的layout被实例化显示出来,并返回。我们来看看inflate()方法的实现。

public View inflate() {
final ViewParent viewParent = getParent();

if (viewParent != null && viewParent instanceof ViewGroup) {
if (mLayoutResource != 0) {
// 获取ViewStub的父视图,强转为ViewGroup类型
final ViewGroup parent = (ViewGroup) viewParent;
// 获取LayoutInflater对象
final LayoutInflater factory;
if (mInflater != null) {
factory = mInflater;
} else {
factory = LayoutInflater.from(mContext);
}
// 实例化mLayoutResource,赋值给view变量
final View view = factory.inflate(mLayoutResource, parent,
false);

// 如果设置了mInflatedId,赋值给view的id
if (mInflatedId != NO_ID) {
view.setId(mInflatedId);
}

// 移除ViewStub自身
final int index = parent.indexOfChild(this);
parent.removeViewInLayout(this);

// getLayoutParams()获取的是ViewStub的布局参数
final ViewGroup.LayoutParams layoutParams = getLayoutParams();
// 把view添加到ViewStub的父视图中
if (layoutParams != null) {
parent.addView(view, index, layoutParams);
} else {
parent.addView(view, index);
}

// 把view赋值给成员变量mInflatedViewRef
mInflatedViewRef = new WeakReference<View>(view);

if (mInflateListener != null) {
mInflateListener.onInflate(this, view);
}

// 最后返回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");
}
}
首先,使用LayoutInflater将指定的layout实例化赋值给变量view。如果设置了inflatedId,赋值给该view的id属性。然后,从ViewStub的父视图parent中移除ViewStub自身,使用ViewStub的布局参数把view添加到parent中。最后,把view赋值给成员变量mInflatedViewRef,返回view。

ViewStub的setVisibility()方法被调用时,指定的layout同样也可以被实例化。ViewStub对View中的setVisibility()方法进行了重写。

@Override
public void setVisibility(int visibility) {
if (mInflatedViewRef != null) {// 如果ViewStub的inflate()已经被调用,执行if
View view = mInflatedViewRef.get();
// 此时ViewStub已经被inflate(),view已经有值,直接执行view的setVisibility()方法
if (view != null) {
view.setVisibility(visibility);
} else {
throw new IllegalStateException("setVisibility called on un-referenced view");
}
} else {// 如果ViewStub的inflate()还未被调用,执行else
// 先调用父类View的setVisibility()
super.setVisibility(visibility);
// 如果传入的参数为VISIBLE或INVISIBLE,执行inflate()方法
if (visibility == VISIBLE || visibility == INVISIBLE) {
inflate();
}
}
}
在setVisibility()方法被调用之前,如果ViewStub的inflate()已经调用,此时成员变量mInflatedViewRef即是ViewStub指定的layout对象,直接执行view的setVisibility()方法。如果ViewStub的inflate()未被调用,先将参数传递到父类View的setVisibility(),然后判断传入的参数为VISIBLE或INVISIBLE时,执行inflate()方法,去实例化layout对象。

所以,当ViewStub的inflate()方法被调用,或setVisibility()方法被调用并传入参数VISIBLE或INVISIBLE,ViewStub所指向的布局layout会被实例化,并添加到ViewStub的父视图中显示出来。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息