ViewStub源码研究
2017-02-19 22:50
218 查看
周末在家,没有办法写demo,当然也没办法测试,只能通过源码直接分析。网上找了一个源码库,分享给大家
链接:http://repository.grepcode.com/java/ext/
ViewStub源码链接:http://www.grepcode.com/file/repository.grepcode.com/java/ext/com.google.android/android/5.1.1_r1/android/view/ViewStub.java
ViewStub调用setVisibility(View.VISIBLE)或inflate()后,会触发inflated layout被初始化。,ViewStub本身会被removed掉,inflated Layout会添加到parent layout中。
注意,inflatedlayout会使用ViewStub的layoutparameters。被remove掉后,ViewStub就没了。后续就得使用inflated ID。
android:inflatedId="@+id/subTree"
android:layout="@layout/mySubTree"
android:layout_width="120dip"
android:layout_height="40dip"/>
解析前通过android:id->stub可以find到ViewStub,inflate()之后通过android:inflatedId->subTree也可以找到inflatedlayout。
Inflatedlayout不需要通过findViewById也可以直接get到View。
这是官方推荐的获取方式。我也觉得更靠谱,避免了一些不必要的逻辑问题。
private int mInflatedId;
private WeakReference<View>mInflatedViewRef;
private LayoutInflater mInflater;
private OnInflateListener mInflateListener;
这里需要看下红字标明的部分。
mLayoutResource = layoutResource;
initialize(context);
}
public ViewStub(Context context,AttributeSet attrs) {
this(context, attrs, 0);
}
@SuppressWarnings({"UnusedDeclaration"})
public ViewStub(Context context,AttributeSet attrs, int defStyleAttr) {
this(context, attrs, defStyleAttr, 0);
}
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);
mLayoutResource =a.getResourceId(R.styleable.ViewStub_layout, 0);
a.recycle();
a = context.obtainStyledAttributes(
attrs,com.android.internal.R.styleable.View, defStyleAttr, defStyleRes);
mID =a.getResourceId(R.styleable.View_id, NO_ID);
a.recycle();
initialize(context);
}
除了mInflatedId和mLayoutResource的赋值,主要看initialize(context).
mContext = context;
setVisibility(GONE);
setWillNotDraw(true);
}
看起来一切正常。setWillNotDraw(true)你懂的,主要看setVisibility(GONE)。
@android.view.RemotableViewMethod
public void setVisibility(int visibility) {
if (mInflatedViewRef != null) {
View view = mInflatedViewRef.get();
if (view != null) {
view.setVisibility(visibility);
} else {
throw newIllegalStateException("setVisibility called on un-referenced view");
}
} else {
super.setVisibility(visibility);
if(visibility == VISIBLE || visibility == INVISIBLE) {
inflate();
}
}
}
这里可以看到setVisibility被ViewStub重写。
若mInflatedViewRef保存了inflatedView的弱引用,如果什么时候这个弱引用get到一个null是要抛出IllegalStateException的。那么在使用的时候就需要注意了,这是可能会挂的。
倘若没有被inflated过,那么mInflatedViewRef肯定是null。看红色标红地方,setVisibility只有在View.GONE才是不会调用inflated的,其他情况都会启动 inflate()。
@android.view.RemotableViewMethod
public void setInflatedId(int inflatedId)
public int getLayoutResource()
@android.view.RemotableViewMethod
public void setLayoutResource(int layoutResource)
public void setLayoutInflater(LayoutInflater inflater)
public LayoutInflater getLayoutInflater()
@Override
protected void onMeasure(int widthMeasureSpec, intheightMeasureSpec) {
setMeasuredDimension(0, 0);
}
@Override
public void draw(Canvas canvas) {
}
@Override
protected void dispatchDraw(Canvas canvas) {
}
这里可以看到ViewStub本身就是空白的。
final ViewParent viewParent =getParent();
if (viewParent != null && viewParentinstanceof 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);
if (mInflatedId != NO_ID) {
view.setId(mInflatedId);
}
final int index =parent.indexOfChild(this);
parent.removeViewInLayout(this);
final ViewGroup.LayoutParams layoutParams =getLayoutParams();
if(layoutParams != null) {
parent.addView(view, index, layoutParams);
}
else {
parent.addView(view,index);
}
mInflatedViewRef = newWeakReference<View>(view);
if (mInflateListener != null) {
mInflateListener.onInflate(this, view);
}
return view;
} else {
thrownew IllegalArgumentException("ViewStub must have a validlayoutResource");
}
} else {
throw new IllegalStateException("ViewStubmust have a non-null ViewGroup viewParent");
}
}
注意看标红的code。
第一个 if(viewParent != null && viewParent instanceof ViewGroup)
说明--->ViewStub不能单独使用,必须作为ViewGroup的二级layout。
第二个 if(mLayoutResource != 0)
说明--->ViewStub如果没有inflate layout定义,那就根本没有存在的价值,直接报错了。IllegalArgument。非法主题。类型都说的很直白了。
第三个 if(mInflatedId != NO_ID) {
view.setId(mInflatedId);
}
说明--->如果没有定义android:inflatedId,那么inflated layout是不会被赋值到inflate出来的Views的
第四个 parent.removeViewInLayout(this);
说明--->ViewStub在inflate之后,会先被remove
第五个 final ViewGroup.LayoutParamslayoutParams = getLayoutParams();
if(layoutParams != null) {
parent.addView(view, index, layoutParams);
}
说明--->inflatedlayout被赋予了ViewStub的layoutparameters
第六个 mInflatedViewRef= new WeakReference<View>(view);
说明--->inflatedlayout被封装在了WeakReference,各位朋友,弱引用的利弊要细细品味。既然是弱引用一些必要的判断就必不可少了。
第七个 mInflateListener.onInflate(this,view);
说明--->inflate完成后,会通过onInflate(this,view)。讲ViewStub和inflated view回调出来,通知ViewStub所在的Activity实例
2. ViewStub必须要有inflateLayout
3. ViewStub的延迟加载原理是将必要的id保存到新view,inflate新new后,先remove ViewStub自身,再讲新View add到parent上。
4. ViewStub利用了同样的layout parameters,所以不会让你感到闪烁,如果有这种感觉,请查一下自己的inflateLayout
5. ViewStub利用了WeakReference保存了inflated View,弱引用是把双刃剑,在使用ViewStub时要小心
链接:http://repository.grepcode.com/java/ext/
ViewStub源码链接:http://www.grepcode.com/file/repository.grepcode.com/java/ext/com.google.android/android/5.1.1_r1/android/view/ViewStub.java
简介
ViewStub是一个用来延迟初始化的View的类。适用于那些Views布局复杂,或者加载比较耗时的情况,延迟初始化可以优化用户体验。官方解释的理解
ViewStub是不可见的,0像素View,被用来在运行时推迟填充layout资源。ViewStub调用setVisibility(View.VISIBLE)或inflate()后,会触发inflated layout被初始化。,ViewStub本身会被removed掉,inflated Layout会添加到parent layout中。
注意,inflatedlayout会使用ViewStub的layoutparameters。被remove掉后,ViewStub就没了。后续就得使用inflated ID。
ViewStub的xml布局范例
<ViewStubandroid:id="@+id/stub"android:inflatedId="@+id/subTree"
android:layout="@layout/mySubTree"
android:layout_width="120dip"
android:layout_height="40dip"/>
解析前通过android:id->stub可以find到ViewStub,inflate()之后通过android:inflatedId->subTree也可以找到inflatedlayout。
换个方式,还可以这样。
ViewStub stub = (ViewStub) findViewById(R.id.stub);
View inflated = stub.inflate();
Inflatedlayout不需要通过findViewById也可以直接get到View。
这是官方推荐的获取方式。我也觉得更靠谱,避免了一些不必要的逻辑问题。
源码解析
变量定义
private int mLayoutResource = 0;private int mInflatedId;
private WeakReference<View>mInflatedViewRef;
private LayoutInflater mInflater;
private OnInflateListener mInflateListener;
这里需要看下红字标明的部分。
ViewStub结构体
public ViewStub(Context context, intlayoutResource) {mLayoutResource = layoutResource;
initialize(context);
}
public ViewStub(Context context,AttributeSet attrs) {
this(context, attrs, 0);
}
@SuppressWarnings({"UnusedDeclaration"})
public ViewStub(Context context,AttributeSet attrs, int defStyleAttr) {
this(context, attrs, defStyleAttr, 0);
}
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);
mLayoutResource =a.getResourceId(R.styleable.ViewStub_layout, 0);
a.recycle();
a = context.obtainStyledAttributes(
attrs,com.android.internal.R.styleable.View, defStyleAttr, defStyleRes);
mID =a.getResourceId(R.styleable.View_id, NO_ID);
a.recycle();
initialize(context);
}
除了mInflatedId和mLayoutResource的赋值,主要看initialize(context).
Initialize(context)
private void initialize(Context context) {mContext = context;
setVisibility(GONE);
setWillNotDraw(true);
}
看起来一切正常。setWillNotDraw(true)你懂的,主要看setVisibility(GONE)。
setVisibility(GONE)
@Override@android.view.RemotableViewMethod
public void setVisibility(int visibility) {
if (mInflatedViewRef != null) {
View view = mInflatedViewRef.get();
if (view != null) {
view.setVisibility(visibility);
} else {
throw newIllegalStateException("setVisibility called on un-referenced view");
}
} else {
super.setVisibility(visibility);
if(visibility == VISIBLE || visibility == INVISIBLE) {
inflate();
}
}
}
这里可以看到setVisibility被ViewStub重写。
若mInflatedViewRef保存了inflatedView的弱引用,如果什么时候这个弱引用get到一个null是要抛出IllegalStateException的。那么在使用的时候就需要注意了,这是可能会挂的。
倘若没有被inflated过,那么mInflatedViewRef肯定是null。看红色标红地方,setVisibility只有在View.GONE才是不会调用inflated的,其他情况都会启动 inflate()。
继续看源码
public int getInflatedId()@android.view.RemotableViewMethod
public void setInflatedId(int inflatedId)
public int getLayoutResource()
@android.view.RemotableViewMethod
public void setLayoutResource(int layoutResource)
public void setLayoutInflater(LayoutInflater inflater)
public LayoutInflater getLayoutInflater()
@Override
protected void onMeasure(int widthMeasureSpec, intheightMeasureSpec) {
setMeasuredDimension(0, 0);
}
@Override
public void draw(Canvas canvas) {
}
@Override
protected void dispatchDraw(Canvas canvas) {
}
这里可以看到ViewStub本身就是空白的。
另一个重头函数inflate()
public View inflate() {final ViewParent viewParent =getParent();
if (viewParent != null && viewParentinstanceof 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);
if (mInflatedId != NO_ID) {
view.setId(mInflatedId);
}
final int index =parent.indexOfChild(this);
parent.removeViewInLayout(this);
final ViewGroup.LayoutParams layoutParams =getLayoutParams();
if(layoutParams != null) {
parent.addView(view, index, layoutParams);
}
else {
parent.addView(view,index);
}
mInflatedViewRef = newWeakReference<View>(view);
if (mInflateListener != null) {
mInflateListener.onInflate(this, view);
}
return view;
} else {
thrownew IllegalArgumentException("ViewStub must have a validlayoutResource");
}
} else {
throw new IllegalStateException("ViewStubmust have a non-null ViewGroup viewParent");
}
}
注意看标红的code。
第一个 if(viewParent != null && viewParent instanceof ViewGroup)
说明--->ViewStub不能单独使用,必须作为ViewGroup的二级layout。
第二个 if(mLayoutResource != 0)
说明--->ViewStub如果没有inflate layout定义,那就根本没有存在的价值,直接报错了。IllegalArgument。非法主题。类型都说的很直白了。
第三个 if(mInflatedId != NO_ID) {
view.setId(mInflatedId);
}
说明--->如果没有定义android:inflatedId,那么inflated layout是不会被赋值到inflate出来的Views的
第四个 parent.removeViewInLayout(this);
说明--->ViewStub在inflate之后,会先被remove
第五个 final ViewGroup.LayoutParamslayoutParams = getLayoutParams();
if(layoutParams != null) {
parent.addView(view, index, layoutParams);
}
说明--->inflatedlayout被赋予了ViewStub的layoutparameters
第六个 mInflatedViewRef= new WeakReference<View>(view);
说明--->inflatedlayout被封装在了WeakReference,各位朋友,弱引用的利弊要细细品味。既然是弱引用一些必要的判断就必不可少了。
第七个 mInflateListener.onInflate(this,view);
说明--->inflate完成后,会通过onInflate(this,view)。讲ViewStub和inflated view回调出来,通知ViewStub所在的Activity实例
小结
1. ViewStub必须作为ViewGroup的二级view。2. ViewStub必须要有inflateLayout
3. ViewStub的延迟加载原理是将必要的id保存到新view,inflate新new后,先remove ViewStub自身,再讲新View add到parent上。
4. ViewStub利用了同样的layout parameters,所以不会让你感到闪烁,如果有这种感觉,请查一下自己的inflateLayout
5. ViewStub利用了WeakReference保存了inflated View,弱引用是把双刃剑,在使用ViewStub时要小心
相关文章推荐
- 基于jquery的lazy loader插件实现图片的延迟加载[简单使用]
- PHP strtotime函数用法、实现原理和源码分析
- jQuery 源码分析笔记(3) Deferred机制
- js 延迟加载 改变JS的位置加快网页加载速度
- javascript图片延迟加载实现方法及思路
- jQuery 源码分析笔记(5) jQuery.support
- jQuery-1.9.1源码分析系列(十)事件系统之事件包装
- jQuery实现鼠标滚动图片延迟加载效果附源码下载
- 使用jquery插件实现图片延迟加载技术详细说明
- Mybatis查询延迟加载详解及实例
- javascript 延迟加载技术(lazyload)简单实现
- 浅析js预加载/延迟加载
- jquery插件lazyload.js延迟加载图片的使用方法
- ECHO.js 纯javascript轻量级延迟加载的实例代码
- jquery lazyload延迟加载技术的实现原理分析
- JS实现图片延迟加载并淡入淡出效果的简单方法
- 关于延迟加载JavaScript
- Android布局技巧之使用ViewStub
- Android笔记之:深入ViewStub的应用
- Android组件ViewStub基本使用方法详解