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

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. iredmail开源邮件系统部署

    Iredmail 安装前注意事项: 通过官网了解iRedMail: 2.    选择相应的linux发行版(我选的是64位的CentOS6.4,iRedMail-0.8.6.tar.bz2) ired ...

  2. C++内存管理2-内存泄漏

    1 C++中动态内存分配引发问题的解决方案 假设我们要开发一个String类,它可以方便地处理字符串数据.我们可以在类中声明一个数组,考虑到有时候字符串极长,我们可以把数组大小设为200,但一般的情况 ...

  3. /home/jdk1.8.0_211/jre/bin/java: Permission denied

    在Linux上安装JDK后启动tomcat 日志中提示  /home/jdk1.8.0_211/jre/bin/java: Permission denied 原因是:运行的命令在这个路径下没有权限 ...

  4. 小程序报错:对应的服务器 TLS 为 TLS 1.0 ,小程序要求的 TLS 版本必须大于等于 1.2

    我这里出现此错误的原因是,搭载域名网站的服务器是windows2008 r2,配置的域名证书是TLS1.0版本,需要在服务器注册表中加入TLS的其他版本. 处理办法如下 小程序报错 TLS 版本必须大 ...

  5. NET高级开发工程师职责要求

    岗位职责1.参与架构以及核心业务的设计:2.使用简单,干净,可维护性高,扩展性好的代码实现产品功能,并在必要时重构现有代码:3.贯彻面向接口以及模块化组件的设计理念:4.熟练RabbitMQ.ES.M ...

  6. Java分布式:分布式锁之Zookeeper

    Java分布式:分布式锁之Zookeeper 分布式锁系列教程重点分享锁实现原理 引入ZooKeeper ZooKeeper是什么呢? ZooKeeper 是一个开源的分布式协调服务,它可以在分布式系 ...

  7. find命令实例

    按时间查找也有参数 -atime 访问时间 -ctime 改变状态的时间 -mtime修改的时间. 这里的时间是以24小时为单位的. 查找最近30分钟修改的当前目录下的.php文件 find . -n ...

  8. ASP.NET SignalR 系列(一)之SignalR介绍

    一.SignalR介绍 ASP.NET SignalR 是一个面向 ASP.NET 开发人员的库,可简化将实时 web 功能添加到应用程序的过程. 实时 web 功能是让服务器代码将内容推送到连接的客 ...

  9. Raft选举算法

    目标:分布式集群中,选举Leader,保持数据一致性   集群中每个节点都有三种状态: Follower:纯小弟 Candidate:候选人.我原来是小弟,但我现在想当老大 Leader:老大 集群状 ...

  10. Dockerfile构建jar镜像

    dockerDockerfilejar包docker-compose 一.安装docker和compose 二.准备jar包 三.编写配置文件 1. Dockerfile 2. docker-comp ...