1.viewstub就是动态加载试图;也就是在我们的app启动绘制页面的时候,他不会绘制到view树中;当在代码中执行inflate操作后,她才会被添加到试图中。其实ViewStub就是一个宽高都为0的一个View,它默认是不可见的,只有通过调用setVisibility函数或者Inflate函数才 会将其要装载的目标布局给加载出来,从而达到延迟加载的效果,这个要被加载的布局通过android:layout属性来设置。最终目的是把app加载页面的速度提高了,使用户体验更好。
2.看一个简单的demo
viewstub.xml
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/inflatedStart"
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:id="@+id/hello_tv"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent"
android:text="DATA EMPTY!"/>
</android.support.constraint.ConstraintLayout>
activity_myviewstub.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<Button
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:onClick="inflate"
android:text="inflate" />
<Button
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:onClick="setData"
android:text="setdata"/>
<Button
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:onClick="hide"
android:text="hide"/>
<Button
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:onClick="show"
android:text="show"/>
<ViewStub
android:id="@+id/vs"
android:inflatedId="@+id/inflatedStart"
android:layout="@layout/viewstub"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</LinearLayout>
MyViewStubActivity.java
package com.ysl.myandroidbase.viewstub;
import android.os.Bundle;
import android.support.annotation.Nullable;
import android.support.constraint.ConstraintLayout;
import android.support.v7.app.AppCompatActivity;
import android.view.View;
import android.view.ViewStub;
import android.widget.TextView;
import com.ysl.myandroidbase.R;
public class MyViewStubActivity extends AppCompatActivity {
private ViewStub viewStub;
private TextView textView;
private View inflate;
private ConstraintLayout constraintLayout;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_myviewstub);
viewStub = findViewById(R.id.vs);
//textView = (TextView) findViewById(R.id.hello_tv);空指针,因为viewstub没有inflate
}
public void inflate(View view){
if (inflate == null) {//inflate只会进行一次,当第二次调用的时候,就会抛异常;也可以try catch进行处理
inflate = viewStub.inflate();
constraintLayout = findViewById(R.id.inflatedStart);
System.out.println(constraintLayout);
System.out.println("viewStub-------->"+viewStub);
textView = viewStub.findViewById(R.id.hello_tv);//获取到的textview是空的;
System.out.println("viewStub textView-------->"+textView);//null
textView = constraintLayout.findViewById(R.id.hello_tv);
System.out.println("constraintLayout textView-------->"+textView);
textView = findViewById(R.id.hello_tv);
System.out.println("textView-------->"+textView);
}
}
public void setData(View view){
if (constraintLayout != null) {
textView = constraintLayout.findViewById(R.id.hello_tv);
textView.setText("HAVE DATA !!!");
}
}
public void hide(View view){
viewStub.setVisibility(View.GONE);
// if (constraintLayout != null){
// constraintLayout.setVisibility(View.GONE);
// }
}
public void show(View view){
viewStub.setVisibility(View.VISIBLE);
// if (constraintLayout != null){
// constraintLayout.setVisibility(View.VISIBLE);
// }
}
}
3.当调用第二次inflate的时候,会报错:
我们看一下这是为什么?进入viewStub.inflate();的源码:
public View inflate() {
final ViewParent viewParent = getParent();
if (viewParent != null && viewParent instanceof ViewGroup) {
if (mLayoutResource != 0) {
final ViewGroup parent = (ViewGroup) viewParent;
final View view = inflateViewNoAdd(parent);
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 {
throw new IllegalStateException("ViewStub must have a non-null ViewGroup viewParent");
}
}
可以看到当viewParent为空或者不是viewgroup时才会报这个错误;那么第一次调用的时候,肯定是进去了;发现一个方法replaceSelfWithView(view,parent);view就是我们在布局文件中给viewstub指定的layout所引用的那个布局;parent就是getParent方法得到的,也就是acticity的填充布局LinearLayout;
进去看一下:
private void replaceSelfWithView(View view, ViewGroup parent) {
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);
}
}
可以发现parent.removeViewInLayout(this);把this就是viewstub从父布局linearlayout中移除了;parent.addView()就是把view(也就是我们引用的布局)添加到了父布局LinearLayout中。
我们用layout inspector来查看一下:
inflate前:可以看到viewstub是灰色的
inflate后:可以看到viewstub直接被移除了,把引用布局直接放到view树里了。
所以当我们第二次再调用inflate方法时,viewstub的parent已经为空了;就会抛出此异常;
当调用textView = viewStub.findViewById(R.id.hello_tv);//获取到的textview是空的;
而使用textView = findViewById(R.id.hello_tv);就可以直接拿到控件对象了;
当实现引用布局的显示和隐藏时,测试发现使用viewstub的setVisibility()方法可以实现,这是为什么呢?;按理说使用constraintLayout.setVisibility()当然也可以;根据上面的view树结构来看,好像使用引用布局的setVisibility()方法更合理一些;
下面我们再来看看viewstub的setVisibility()为什么也可以;跟进源码看看:
源码中使用mInflatedViewRef获取到view,然后设置隐藏与显示;mInflatedViewRef是一个view的弱引用WeakReference<View>
其实在上面的inflate方法中已经为其添加了mInflatedViewRef = new WeakReference<>(view);这个view就是viewstub中的引用布局;
所以,使用viewstub可以实现相同的显示或隐藏效果;
从上图的最后一个红色框中可以发现,假设现在我没有调用inflate方法,而是直接点击了show按钮;然后引用布局也可以绘制出来;这就是我在写demo的时候,直接上去点击show按钮,竟然也可以显示的原因。