Android布局优化(2)——ViewStub使用和源码梳理
问题背景
本文主要讨论安卓开发layout布局文件时的部分优化手段,上一篇文章中中初步介绍了include和merge两种方式,具体可参考 https://blog.51cto.com/baorant24/6057402 ,本文主要是介绍安卓布局优化的第三种手段,ViewStub的使用和梳理。
问题分析
很多同学在之前工作和学习过程中或多或少都使用过ViewStub,大都知道ViewStub是一个轻量级的视图控件,而实际开发中在合适的场景中使用,可以提高渲染速度,占用的内存更少,从而提高App的UI性能。话不多少,先梳理一波ViewStub的源码,对整体原理有个初步的了解先。 (1)ViewStub是一个继承了View类的视图
@RemoteView
public final class ViewStub extends View {
private int mInflatedId;
private int mLayoutResource;
(2)ViewStub是不可见的,实际上是把宽高都设置为0
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
// 长宽都设置为0
setMeasuredDimension(0, 0);
}
@Override
public void draw(Canvas canvas) {
// 不绘制
}
@Override
protected void dispatchDraw(Canvas canvas) {
// 不分发
}
(3)可以通过布局文件的android:inflatedId或者调用ViewStub的setInflatedId方法为懒加载视图的跟节点设置ID
public ViewStub(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context);
final TypedArray a = context.obtainStyledAttributes(attrs,
R.styleable.ViewStub, defStyleAttr, defStyleRes);
saveAttributeDataForStyleable(context, R.styleable.ViewStub, attrs, a, defStyleAttr,
defStyleRes);
// 通过自属性inflatedId来获取加载的视图根节点ID,默认返回NO_ID,代表没有赋值
mInflatedId = a.getResourceId(R.styleable.ViewStub_inflatedId, NO_ID);
// 需要加载的视图资源ID
mLayoutResource = a.getResourceId(R.styleable.ViewStub_layout, 0);
mID = a.getResourceId(R.styleable.ViewStub_id, NO_ID);
a.recycle();
setVisibility(GONE);
setWillNotDraw(true);
}
(4)inflate方法
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);
// 从父节点中移除ViewStub,参考后面replaceSelfWithView方法代码
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");
}
}
android.view.ViewStub#replaceSelfWithView
private void replaceSelfWithView(View view, ViewGroup parent) {
final int index = parent.indexOfChild(this);
// 从父节点中移除View
parent.removeViewInLayout(this);
final ViewGroup.LayoutParams layoutParams = getLayoutParams();
if (layoutParams != null) {
parent.addView(view, index, layoutParams);
} else {
parent.addView(view, index);
}
}
(5)setVisibility()方法
@Override
@android.view.RemotableViewMethod(asyncImpl = "setVisibilityAsync")
public void setVisibility(int visibility) {
// 如果对待加载视图的软引用不为空,说明已经执行过inflate方法了
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();
}
}
}
问题解决
上面介绍了ViewStub的基本源码和原理,下面结合demo,分析一般的使用。 页面activity代码如下:
import android.os.Bundle
import android.view.ViewStub
import androidx.appcompat.app.AppCompatActivity
import kotlinx.android.synthetic.main.activity_view_stub.*
class ViewStubActivity2 : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_view_stub)
dianji.setOnClickListener {
(viewstub as ViewStub).inflate()
}
}
}
页面布局文件如下,主要包括一个按钮和一个viewstub组件:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".ViewStubActivity">
<Button
android:id="@+id/dianji"
android:text="点击显示图片 "
android:layout_gravity="center_horizontal"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
<ViewStub
android:id="@+id/viewstub"
android:layout_marginTop="20dp"
android:layout="@layout/view_stub"
android:inflatedId="@+id/inflatedid_viewstub"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
</LinearLayout>
ViewStub引用view_stub布局文件如下,包括一个图片和一个文字框的线性布局:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical" android:layout_width="match_parent"
android:layout_height="match_parent">
<ImageView
android:id="@+id/image_name"
android:layout_gravity="center"
android:src="@drawable/bg_ranking"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
<TextView
android:text="加油"
android:textColor="@color/blue_unable"
android:layout_gravity="center"
android:textSize="18sp"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
</LinearLayout>
运行程序后,运行结果如下: 点击按钮,运行结果如下: 我们用工具查看下两次情况的布局结构。 viewstub替换前: viewstub替换后:
问题总结
本文主要讨论安卓开发layout布局文件时的部分优化手段,上一篇文章中中初步介绍了include和merge两种方式,具体可参考 https://blog.51cto.com/baorant24/6057402 ,本文主要是介绍安卓布局优化的第三种手段,ViewStub的使用和梳理。有兴趣的同学可以进一步深入研究。