ViewStub你真的了解吗
目录介绍
- 01.什么是ViewStub
- 02.ViewStub构造方法
- 03.inflate()方法解析
- 04.WeakReference使用
- 05.ViewStub为何无大小
- 06.ViewStub为何不绘制
- 07.可以多次inflate()吗
- 08.ViewStub不支持merge
- 09.ViewStub使用场景
- 10.ViewStub总结分析
好消息
- 博客笔记大汇总【16年3月到至今】,包括Java基础及深入知识点,Android技术博客,Python学习笔记等等,还包括平时开发中遇到的bug汇总,当然也在工作之余收集了大量的面试题,长期更新维护并且修正,持续完善……开源的文件是markdown格式的!同时也开源了生活博客,从12年起,积累共计N篇[近100万字,陆续搬到网上],转载请注明出处,谢谢!
- 链接地址:https://github.com/yangchong211/YCBlogs
- 如果觉得好,可以star一下,谢谢!当然也欢迎提出建议,万事起于忽微,量变引起质变!
01.什么是ViewStub
- ViewStub 是一个看不见的,没有大小,不占布局位置的 View,可以用来懒加载布局。
- 当 ViewStub 变得可见或
inflate()
的时候,布局就会被加载(替换 ViewStub)。因此,ViewStub 一直存在于视图层次结构中直到调用了setVisibility(int)
或inflate()
。 - 在 ViewStub 加载完成后就会被移除,它所占用的空间就会被新的布局替换。
02.ViewStub构造方法
-
先来看看构造方法:
public ViewStub(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { super(context); final TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.ViewStub, defStyleAttr, defStyleRes); // 要被加载的布局 Id mInflatedId = a.getResourceId(R.styleable.ViewStub_inflatedId, NO_ID); // 要被加载的布局 mLayoutResource = a.getResourceId(R.styleable.ViewStub_layout, 0); // ViewStub 的 Id mID = a.getResourceId(R.styleable.ViewStub_id, NO_ID); a.recycle(); // 初始状态为 GONE setVisibility(GONE); // 设置为不会绘制 setWillNotDraw(true); }
-
接下来就看看关键的方法,然后看看初始化状态setVisibility方法。
// 复写了 setVisibility(int) 方法 @Override @android.view.RemotableViewMethod public void setVisibility(int visibility) { // private WeakReference<View> mInflatedViewRef; // mInflatedViewRef 是对布局的弱引用 if (mInflatedViewRef != null) { // 如果不为 null,就拿到懒加载的 View View view = mInflatedViewRef.get(); if (view != null) { // 然后就直接对 View 进行 setVisibility 操作 view.setVisibility(visibility); } else { // 如果为 null,就抛出异常 throw new IllegalStateException("setVisibility called on un-referenced view"); } } else { super.setVisibility(visibility); // 之前说过,setVisibility(int) 也可以进行加载布局 if (visibility == VISIBLE || visibility == INVISIBLE) { // 因为在这里调用了 inflate() inflate(); } } }
03.inflate()方法解析
-
核心来了,平时用的时候,会经常调用到该方法。inflate() 是关键的加载实现,代码如下所示:
public View inflate() { // 获取父视图 final ViewParent viewParent = getParent(); if (viewParent != null && viewParent instanceof ViewGroup) { // 如果没有指定布局,就会抛出异常 if (mLayoutResource != 0) { // viewParent 需为 ViewGroup final ViewGroup parent = (ViewGroup) viewParent; final LayoutInflater factory; if (mInflater != null) { factory = mInflater; } else { // 如果没有指定 LayoutInflater factory = LayoutInflater.from(mContext); } // 获取布局 final View view = factory.inflate(mLayoutResource, parent, false); // 为 view 设置 Id if (mInflatedId != NO_ID) { view.setId(mInflatedId); } // 计算出 ViewStub 在 parent 中的位置 final int index = parent.indexOfChild(this); // 把 ViewStub 从 parent 中移除 parent.removeViewInLayout(this); // 接下来就是把 view 加到 parent 的 index 位置中 final ViewGroup.LayoutParams layoutParams = getLayoutParams(); if (layoutParams != null) { // 如果 ViewStub 的 layoutParams 不为空 // 就设置给 view parent.addView(view, index, layoutParams); } else { parent.addView(view, index); } // mInflatedViewRef 就是在这里对 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"); } }
-
Inflate使用特点
- ViewStub只能被Inflate一次,inflate之后ViewStub对象就会被置为空。即某个被ViewStub指定的布局被Inflate后,就不能够再通过ViewStub来控制它了。
- ViewStub只能用来Inflate一个布局文件,而不是某个具体的View,当然也可以把View写在某个布局文件中。
04.WeakReference使用
-
使用了弱引用管理对象的创建,代码如下所示
- 在这里使用了get方法
@Override @android.view.RemotableViewMethod(asyncImpl = "setVisibilityAsync") 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 { } }
- 在这里创建了弱引用对象
public View inflate() { final ViewParent viewParent = getParent(); if (viewParent != null && viewParent instanceof ViewGroup) { if (mLayoutResource != 0) { mInflatedViewRef = new WeakReference<>(view); return view; } else { throw new IllegalArgumentException("ViewStub must have a valid layoutResource"); } } }
05.ViewStub为何无大小
-
首先先看一段源码,如下所示:
@Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { setMeasuredDimension(0, 0); } @Override public void draw(Canvas canvas) { } @Override protected void dispatchDraw(Canvas canvas) { }
-
有没有觉得很与众不同
- draw和dispatchDraw虽然重写了,但是看代码却都是什么也不做!并且onMeasure还什么也不做,直接setMeasuredDimension(0,0);来把view区域设置位0,原来一个ViewStub虽然是一个view,却是一个没有任何显示内容,也不显示任何内容的特殊view,并且对layout在加载时候不可见的。
06.ViewStub为何不绘制
-
具体看一下setWillNotDraw(true)方法,代码如下:
public void setWillNotDraw(boolean willNotDraw) { setFlags(willNotDraw ? WILL_NOT_DRAW : 0, DRAW_MASK); }
-
View中,对于WILL_NOT_DRAW是这样定义的:
/** * This view won't draw. {@link #onDraw(android.graphics.Canvas)} won't be * called and further optimizations will be performed. It is okay to have * this flag set and a background. Use with DRAW_MASK when calling setFlags. * {@hide} */ static final int WILL_NOT_DRAW = 0x00000080;
-
设置WILL_NOT_DRAW之后,onDraw()不会被调用,通过略过绘制的过程,优化了性能。在ViewGroup中,初始化时设置了WILL_NOT_DRAW,代码如下:
public ViewGroup(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { super(context, attrs, defStyleAttr, defStyleRes); initViewGroup(); initFromAttributes(context, attrs, defStyleAttr, defStyleRes); } private void initViewGroup() { // ViewGroup doesn't draw by default if (!debugDraw()) { setFlags(WILL_NOT_DRAW, DRAW_MASK); } mGroupFlags |= FLAG_CLIP_CHILDREN; mGroupFlags |= FLAG_CLIP_TO_PADDING; mGroupFlags |= FLAG_ANIMATION_DONE; mGroupFlags |= FLAG_ANIMATION_CACHE; mGroupFlags |= FLAG_ALWAYS_DRAWN_WITH_CACHE; if (mContext.getApplicationInfo().targetSdkVersion >= Build.VERSION_CODES.HONEYCOMB) { mGroupFlags |= FLAG_SPLIT_MOTION_EVENTS; } setDescendantFocusability(FOCUS_BEFORE_DESCENDANTS); mChildren = new View[ARRAY_INITIAL_CAPACITY]; mChildrenCount = 0; mPersistentDrawingCache = PERSISTENT_SCROLLING_CACHE; }
- 所以,在写自定义布局时,如果需要调用onDraw()进行绘制,则需要在初始化时候,调用setWillNotDraw(false)。若是想要更进一步阅读View中WILL_NOT_DRAW的相关源码,可以去看下PFLAG_SKIP_DRAW相关的代码。
07.可以多次inflate()吗
- ViewStub对象只可以Inflate一次,之后ViewStub对象会被置为空。同时需要注意的问题是,inflate一个ViewStub对象之后,就不能再inflate它了,否则会报错:ViewStub must have a non-null ViewGroup viewParent。。
-
其实看一下源码就很好理解:
public View inflate() { //获取viewStub的父容器对象 final ViewParent viewParent = getParent(); if (viewParent != null && viewParent instanceof ViewGroup) { if (mLayoutResource != 0) { final ViewGroup parent = (ViewGroup) viewParent; //这里是加载布局,并且给它设置id //布局的加载是通过LayoutInflater解析出来的 final View view = inflateViewNoAdd(parent); //这行代码很重要,下面会将到 replaceSelfWithView(view, parent); //使用弱引用 mInflatedViewRef = new WeakReference<>(view); if (mInflateListener != null) { mInflateListener.onInflate(this, view); } return view; } else { //如果已经加载出来,再次inflate就会抛出异常呢 throw new IllegalArgumentException("ViewStub must have a valid layoutResource"); } } else { throw new IllegalStateException("ViewStub must have a non-null ViewGroup viewParent"); } }
-
其实也可以用一张图来理解它,如下所示,摘自网络
- 也就是说,一旦调用inflate上面的方法后ViewStub就会变成null了,因此使用该对象特别需要注意空指针问题。
08.ViewStub不支持merge
- 不能引入包含merge标签的布局到ViewStub中。否则会报错:android.view.InflateException: Binary XML file line #1: can be used only with a valid ViewGroup root and attachToRoot=true
09.ViewStub使用场景
- 一般的app中大多有这么一个功能,当加载的数据为空时显示一个数据为空的视图、在数据加载失败时显示加载失败对应的UI,当没有网络的时候加载没有网络的UI,并支持点击重试会比白屏的用户体验更好一些。俗称,页面状态切换管理……一般来说,加载中、加载失败、空数据等状态的UI风格,在App内的所有页面中需要保持一致,也就是需要做到全局统一,也支持局部定制。
- ViewStub的优势在于在上面的场景中,并不一定需要把所有的内容都展示出来,可以隐藏一些View视图,待用户需要展示的时候再加载到当前的Layout中,这个时候就可以用到ViewStub这个控件了,这样可以减少资源的消耗,使最初的加载速度变快。
- 那么就有了之前开发使用的状态管理器开源库,就是采用了ViewStub这个控件,让View状态的切换和Activity彻底分离开。用builder模式来自由的添加需要的状态View,可以设置有数据,数据为空,加载数据错误,网络错误,加载中等多种状态,并且支持自定义状态的布局。可以说完全不影响性能……
10.ViewStub总结分析
- 分析源码的原理,不管认识到哪一步,最终的目标还是在运用上,即把看源码获得的知识用到实际开发中,那么关于ViewStub的使用技巧,具体可以看我的状态管理器案例,链接地址:https://github.com/yangchong211/YCStateLayout
- 欢迎你的star,这也是开源和写博客的源源动力,哈哈
ViewStub状态管理库:https://github.com/yangchong211/YCStateLayout
开源博客大汇总:https://github.com/yangchong211/YCBlogs
低调大师中文资讯倾力打造互联网数据资讯、行业资源、电子商务、移动互联网、网络营销平台。
持续更新报道IT业界、互联网、市场资讯、驱动更新,是最及时权威的产业资讯及硬件资讯报道平台。
转载内容版权归作者及来源网站所有,本站原创内容转载请注明来源。
- 上一篇
阿里云城市大脑最新实践 衢州公交通行效率提高14%
11月20日,衢州市人民政府发布了“城市大脑”的最近实践成果——当地1路和103路公交路线的27个路口实现了公交信号优先,其中103路公交车通行效率提高了14%。 据了解,衢州智慧交通项目在新型智慧城市顶层设计框架下,以城市大脑 2.0 为技术支撑,采用人工智能、云计算、物联网等技术,全面提升交通管理水平和交通运行效率。 为了提高公共交通通行效率,衢州采用全国首创的亚米级高精定位系统、地图系统和信号联动系统,在不影响路面及其他车辆的通行效率的前提下,对公交车通行执行相对优先。 城市大脑支持衢州智慧交通一期项目实现了基于实时流量和人工智能的信号灯智能调度,全面完成了市区的信号灯配时优化,保证路口通行能力处于单点最优或区域最优。 “在交通治堵方面,我们将主城区的118个信号机接入指挥中心,这118个信控路口划分为56个子区,实现了区域系统信号配时优化。”衢州市公安交警支队支队长余水陆介绍说。 此外,当地市民可通过支付宝、衢州交警公众号等入口进入智慧交通的应用端“e衢行”,享受到停车场查询、实时公交查询、公共自行车查询等便民服务。 “我们会进一步将深入业务场景,通过数据智能技术、视频技术等融...
- 下一篇
这场骗局像极了爱情,阿里小程序“一云多端”用法律武器捍卫爱情
王某通过婚恋平台认识了自称丁某的男士,并添加其社交账号,对方通过包装自己的社交形象,伪装成成功男士,并经常发送甜言蜜语俘获王某芳心。在确定男女朋友关系后,丁某便引诱王某去彩票平台帮忙充值刷流水,声称充值后随时可以提现。王某先后充值2万元后,发现所谓可以提现的网址无法打开,也无法联系到丁某时,才意识到被骗。 面对防不胜防的各种电信诈骗行为,法家云牵手支付宝与阿里云共同打造“云上法律服务平台”,通过简单易操作的支付宝小程序“法家云法律服务”提高法律服务的性价比,更好更有效的打击诈骗。 法家云是全球法律服务整合平台,法家云已有的4万多名律师遍布全国359个城市和世界62个国家和地区。法家云与各地的司法行政机构均有合作,还是上海市司法局的战略合作伙伴。法家云2015年12月上线支付宝城市服务,2016年10月上线钉钉,2016年12月获得蚂蚁
相关文章
文章评论
共有0条评论来说两句吧...
文章二维码
点击排行
推荐阅读
最新文章
- CentOS6,CentOS7官方镜像安装Oracle11G
- 设置Eclipse缩进为4个空格,增强代码规范
- SpringBoot2整合Thymeleaf,官方推荐html解决方案
- CentOS7编译安装Cmake3.16.3,解决mysql等软件编译问题
- SpringBoot2配置默认Tomcat设置,开启更多高级功能
- SpringBoot2全家桶,快速入门学习开发网站教程
- Docker快速安装Oracle11G,搭建oracle11g学习环境
- CentOS7,CentOS8安装Elasticsearch6.8.6
- CentOS8,CentOS7,CentOS6编译安装Redis5.0.7
- MySQL8.0.19开启GTID主从同步CentOS8