最近在面试的时候被面试官问到ViewStub内部是如何使用占位的时候,我是一脸懵逼,说实话我之前对UI上的一些控件内部的代码看的非常少,所以一时答不上来,这个东西其实并不说有多难,而是我们平时在开发的过程中只是简单的去调用了一下但是并没有深入的去了解其内部的原理。其实面试的过程就是我们一个查漏补缺的过程中,让我们的各个知识面都更加的能提高,同时做到知其然更知其所以然。
gone和visible一个view被设置了visibility=gone,在显示的时候它会不会被绘制? - 知乎
1、invisible
view设置为invisible时,view在layout布局文件中会占用位置,但是view为不可见,该view还是会创建对象,会被初始化,会占用资源。
2、gone
view设置gone时,view在layout布局文件中不占用位置,但是该view还是会创建对象,会被初始化,会占用资源。GONE需要重新的布局和通知上级View去刷新,有缓存还要清空缓存
3、viewstub
viewstub是一个轻量级的view,它不可见,不用占用资源,只有设置viewstub为visible或者调用其inflater()方法时,其对应的布局文件才会被初始化。但是viewstub的引用对象需要是一个布局layout文件,如果要是单个的view的话,viewstub就不能满足要求,就需要调用view的gone或者invisible属性了。
https://www.zhihu.com/question/54416902/answer/476979353
GONE真的隐藏;
INVISIBLE不可见但是预留了View的位置;
/* Check if the GONE bit has changed */
if ((changed & GONE) != 0) {
needGlobalAttributesUpdate(false);
requestLayout();//异同点1111111
if (((mViewFlags & VISIBILITY_MASK) == GONE)) {
if (hasFocus()) clearFocus();
clearAccessibilityFocus();
destroyDrawingCache();//异同点2222222
if (mParent instanceof View) {
// GONE views noop invalidation, so invalidate the parent
((View) mParent).invalidate(true);
}
// Mark the view drawn to ensure that it gets invalidated properly the next
// time it is visible and gets invalidated
mPrivateFlags |= PFLAG_DRAWN;
}
if (mAttachInfo != null) {
mAttachInfo.mViewVisibilityChanged = true;
}
}
/* Check if the VISIBLE bit has changed */
if ((changed & INVISIBLE) != 0) {
needGlobalAttributesUpdate(false);
/*
* If this view is becoming invisible, set the DRAWN flag so that
* the next invalidate() will not be skipped.
*/
mPrivateFlags |= PFLAG_DRAWN;
if (((mViewFlags & VISIBILITY_MASK) == INVISIBLE)) {
// root view becoming invisible shouldn't clear focus and accessibility focus
if (getRootView() != this) {
if (hasFocus()) clearFocus();
clearAccessibilityFocus();
}
}
if (mAttachInfo != null) {
mAttachInfo.mViewVisibilityChanged = true;
}
}
如果在GONE和INVISIBLE两者都可以完成你的效果,那么你应该选择INVISIBLE。因为从源码中来看GONE需要重新的布局和通知上级View去刷新,有缓存还要清空缓存;从视图变更开销的来说INVISIBLE要更加的划算一些,如果你的View不是十分占用资源的情况!!!
android中View的GONE和INVISIBLE的原理 - soft.push("zzq") - 博客园
ViewStub介绍一些布局控件在开始时并不需要显示,在程序启动后再根据业务逻辑进行显示,通常的做法是在 xml
中将其定义为不可见,然后在代码中通过setVisibility()
更新其可见性,但是这样做会对程序性能产生不利影响,因为虽然该控件的初始状态是不可见,但仍然会在程序启动时进行创建和绘制,增加了程序的启动时间。正是由于这种情况的存在,Android
系统提供了ViewStub
框架,能够很容易实现“懒加载”以提升程序性能,本文从“使用方法”和“实现原理”两个方面对其进行讲解,希望能对大家有所帮助。
平时我们在开发Android的界面的时候,如果遇到不需要显示的控件或者是布局的时候挺通常我们都会将其设置为View.Gone或者是View.INVISIBLE来达到我们想要的目的的,这样做的优点就是逻辑简单而且控制起来比较灵活,但是其缺点就是耗资源,其实在内部Xml解析的时候同样也会将其解析并且实例化、设置属性的,同样还是会消耗系统资源的。这个时候ViewStub就闪亮登场了,为了更好的方便理解,首先我们看看其大概用法,同时分析源代码以后再来总结其结论
用法首先我们在布局文件里面使用两个ViewStub,然后设置其 id 和layout 布局文件
public class MainActivity extends AppCompatActivity {
private ViewStub mViewStub1;
private ViewStub mViewStub2;
private View mViewStubContentView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//首先根据 id 获取 ViewStub
mViewStub1 = findViewById(R.id.style_1);
mViewStub2 = findViewById(R.id.style_2);
//同时在我们需要的时候,初始化 ViewStub 包裹的布局,其实ViewStub的延迟加载就是这么个原理的
mViewStubContentView = mViewStub1.inflate();
//同时获取 ViewStub的 LayoutParams 参数
ViewGroup.LayoutParams params = mViewStub1.getLayoutParams();
Log.i("LOH", params.width + "...height..." + params.height);
//我们使用display按钮来控制 ViewStub加载出来以后的view的显示与隐藏
findViewById(R.id.display).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if(mViewStubContentView.getVisibility() == View.VISIBLE) {
mViewStubContentView.setVisibility(View.GONE);
}else {
mViewStubContentView.setVisibility(View.VISIBLE);
}
}
});
}
}
上面就是一个非常简单的 ViewStub 的使用案例,如果我们需要显示ViewStub 中布局文件的话,可以调用inflate 方法或者是也可以调用 ViewStub.setVisible(View.VISIBLE)就能将布局显示出来。
代码分析构造方法分析
public final class ViewStub extends View {
.......
//一般ViewStub 在xml中引用的话,都是走这个构造方法的。
public ViewStub(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context);
final TypedArray a = context.obtainStyledAttributes(attrs,
R.styleable.ViewStub, defStyleAttr, defStyleRes);
saveAttributeDataForStyleable(context, R.styleable.ViewStub, attrs, a, defStyleAttr,
defStyleRes);
//首先获取 inflateId 编号
mInflatedId = a.getResourceId(R.styleable.ViewStub_inflatedId, NO_ID);
//然后获取自定义属性 inflatedId 也就是 布局文件(xml 文件)
mLayoutResource = a.getResourceId(R.styleable.ViewStub_layout, 0);
//获取 ViewStub 定义的 id 编号
mID = a.getResourceId(R.styleable.ViewStub_id, NO_ID);
//记住这里需要回收(因为编译器会提示说这里内存泄漏)
a.recycle();
//这是是核心关键,首先设置 当前的View隐藏,同时设置自己不参与绘制,接着我们在 onDraw方法
setVisibility(GONE);
setWillNotDraw(true);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
setMeasuredDimension(0, 0);
}
@Override
public void draw(Canvas canvas) {
}
}
通过上面的代码我们可以很清楚的知道了ViewStub不参与在View的绘制中,首先是设置了View.GONE,接着调用了View方法里面的 setWillNotDraw 不参与界面绘制,而且自己的 draw方法也未任何的实现。最后将自己的宽度和高度都设置为了0。
通过setVisible 方法显示界面
public void setVisibility(int visibility) {
//首先第一次调用的 mInflatedViewRef 是为空的,所以就进入else 分支
if (mInflatedViewRef != null) {
View view = mInflatedViewRef.get();
if (view != null) {
view.setVisibility(visibility);
} else {
throw new IllegalStateException("setVisibility called on un-referenced view");
}
} else {
//首先这里会直接调用 view的 setVisibility方法
super.setVisibility(visibility);
//接着判断我们传进来的 visibility的值,如果还是 GONE的话则不做处理,最后还是调用inflate
if (visibility == VISIBLE || visibility == INVISIBLE) {
inflate();
}
}
}
通过上面的代码分析我们可以得出 setVisibility 最后调用的还是 inflate,所以这个方法才是关键的
public View inflate() {
final ViewParent viewParent = getParent();
//首先判断 ViewStub是否存在父控件,同时父控件是否 ViewGroup
if (viewParent != null && viewParent instanceof ViewGroup) {
//同时必须设置 ViewStub的layout 信息,不能单独设置 View。
if (mLayoutResource != 0) {
final ViewGroup parent = (ViewGroup) viewParent;
//将 ViewStub 的layout布局文件转化为 View,但是不添加到 parent中
final View view = inflateViewNoAdd(parent);
//最后在viewParen中找到ViewStub的位置,同时将inflate出来的View替换ViewStub。
replaceSelfWithView(view, parent);
mInflatedViewRef = new WeakReference(view);
if (mInflateListener != null) {
mInflateListener.onInflate(this, view);
}
return view;
} else {
throw new IllegalArgumentException("ViewStub must have a valid layoutResource");
}
} else {
//ViewStub 不能单独使用,比如是 ViewGroup的一个子View。
throw new IllegalStateException("ViewStub must have a non-null ViewGroup viewParent");
}
}
最后将实例化出来的view替换ViewStub在viewParent中的位置
private void replaceSelfWithView(View view, ViewGroup parent) {
//首先获取ViewStub 在 parent中的位置
final int index = parent.indexOfChild(this);
//同时将其从父控件中移除
parent.removeViewInLayout(this);
//注意这里获取的是 ViewStub的 LayoutParams,也就是 layout出来的参数是没效果的。只能设置ViewStub的参数才行。
final ViewGroup.LayoutParams layoutParams = getLayoutParams();
if (layoutParams != null) {
parent.addView(view, index, layoutParams);
} else {
parent.addView(view, index);
}
}
分析
这个类设计的其实非常的简单,其代码也就仅仅只有几百行。通过我们上面对代码的分析可以将其
总结如下ViewStub 通过设置GONE 以及设置宽和高都为0,以及调用函数setWillNotDraw(true)来达到自己不绘制,不渲染在界面的效果,其实仅仅就是作为一个占着坑的意思。 ViewStub 只能调用一次 setVisibility 方法,而 setVisibility 最后调用的还是 inflate 方法。在 replaceSelfWithView 中 indexOfChild(this)代码中,如果ViewStub被移除了以后,index则是 -1那么 addView的时候则会抛出异常的。 ViewStub layoutParams 加入到载入的android:layotu视图上。而其根节点 layoutParams 设置无效 总结 通过上面的源代码我们可以分析得出ViewStub的懒加载的原理,首先通过 ViewStub占住位置而且又不绘制和显示在界面(原因看分析),接着我们在需要的时候调用ViewStub的setVisible方法和inflate方法来将页面显示,其原来就是将 layout 文件渲染成 view然后添加到其父控件(ViewGroup中),主要是替换之前ViewStub之前占用的位置来达到显示界面。