这个问题也是头条面试官问的,本身没什么难度,但以前确实没仔细研究过。

1、使用介绍

ViewStub是一种不可见的尺寸为0的View,用来实现布局资源的懒加载。当ViewStub被设置为用户可见或其  inflate() 被调用时,实际的布局资源才会被加载。这时ViewStub在View树中的位置会被新加载的View取代,并且新加载的View会继承ViewStub所拥有的布局参数。而且我们还可以通过ViewStub的 inflatedId 属性定义新加载View的控件ID。

<ViewStub
android:id="@+id/stub"
android:inflatedId="@+id/subTree"
android:layout="@layout/mySubTree"
android:layout_width="120dip"
android:layout_height="40dip" />

此时ViewStub可以通过“stub”这个ID获取到,在“mySubTree”指向的布局被加载之后,ViewStub会被从View树中移出,“myStubTree”指向的布局便可以通过“subTree”这个ID获取到。新加载的布局会继承ViewStub的布局参数,在这里就是宽120dip,高40dip。利用StubView加载布局的方式如下:

ViewStub stub = findViewById(R.id.stub);
View inflated = stub.inflate();

当 inflate() 被调用以后,ViewStub被新加载的View取代。通过这种方式应用可以直接获取到新加载的View而无需再次调用  findViewById() 。

翻译的可能不是太准确,有兴趣的朋友可以看原文:StubView

2、源码分析

显然,我们需要关注的函数是ViewStub的 inflate() 。这个函数在使用的时候有一个需要注意的地方就是它只能被调用一次,至于为什么可以在源码中找到答案。

public View inflate() {
// 获取父View。
final ViewParent viewParent = getParent();
// 如果父View为null或者父View不是ViewGroup的子类则抛出异常。
if (viewParent != null && viewParent instanceof ViewGroup) {
// mLayoutResource即为StubView的layout属性的值,它代表了
// 需要被加载的布局ID。显然它不能为0。
if (mLayoutResource != 0) {
final ViewGroup parent = (ViewGroup) viewParent;
final LayoutInflater factory;
if (mInflater != null) {
factory = mInflater;
} else {
factory = LayoutInflater.from(mContext);
}
// 加载布局并实例化。
final View view = factory.inflate(mLayoutResource, parent,
false);
// 此处StubView将自己的inflatedId属性的值设置给了新加载的View。
if (mInflatedId != NO_ID) {
view.setId(mInflatedId);
}
// 紧接着StubView将自己从父View中移出。
final int index = parent.indexOfChild(this);
parent.removeViewInLayout(this);
// 将自己的LayoutParams设置给新加载的View,这就是为什么新加载的View会
// 继承ViewStub的布局参数。
final ViewGroup.LayoutParams layoutParams = getLayoutParams();
// 将新加载的View添加到父View中,而且是自己原来的位置。
if (layoutParams != null) {
parent.addView(view, index, layoutParams);
} else {
parent.addView(view, index);
}
// 保存新加载的View的弱引用。
mInflatedViewRef = new WeakReference<View>(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");
}
}

在函数的第26行可以看到,ViewStub将自己从父View中移除,所以如果再次调用 inflate() ,第5行的判断不成立就会抛出异常。在第37行,ViewStub保存了新加载的View的弱引用,为什么还要保存这个引用呢?这是因为在调用了 inflate() 之后,仍可以使用StubView的 setVisibility() 来设置新加载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();
}
}
}

如果之前已将调用了 inflate() 那么 mInflatedViewRef 肯定不为 null ,否则如果参数为 VISIBLE 或 INVISIBLE 的话, inflate() 函数就会被调用。

3、总结

ViewStub如何提高我们的加载性能:

在它的初始化函数中,使用了 setVisibility(GONE) ,我们知道,可见性为 GONE 的View是不会被绘制且不占用空间的。

与直接将目标控件的可见性设置为 GONE 相比它的优势:

即使将可见性设置为 GONE ,在加载布局时仍需要对控件进行初始化等操作。这时ViewStub则显得十分轻量。

利用ViewStub实现布局懒惰加载的更多相关文章

  1. 利用青瓷布局自定义加载的场景,而不是自己改写qici-loading

    加载界面如果全部通过自己手动布局不仅不美观,还很难控制.借用原生的场景切换加载效果,来实现我们游戏的加载效果. 没有做加载修改的原来的加载顺序:   黑乎乎界面->(游戏定制的加载)你的第一个场 ...

  2. 利用简洁的图片预加载组件提升h5移动页面的用户体验

    在做h5移动页面,相信大家一定碰到过页面已经打开,但是里面的图片还未加载出来的情况,这种问题虽然不影响页面的功能,但是不利于用户体验.抛开网速的原因,解决这个问题有多方面的思路:最基本的,要从http ...

  3. Android 实现布局动态加载

    Android 动态加载布局 通过使用LayoutInflater 每次点击按钮时候去读取布局文件,然后找到布局文件里面的各个VIEW 操作完VIEW 后加载进我们setContentView 方面里 ...

  4. MVC学习七:Razor布局之加载分部视图【PartialView】

    Partial View 顾名思义就是Html代码片段,应用于此HTML代码多次被页面加载时使用.(类似于WebForm程序中的用户控件) 注:PartialView和正常的View页面在访问时没有任 ...

  5. C# winform利用反射和自定义特性加载功能模块(插件式开发)

    由于在实际的工作中, 碰见这样的一个问题: 一个软件, 销售给A客户 他需要所有功能, 但是销售给B客户, 他只需要其中的一部分, 1.如果我们在实际的开发过程中, 没有把一些功能模块区分开来的话, ...

  6. 利用Navigation Timing测量页面加载时间

    最近在看一本名为<web性能实践日志>的书籍,其中第十三章"网络计时"中介绍了一种比较新的计算页面各部分加载时间方法,这也是W3C Web性能工作小组正在做的事情,接下 ...

  7. 利用LruCache为GridView异步加载大量网络图片完整示例

    MainActivity如下: package cc.testlrucache; import android.os.Bundle; import android.widget.GridView; i ...

  8. 2016 - 1- 19 利用多线程优化从网上加载图片的Demo

    // // ZZTableViewController.m // 多图片下载 // // Created by Mac on 16/1/19. // Copyright © 2016年 Mac. Al ...

  9. 利用iscroll实现上拉加载下拉刷新

    1.首先引用isScroll插件 说明:页面加载时初始化isScroll,然后调用pullDownAction()和pullUpAction(),每次切换tab时,只需要对pullDownAction ...

随机推荐

  1. org.apache.hadoop.fs.FsUrlStreamHandlerFactory 在哪个jar包

    org.apache.hadoop.fs.FsUrlStreamHandlerFactory在org.apache.hadoop类中,org.apache.hadoop在hadoop安装目录下.

  2. reentrant,thread-safe 和 async-signal-safe

    可重入,线程安全和异步信号安全POSIX定义: Reentrant Function A function whose effect, when called by two or more threa ...

  3. 破解MySQL登录密码的几种方法

    工具列表 Medusa Ncrack Hydra Metasploit Medusa medusa  -h 192.168.1.106 –U /root/Desktop/user.txt –P /ro ...

  4. Git 克隆

    工作中大家分工协作,共同开发维护一个项目,git仓库放在公共的服务器上,如github.Gitee等. 在这种情况使用git,需要先克隆仓库到本地. 克隆非常简单,使用命令git clone 将远程仓 ...

  5. jQuery正则校验

    jQuery正则校验 银行卡号 //验证银行卡号,bankno为银行卡号function luhnCheck(){ var bankno = $.trim($("#bankNoInp&quo ...

  6. window安装mysql8.0解决大部分客户端无法连接问题登陆问题

    https://blog.csdn.net/u013308810/article/details/80114021

  7. @PropertySource绝对路径(java.io.FileNotFoundException问题解决)

    经常使用@PropertySource 来指定配置文件,然后@value获取配置参数: @Component @PropertySource(value= {"classpath:rules ...

  8. Word 去除文字底纹

    有时候从网页复制的文字有底纹,如何清除呢? 未完 ...... 点击访问原文(进入后根据右侧标签,快速定位到本文)

  9. python_协程

    协程 问题一: 生成器与函数的区别?生成器分阶段的返回多个值,相当于有多个出口(结果): yield ''' yield # 中断.返回函数值 1.只能在函数中使用 2.会暂停函数执行并且返回表达式结 ...

  10. linux初学者-编辑文件工具vim

      "vim"是linux中非常强大,应用非常广的编辑方式.下面介绍一些"vim"的基本用法.以"/etc/passwd"为例. 1.vim ...