ViewStub是一种特殊的View,Android官方给出的解释是:一种不可见的(GONE)、size是0的占位view,多用于运行时

延迟加载的,也就是说真正需要某个view的时候。在实际项目中,我发现它试用的场景大体有2种:

1. 某种只第一次需要显示的view,比如某个介绍性的东西,比如用户触发了某些操作,给他弹出一个使用介绍,

这种介绍只出现一次,之后的操作中不会再出现,所以这种情况下可以先用ViewStub来占个位,等介绍的view真正

需要的时候才inflate,一般情况下这种view的创建也比较expensive;

2. 某种直到运行时某一刻的时候才知道应该加载哪个view,比如你可能需要根据服务器返回的某些数据来决定是显示View 1

还是显示View 2,这种情况下,也可以先用ViewStub占位,直到可以做决定了在inflate相应的view;

为了接下来的分析方便,这里我先贴一段ViewStub的xml代码,如下:

<ViewStub
android:id="@+id/stub"
android:inflatedId="@+id/realView" // 可设置,不过一般意义不大
android:layout="@layout/my_real_view"
android:visibility="GONE" // really no need, don't write useless code
android:layout_width="match_parent"
android:layout_height="40dip" />

在代码里你同样可以通过findViewById(R.id.stub)来找到此ViewStub,当真正的"my_real_view"布局被inflate之后,

ViewStub这个控件就被从view层次结构中删除了,取而代之的就是"my_real_view"代表的布局。注意下,这里的所有

layout_width/height都是用来控制inflate之后的view的,而不是给ViewStub用的,这个可能是Android系统里唯一的例外。

在我们的Java代码里使用的方式,官方推荐的做法是:

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

但这里有一点需要留意的就是findViewById(R.id.stub),因为我们知道stub在第一次被inflate之后就会从view层次结构中

删除,所以你应该要确保这样的findViewById只会被调用一次(一般都是这样的情况),但在有些你没想到的case下

可能也会被调用多次。前几天我们在项目里就遇到了这个情况,出现了NPE,最终调查发现是stub的findViewById被又调用

了一次,导致了NPE(因为层次结构中已经找不到这个id了)。好的做法是比如把这种findViewById的放在onCreate类似

的只会被执行一次的地方,否则你就应该格外小心,自己加些保护处理。

  说了这么多,接下来可以切入正题了,废话不多说,从ctor开始上代码:

    public ViewStub(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
TypedArray a = context.obtainStyledAttributes(
attrs, com.android.internal.R.styleable.ViewStub, defStyleAttr, defStyleRes); mInflatedId = a.getResourceId(R.styleable.ViewStub_inflatedId, NO_ID); // 从layout文件中读取inflateId的值,如果有的话
mLayoutResource = a.getResourceId(R.styleable.ViewStub_layout, 0); // 同样的,读取真正的layout文件 a.recycle(); a = context.obtainStyledAttributes(
attrs, com.android.internal.R.styleable.View, defStyleAttr, defStyleRes);
mID = a.getResourceId(R.styleable.View_id, NO_ID); // 读取View_id属性
a.recycle(); initialize(context);
} private void initialize(Context context) {
mContext = context;
setVisibility(GONE); // 看到这里,你就明白了我前面xml文件中提到的没必要手动指定android:visibility="GONE"的原因
setWillNotDraw(true); // 因为ViewStub只是个临时占位的,它不做任何绘制操作,所以打开这个标志位以优化绘制过程,提高性能
}

  接下来,我们看几个相关的比较简单的方法,如下:

    @Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
setMeasuredDimension(0, 0); // 实现很简单,写死的大小为0,0
} @Override
public void draw(Canvas canvas) { // 不做任何绘制
} @Override
protected void dispatchDraw(Canvas canvas) { // 没啥需要dispatch的
} /**
* When visibility is set to {@link #VISIBLE} or {@link #INVISIBLE},
* {@link #inflate()} is invoked and this StubbedView is replaced in its parent
* by the inflated layout resource. After that calls to this function are passed
* through to the inflated view.
*
* @param visibility One of {@link #VISIBLE}, {@link #INVISIBLE}, or {@link #GONE}.
*
* @see #inflate()
*/
@Override
@android.view.RemotableViewMethod
public void setVisibility(int visibility) { // setVisibility做了些特殊处理
if (mInflatedViewRef != null) { // 真正View的弱引用
View view = mInflatedViewRef.get();
if (view != null) {
view.setVisibility(visibility);
} else {
throw new IllegalStateException("setVisibility called on un-referenced view");
}
} else { // 当还没inflate的时候,如果调了setVisibility方法,如果不是GONE的情况会自动在这种情况下调inflate的
super.setVisibility(visibility);
if (visibility == VISIBLE || visibility == INVISIBLE) {
inflate(); // 非GONE的情况,会多调用inflate()方法,所以你拿到ViewStub的实例后既可以主动调
} // inflate()方法也可以通过setVisiblity(VISIBLE/INVISIBLE)来触发对inflate的间接调用
}
}

  最后,我们来看看真正关键的方法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) {
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); // 注意这行,实际上还是调用了LayoutInflater的inflate方法,注意最后一个参数是false,所以这里返回的
// view就是真正layout文件里的root view而不是parent了。
if (mInflatedId != NO_ID) {
view.setId(mInflatedId); // 如果layout文件里有给真正的view指定id就在这里设置上
} final int index = parent.indexOfChild(this);
parent.removeViewInLayout(this); // 找到ViewStub在parent中的位置并删除ViewStub,看到了,在inflate的过程中
// ViewStub真的是被删掉了!!!
final ViewGroup.LayoutParams layoutParams = getLayoutParams(); // 获取LayoutParams,就是你在xml文件中写的
// layout_xxx这样的属性,虽然实际上是属于ViewStub的,
if (layoutParams != null) { // 但下面紧接着就用这个LayoutParams来添加真正的view了
parent.addView(view, index, layoutParams); // 注意下添加的位置就是原先ViewStub待的位置,
// 所以等于就是完完全全替换了
} else {
parent.addView(view, index); // 如果layoutParams是null的case
} mInflatedViewRef = new WeakReference<View>(view); // 用view初始化弱引用 if (mInflateListener != null) { // 通过mInflateListener将inflate完成事件通知出去。。。
mInflateListener.onInflate(this, view);
} return view; // 返回真正的view
} else { // ViewStub必须指定真正的layout文件!!!
throw new IllegalArgumentException("ViewStub must have a valid layoutResource");
}
} else { // ViewStub也必须有个parent view的!!!
throw new IllegalStateException("ViewStub must have a non-null ViewGroup viewParent");
}
} /**
* Specifies the inflate listener to be notified after this ViewStub successfully
* inflated its layout resource.
*
* @param inflateListener The OnInflateListener to notify of successful inflation.
*
* @see android.view.ViewStub.OnInflateListener
*/
public void setOnInflateListener(OnInflateListener inflateListener) {
mInflateListener = inflateListener; // 技术上要想响应inflate完成事件,有2种途径,这里通过设置listener是一种方式,
} // 也可以通过更generic的方式:override真正view的onFinishInflate方法。 /**
* Listener used to receive a notification after a ViewStub has successfully
* inflated its layout resource.
*
* @see android.view.ViewStub#setOnInflateListener(android.view.ViewStub.OnInflateListener)
*/
public static interface OnInflateListener {
/**
* Invoked after a ViewStub successfully inflated its layout resource.
* This method is invoked after the inflated view was added to the
* hierarchy but before the layout pass.
*
* @param stub The ViewStub that initiated the inflation.
* @param inflated The inflated View.
*/
void onInflate(ViewStub stub, View inflated);
}
}

  ViewStub的使用也被Android归到提高性能里面,之所以这么说一方面是因为ViewStub有延迟加载(直到需要的时候才inflate

真正的view)、先占位的作用,另外一方面也是由于对measure、layout、draw这3个关键过程的优化,因为ViewStub的这3个过程

很简单,或者根本就是do nothing,所以性能很好。综上,Android才建议我们用ViewStub来优化layout性能,当然也是真正合适使用

ViewStub的时机,而不是要你在所有的layout文件中都这么做,这个时机在开始的时候我也提到了,另外也可以参考下Android官方的文章:

http://developer.android.com/training/improving-layouts/loading-ondemand.html

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

P.S. 这篇文章本来应该是在上周末完成的,这周末去千岛湖outing,在酒店里实在无聊,发现还有篇未完成的文章就在这里补充下,enjoy。。。

ViewStub源码分析的更多相关文章

  1. 布局优化之ViewStub源码分析

    源码分析 @RemoteView public final class ViewStub extends View { private int mInflatedId; private int mLa ...

  2. ABP源码分析一:整体项目结构及目录

    ABP是一套非常优秀的web应用程序架构,适合用来搭建集中式架构的web应用程序. 整个Abp的Infrastructure是以Abp这个package为核心模块(core)+15个模块(module ...

  3. HashMap与TreeMap源码分析

    1. 引言     在红黑树--算法导论(15)中学习了红黑树的原理.本来打算自己来试着实现一下,然而在看了JDK(1.8.0)TreeMap的源码后恍然发现原来它就是利用红黑树实现的(很惭愧学了Ja ...

  4. nginx源码分析之网络初始化

    nginx作为一个高性能的HTTP服务器,网络的处理是其核心,了解网络的初始化有助于加深对nginx网络处理的了解,本文主要通过nginx的源代码来分析其网络初始化. 从配置文件中读取初始化信息 与网 ...

  5. zookeeper源码分析之五服务端(集群leader)处理请求流程

    leader的实现类为LeaderZooKeeperServer,它间接继承自标准ZookeeperServer.它规定了请求到达leader时需要经历的路径: PrepRequestProcesso ...

  6. zookeeper源码分析之四服务端(单机)处理请求流程

    上文: zookeeper源码分析之一服务端启动过程 中,我们介绍了zookeeper服务器的启动过程,其中单机是ZookeeperServer启动,集群使用QuorumPeer启动,那么这次我们分析 ...

  7. zookeeper源码分析之三客户端发送请求流程

    znode 可以被监控,包括这个目录节点中存储的数据的修改,子节点目录的变化等,一旦变化可以通知设置监控的客户端,这个功能是zookeeper对于应用最重要的特性,通过这个特性可以实现的功能包括配置的 ...

  8. java使用websocket,并且获取HttpSession,源码分析

    转载请在页首注明作者与出处 http://www.cnblogs.com/zhuxiaojie/p/6238826.html 一:本文使用范围 此文不仅仅局限于spring boot,普通的sprin ...

  9. ABP源码分析二:ABP中配置的注册和初始化

    一般来说,ASP.NET Web应用程序的第一个执行的方法是Global.asax下定义的Start方法.执行这个方法前HttpApplication 实例必须存在,也就是说其构造函数的执行必然是完成 ...

随机推荐

  1. 认识AMD、CMD、UMD、CommonJS

    0.导言 JavaScript的生态系统一直在稳步增长,当各种组件混合使用时,就可能会发现不是所有的组件都能“和平共处”,为了解决这些问题,各种模块规范就出来了. 1.AMD(Asynchromous ...

  2. MyCAT简易入门

    MyCAT是mysql中间件,前身是阿里大名鼎鼎的Cobar,Cobar在开源了一段时间后,不了了之.于是MyCAT扛起了这面大旗,在大数据时代,其重要性愈发彰显.这篇文章主要是MyCAT的入门部署. ...

  3. C语言 第七章 数组与字符串

    一.数组 1.1.数组的概念 用来存储一组相同类型数据的数据结构.有点像班上放手机的手机袋,超市的储物柜. 特点:只能存放一种类型的数据,如全部是int型或者全部是char型,数组里的数据成为元素. ...

  4. 重温Servlet学习笔记--servletContext对象

    一个项目中只有一个ServletContext对象,我们可以在多个servlet中获取这个唯一的对象,使用它可以给多个servlet传递数据,我们通常成servletContext为上下文对象.这个对 ...

  5. 探秘Tomcat——从一个简陋的Web服务器开始

    前言: 无论是之前所在实习单位小到一个三五个人做的项目,还是如今一个在做的百人以上的产品,一直都能看到tomcat的身影.工作中经常遇到的操作就是启动和关闭tomcat服务,或者修改了摸个java文件 ...

  6. 使用 gpg 加密文件 - 通过 shell 或 php

    使用 gpg 加密文件 - 通过 shell 或 php 背景:客户提供私钥,并要求我方通过php把加密后的文件传输给他们. 环境 macOS Sierra 10.12.1 php 7.0.8 0.安 ...

  7. 用纯JS做俄罗斯方块 - 简要思路介绍(1)

    大家都知道俄罗斯方块是一款大众化的游戏了,我很小的时候就玩过,今年已经25岁了,可以说俄罗斯方块确实是历史悠久,做俄罗斯方块是我上个星期开始的想法.也许是由于自己从来没有写过这种东西吧,所以有生疏.代 ...

  8. Xamarin.Android之封装个简单的网络请求类

    一.前言 回忆到上篇 <Xamarin.Android再体验之简单的登录Demo> 做登录时,用的是GET的请求,还用的是同步, 于是现在将其简单的改写,做了个简单的封装,包含基于Http ...

  9. XML文档操作集锦(C#篇)

    在JSON没流行起来的时候xml一直作为程序存储配置信息的主流介质:特别是小型数据表方面还是不错的选择,所以经常涉及到的操作无非也就是增删改查,这篇博客主要是对这些对比较常用的操作做了个简单的总结 文 ...

  10. 创业公司招php商城开发者

    众筹  电商 已经融资100W美元 职位要求1.对PHP编程熟悉程度以上,有电商相关开发经验优先:2.熟悉lnmp相关配套搭建运维,开发;熟悉linux 使用3.对数据结构和算法设计有较深刻的理解:4 ...