菜鸟一枚,不擅长用文字解释概念 ,所以,接下来这段出自《Android开发艺术探究》任玉刚 著
引文:
ViewStub继承了View,它非常轻量级且宽高都是0,因为本身不参与任何的布局和绘制过程。ViewStub的意义在于按需加载所需的布局文件,在实际开发中,有很多布局文件在正常情况下不会显示,比如网络异常时的界面,这个时候就没有必要在整个界面初始化的时候将其加载进来,通过ViewStub就可以在使用的时候再加载,提高了程序初始化时的性能。
在需要加载ViewStub中的布局时,可以按照如下两种方式进行:
((ViewStub)findViewById(R.id.mViewStub)).inflate();
((ViewStub)findViewById(R.id.mViewStub)).setVisibility(View.VISIBLE);
当ViewStub通过setVisible或者inflate方法加载后,ViewStub就会被它内部的布局替换掉,这个时候ViewStub不在是整个布局结构中的一部分了。
——引文结束
那我们先通过一个Demo了解一下:
主布局文件:
activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/activity_main"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context="com.example.liujianbo.viewstub.MainActivity">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="16sp"
android:layout_margin="10dp"
android:layout_gravity="center"
android:text="曾经沧海难为水,"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="16sp"
android:layout_margin="10dp"
android:layout_gravity="center"
android:text="除却巫山不是云。"/>
<Button
android:id="@+id/button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="显示"/>
<ViewStub
android:id="@+id/viewStub"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout="@layout/details"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="16sp"
android:layout_margin="10dp"
android:layout_gravity="center"
android:text="测试"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="16sp"
android:layout_margin="10dp"
android:layout_gravity="center"
android:text="测试"/>
</LinearLayout>
接下来我们在写一个ViewStub对应的布局文件:
(比如ViewStub里面有一个android:layout=@layout/details属性)
details.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="wrap_content">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="16sp"
android:layout_margin="10dp"
android:layout_gravity="center"
android:text="取次花丛懒回顾,"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="16sp"
android:layout_margin="10dp"
android:layout_gravity="center"
android:text="半缘修道半缘君。"/>
</LinearLayout>
如果成功用details.xml替换我们的ViewStub的话,就可以看见整首诗了,恩!
接下来是我们的主活动:
MainActivity
package com.example.liujianbo.viewstub;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.view.ViewStub;
import android.widget.Button;
public class MainActivity extends AppCompatActivity {
private Button mButton;
private ViewStub mViewStub;
private boolean isHideViewStub = false;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
initView();
}
private void initView(){
mViewStub = (ViewStub) findViewById(R.id.viewStub);
mButton = (Button) findViewById(R.id.button);
mButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if (isHideViewStub) {
mViewStub.setVisibility(View.GONE);
isHideViewStub = false;
}
else {
mViewStub.setVisibility(View.VISIBLE);
isHideViewStub = true;
}
}
});
}
}
效果图:
看到了,开心叭。
那接下来我们开始理解一下任玉刚先生的那些话:
1. “ViewStub继承了View,他非常轻量级,且宽高都是0,因此它本身不参与任何的布局和绘制过程。”
ViewStub就类似于四代目的标记,引入的布局就类似于四代目火影。标记是没有大小的,但是可以引入四代目,所以说,被四代目标记的人,已经预定了死神。(看火影的朋友嗨起来!!!)
那我们用一个布局文件说明一下问题:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/activity_main"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context="com.example.liujianbo.viewstub.MainActivity">
<!--我们在原布局的上面添加了两个100dp高的ViewStub-->
<ViewStub
android:layout_width="match_parent"
android:layout_height="100dp"
/>
<ViewStub
android:layout_width="match_parent"
android:layout_height="100dp"
/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="16sp"
android:layout_margin="10dp"
android:layout_gravity="center"
android:text="曾经沧海难为水,"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="16sp"
android:layout_margin="10dp"
android:layout_gravity="center"
android:text="除却巫山不是云。"/>
<Button
android:id="@+id/button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="显示"/>
<ViewStub
android:id="@+id/viewStub"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout="@layout/details"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="16sp"
android:layout_margin="10dp"
android:layout_gravity="center"
android:text="测试"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="16sp"
android:layout_margin="10dp"
android:layout_gravity="center"
android:text="测试"/>
</LinearLayout>
我们在原布局activity_main.xml,“曾经沧海难为水”TextView的上面添加了两个100dp高的ViewStub,看看主界面。
效果图:
我们的“曾经沧海难为水”还是在原来的位置,布局和绘制鸟都不鸟它,老子打烂你的100dp!
那么,ViewStub的layout_width和layout_height有什么用呢?
我们在修改一下activity_main.xml文件,只改ViewStub的高,从wrap_content——>300dp
点击按钮后的效果图:
这次把高度改成50dp。
结论来了:
id就是findViewById的时候,找那个ViewStub用的。
layout=@layout/~~就是,找到ViewStub引用的那个布局,然后插进来。
layout_width和layout_height,就是,只给你这么点地方让你插,不管你多大,也不管你插多少次。
(如果有这样的处女就好了,哈哈。)
2."在需要加载ViewStub中的布局时,可以按照如下两种方式进行:
((ViewStub)findViewById(R.id.mViewStub)).inflate();
((ViewStub)findViewById(R.id.mViewStub)).setVisibility(View.VISIBLE);"
我们理解一下这两个方法。
ViewStub.inflate();
API:Inflates the layout resource identified by getLayoutResource()
and replaces this StubbedView in its parent by the inflated layout resource.
大概意思就是:通过getLayoutResource()方法加载一个布局,然后用这个布局替换这个ViewStub。
ViewStub.setVisibility();
API:When visibility is set to VISIBLE
or INVISIBLE
, inflate()
is invoked and this StubbedView is replaced in its parent by the inflated layout resource.
大概意思是:当visibility被设置为VISIBLE或者INVISIBLE的时候,inflate()就会被唤醒,并且用inflate()方法加载的布局代替ViewStub。
看完之后还是恍恍惚惚,那我们做实验?
之前在findViewById找到ViewStub对象后,用setVisibility()和一个boolean类型的isHideViewStub来控制的,就是这样:
mButton.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { if (isHideViewStub) { mViewStub.setVisibility(View.GONE); isHideViewStub = false; } else { mViewStub.setVisibility(View.VISIBLE); isHideViewStub = true; } } });不是说inflate也可以吗?那么我们试试:
mButton.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { if (isHideViewStub) { mViewStub.setVisibility(View.GONE); isHideViewStub = false; } else { mViewStub.inflate(); isHideViewStub = true; } } });效果图:
什么?报错了!错误类型是:java.lang.IllegalStateException: ViewStub must have a non-null ViewGroup viewParent
非法状态异常:ViewStub必须有一个“不为空的”ViewGroup viewParent。(不会翻译了。)
反正就是什么为空了呗。
就是说,我们之前已经用inflate();方法加载一个布局了,后来只是把它隐藏了,你现在就又要加载一个,犯法了!什么法?安卓法!
就好比你老婆出门了,然后你家里就没老婆了,你就重新娶一个?简直就是犯法,重婚罪!!!!!
上面说到:
ViewStub.setVisibility();
API:When visibility is set to VISIBLE
or INVISIBLE
, inflate()
is invoked and this StubbedView is replaced in its parent by the inflated layout resource.
大概意思是:当visibility被设置为VISIBLE或者INVISIBLE的时候,inflate()就会被唤醒,并且用inflate()方法加载的布局代替ViewStub。
这不是矛盾了吗?那我们上源码?(楼主第一次自己找源码,分析源码,写的不好就别看了,免得辣眼睛。)
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); 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 = 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"); }
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 { super.setVisibility(visibility); if (visibility == VISIBLE || visibility == INVISIBLE) { inflate(); } } }
当我们第一次调用setVisibility的时候(VISIBLE或者INVISIBLE),就会间接调用inflate。
或者我们第一次直接调用inflate。
就会执行
mInflatedViewRef = new WeakReference<View>(view);
然后在调用setVisibility的时候,mInflatedViewRef就不为空啦,通过它可以找到之前加载的那个VIEW,然后设置,看得见,看不见。
也就是说我们之后调用mViewStub.setVisibility()的时候,实际上是执行了view.setVisibility();那个布局已经被当成一个view了。
那么这个view怎么来的?源码中有这么一句:
final View view = factory.inflate(mLayoutResource, parent, false);
就是用inflate加载出来的,这个大家肯定经常用。
解释一下我们的那个异常:
if (viewParent != null && viewParent instanceof ViewGroup) {
} else { throw new IllegalStateException("ViewStub must have a non-null ViewGroup viewParent"); }
这就引出作者任玉刚先生的那句话了:
3.当ViewStub通过setVisible或者inflate方法加载后,ViewStub就会被它内部的布局替换掉,这个时候ViewStub不在是整个布局结构中的一部分了。
viewStub都不存在了,肯定找不到他爸爸viewParent,人家有新儿子了,就你inflate出来的那个,编程的世界就是这么残忍,虎毒食子啊!
不好好学习,新生代分分钟取代你,恩。我就是新生代,哈哈。
ViewStub就好比一个避孕套,用一次就扔了,已经不存在了,恩。
大概就这么多!剩下的自己分析源码去!