Android 布局优化之ViewStub

您所在的位置:网站首页 淮安龙宫大白鲸极地海洋世界门票多少钱 Android 布局优化之ViewStub

Android 布局优化之ViewStub

2023-04-05 04:53| 来源: 网络整理| 查看: 265

前言: 在设计模式的单利模式中,懒汉式和饿汉式是其中两种。 一种是在类被加载的时候就完成单例对象的初始化,一种是在需要使用该单例的时候才初始化。 在android的视图设计中,同样需要使用的这样的设计模式。 这样的视图加载起来需要耗费很多的时间。在这几百个视图里面,可能有部分视图是在点击某一按钮也就是并不是马上加载, 而是延迟到要使用的时候才加载这部分视图。也就是类似于单例模式中的懒加载。 特性: 1. ViewStub是一个继承了View类的视图。 2. ViewStub是不可见的,实际上是把宽高都设置为0 3. 可以通过布局文件的android:inflatedId或者调用ViewStub的setInflatedId方法为懒加载视图的跟节点设置ID 4. ViewStub视图在首次调用setVisibility或者inflate方法之前,一直存在于视图树中 5. 只需要调用ViewStub的setVisibility或者inflate方法即可显示懒加载的视图 6. 调用setVisibility或者inflate方法之后,懒加载的视图会把ViewStub从父节点中替换掉 7. ViewStub的inflate只能被调用一次,第二次调用会抛出异常,setVisibility可以被调用多次,但不建议这么做(后面说原因) 8. 为ViewStub赋值的android:layout_属性会替换待加载布局文件的根节点对应的属性 9. inflate方法会返回待加载视图的根节点 使用:

我在一个activity上放置了一个按钮,点击后加载懒加载的视图。

Activity布局文件定义my_sub_activity.xml:

其中android:inflatedId指定了懒加载视图跟节点的ID。android:layout指定了懒加载的视图。android:layout_width和android:layout_height分别指定了懒加载视图的宽和高。

懒加载布局文件my_sub_tree.xml:

懒加载视图里只有一个TextView(这里只是做测试,正常情况下这里应该是一个复杂的视图)。

ViewStubActivity的代码:

public class ViewStubActivity extends Activity implements View.OnClickListener { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.my_sub_activity); } @Override public void onClick(View v) { // 这里调用的是inflate方法,当然,也可以调用setVisibility方法(但是不建议这么做) // 只能点击一次加载视图按钮,因为inflate只能被调用一次 // 如果再次点击按钮,会抛出异常"ViewStub must have a non-null ViewGroup viewParent" ((ViewStub) findViewById(R.id.stub)).inflate(); } }

代码里设置了布局,并在点击后查到到ViewStub对象,并加载视图。

下面看看加载视图前后的对比图:

image.png

为了说明视图树在加载前后的对比,我使用hierarchyviewer视图树查看工具,做了一个前后对比图: 加载前视图树:

image.png 加载后视图树: image.png

从上面的两个视图树中我们明显发现,ViewStub节点被TextView替换。 也就是说,在调用inflate方法之前,ViewStub一直存在于视图树中,当调用inflate之后,ViewStub被加载的视图替换,到此,ViewStub的作用完成,之后ViewStub可能被内存回收(如果没有声明成成员变量的话,也就是没有强引用)

源码解析: 下面针对ViewStub的特性对源码进行解析: 特性一:ViewStub是一个继承了View类的视图。

public final class ViewStub extends View {

特性二: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) { // 不分发绘制事件 }

ViewStub在计算的时候,为自己请求的宽高都为0,并重写了绘制相关的方法,但不做任何事情。

特性三:可以通过布局文件的android:inflatedId或者调用ViewStub的setInflatedId方法为懒加载视图的跟节点设置ID(如果跟视图未设置ID的话)

public ViewStub(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { TypedArray a = context.obtainStyledAttributes( attrs, com.android.internal.R.styleable.ViewStub, defStyleAttr, defStyleRes); // 通过自属性inflatedId来获取加载的视图跟节点ID,默认返回NO_ID,也就是-1,代表没有赋值id mInflatedId = a.getResourceId(R.styleable.ViewStub_inflatedId, NO_ID); // 需要加载的视图资源ID mLayoutResource = a.getResourceId(R.styleable.ViewStub_layout, 0); a.recycle(); a = context.obtainStyledAttributes( attrs, com.android.internal.R.styleable.View, defStyleAttr, defStyleRes); // 为自己赋值ID,没有则赋值为-1 mID = a.getResourceId(R.styleable.View_id, NO_ID); a.recycle(); // 初始化视图 initialize(context); } private void initialize(Context context) { mContext = context; // 设置视图不可见 setVisibility(GONE); // 设置当前视图不可绘制 setWillNotDraw(true); }

初始化的时候,从配置文件中取出了inflatedId和待加载的资源文件id以及自身的id, 最后,调用了initialize将自身设置为不可见,并设置为不可重绘,最大限度减少资源占用。

最后看看什么时候ViewStub执行加载视图操作: 首先是inflate方法:

/** * Inflates the layout resource identified by {@link #getLayoutResource()} * and replaces this StubbedView in its parent by the inflated layout resource. * * @return The inflated layout resource. * */ public View inflate() { // 拿到父节点 final ViewParent viewParent = getParent(); if (viewParent != null && viewParent instanceof ViewGroup) { // 判断父节点不为空,并且是容器,则进行视图加载 if (mLayoutResource != 0) { // 必须在布局文件中,或者是调用setLayoutResource方法设置待加载的视图资源文件ID final ViewGroup parent = (ViewGroup) viewParent; final LayoutInflater factory; // mInflater是外部设置进来的,通过setLayoutInflater方法设置 if (mInflater != null) { factory = mInflater; } else { // 如果外部未设置视图加载器,初始化 factory = LayoutInflater.from(mContext); } // 加载视图,得到视图根节点 final View view = factory.inflate(mLayoutResource, parent, false); if (mInflatedId != NO_ID) { // 如果有设置inflateId,则赋值给根节点(如果根节点自己有id,会被覆盖) view.setId(mInflatedId); } // 得到ViewStub在父节点中的位置 final int index = parent.indexOfChild(this); // 从父节点中移除ViewStub(到此,ViewStub从视图树种移除) parent.removeViewInLayout(this); // 得到ViewStub在布局文件中定义的android:layout_*的属性 final ViewGroup.LayoutParams layoutParams = getLayoutParams(); // 将懒加载视图添加到ViewStub的父节点(到此,ViewStub被完全替换) if (layoutParams != null) { parent.addView(view, index, layoutParams); } else { parent.addView(view, index); } // 将懒加载的视图使用弱引用进行引用(给setVisibility方法使用,后面会讲) mInflatedViewRef = new WeakReference(view); // 视图加载成功后调用回调方法。 if (mInflateListener != null) { mInflateListener.onInflate(this, view); } // 返回加载后布局文件的根节点 return view; } else { // 如果未设置layoutResource也就是待加载的视图,则抛出异常 throw new IllegalArgumentException("ViewStub must have a valid layoutResource"); } } else { // 如果ViewStub的父节点为空,因为ViewStub成功执行inflate方法后 // 会调用parent.removeViewInLayout(this);将自己从父节点移除 // 所以ViewStub的inflate只能调用一次 throw new IllegalStateException("ViewStub must have a non-null ViewGroup viewParent"); } }

ViewStub的inflate方法简要的讲就是把自己从父亲从移除,把待加载的视图加入到父节点中, 并把自己所有的layout属性给待加载的视图, 什么是layout属性呢,也就是下面以”android:layout_”打头的属性: 如android:layout_width以及layout_height, 所以这里大家需要小心自己的待加载视图的根节点的android:layout_属性被替换掉。

接着我们有提到,调用ViewStub的setVisibility也可以加载待加载视图:

public void setVisibility(int visibility) { if (mInflatedViewRef != null) { // 如果对待加载视图的软引用不为空,说明已经执行过inflate方法了 // 因为在inflate方法执行成功后有对其赋值 View view = mInflatedViewRef.get(); if (view != null) { // 如果引用的视图未被垃圾回收器回收,则设置其可见性 view.setVisibility(visibility); } else { // 如果引用的视图已经被垃圾回收器回收,则抛出异常 // 这也就是为什么setVisibility可以调用多次,但是并不推荐这样做的原因 throw new IllegalStateException("setVisibility called on un-referenced view"); } } else { // 如果弱引用对象未初始化,则说明未调用inflate // 设置自身可见性 super.setVisibility(visibility); if (visibility == VISIBLE || visibility == INVISIBLE) { // 如果传入的是VISIBLE或者INVIBLE,则调用inflate加载视图 inflate(); } } }

另外ViewStub还提供了一系列方法,供用户设置属性:

/** 获取待加载视图的根节点ID */ public int getInflatedId() { return mInflatedId; } /** 设置待加载视图的根节点ID */ public void setInflatedId(int inflatedId) { mInflatedId = inflatedId; } /** 获取待加载视图的资源文件ID */ public int getLayoutResource() { return mLayoutResource; } /** 设置待加载视图的资源文件ID */ public void setLayoutResource(int layoutResource) { mLayoutResource = layoutResource; } /** 设置布局加载器 */ public void setLayoutInflater(LayoutInflater inflater) { mInflater = inflater; }

总结 1.ViewStub标签需要必须通过android:layout属性指定待加载的视图资源文件ID,否则会抛异常(在inflate方法被调用前,通过setLayoutResource也可以设置待加载的视图资源文件ID,但不建议这样做)。 2.ViewStub标签的所有android:layout_打头的属性,都会替换待加载视图的跟布局对应属性 3.最好通过ViewStub的inflate方法加载视图,该方法会返回视图根节点。 4.inflate方法只能调用一次,不建议通过setVisibility加载视图 5.如果需要通过findViewById查找待加载视图中的节点,需要在inflate方法执行之后,否则会找不到 ———————————————— 版权声明:本文为CSDN博主「良秋」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。 原文链接:https://blog.csdn.net/a740169405/article/details/50351013



【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3