Android 控件ViewStub

Android ViewStub

引言:一个可用于性能优化的控件。

时间:2017年09月21日

作者:JustDo23

Github:https://github.com/JustDo23

官网:https://developer.android.com/reference/android/view/ViewStub.html

01. 简介

A ViewStub is an invisible, zero-sized View that can be used to lazily inflate layout resources at runtime.

控件 ViewStub 是一个不可见的,零尺寸的 View 它可以在运行时进行延迟加载。

When a ViewStub is made visible , or when inflate() is invoked, the layout resource is inflated. The ViewStub then replaces itself in its parent with the inflated View or Views.

ViewStubsetVisibility(int) 方法或者 inflate() 方法被调用,它会加载被指定的布局在父布局中将自己替换为加载的布局。

Therefore, the ViewStub exists in the view hierarchy until setVisibility(int) or inflate() is invoked.

替换后,控件 ViewStub 会从布局树移除

The inflated View is added to the ViewStub's parent with the ViewStub's layout parameters.

控件 ViewStub布局属性会传递给被加载的布局。

  • 因此,不是必须显示的布局使用 ViewStub 代替后减少界面首次加载时资源消耗,提升最初加载速度。

02. 用法

  1. 布局

    <ViewStub
        android:id="@+id/viewStub"
        android:layout_width="match_parent"
        android:layout_height="50dp"
        android:layout_marginTop="10dp"
        android:inflatedId="@+id/titleBar"
        android:layout="@layout/just_title" />
    
    • android:layout 指定被加载替换的布局
    • android:inflatedId 指定被加载替换的布局的 id
  2. 加载

    viewStub = (ViewStub) findViewById(R.id.viewStub);
    View inflated = stub.inflate();
    
    • 官方推荐加载首选方法
    • 调用 inflate() 方法后布局被加载替换同时返回布局对象。避免了使用 findViewById() 方法。
    • inflate() 方法只能调用一次,调用被移除而没有了父布局。第二次调用会抛出异常 ViewStub must have a non-null ViewGroup viewParent

03. 示例

  1. 主界面布局

    <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
      android:layout_width="match_parent"
      android:layout_height="match_parent">
    
      <ViewStub
        android:id="@+id/viewStub"
        android:layout_width="match_parent"
        android:layout_height="50dp"
        android:layout_marginTop="10dp"
        android:inflatedId="@+id/titleBar"
        android:layout="@layout/just_title" />
    
      <Button
        android:id="@+id/bt_show"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginTop="80dp"
        android:text="Show"
        android:textAllCaps="false" />
    
      <Button
        android:id="@+id/bt_hide"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_below="@id/bt_show"
        android:layout_marginTop="10dp"
        android:text="Hide"
        android:textAllCaps="false" />
    
    </RelativeLayout>
    
  2. 被替换布局 just_title.xml

    <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
      xmlns:tools="http://schemas.android.com/tools"
      android:layout_width="match_parent"
      android:layout_height="50dp"
      android:background="@color/colorPrimary">
    
      <TextView
        android:id="@+id/tv_show_title"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_centerHorizontal="true"
        android:layout_centerVertical="true"
        android:ellipsize="end"
        android:singleLine="true"
        android:textColor="@android:color/white"
        android:textSize="20sp"
        tools:text="Title" />
    
    </RelativeLayout>
    
  3. 界面加载

    public class ViewStubActivity extends AppCompatActivity implements View.OnClickListener {
    
      private ViewStub viewStub;// 占位控件
      private Button bt_show;// 显示按钮
      private Button bt_hide;// 隐藏按钮
      private TextView tv_show_title;// 标题
    
      @Override
      protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_view_stub);
        viewStub = (ViewStub) findViewById(R.id.viewStub);// 寻找控件
        bt_show = (Button) findViewById(R.id.bt_show);
        bt_hide = (Button) findViewById(R.id.bt_hide);
        bt_show.setOnClickListener(this);
        bt_hide.setOnClickListener(this);
      }
    
      @Override
      public void onClick(View v) {
        switch (v.getId()) {
          case R.id.bt_show:// 显示
            try {
              View titleBar = viewStub.inflate();// 第二次加载会抛出异常
              tv_show_title = (TextView) titleBar.findViewById(R.id.tv_show_title);
              tv_show_title.setText("Title");
            } catch (Exception e) {
              viewStub.setVisibility(View.VISIBLE);
            }
            break;
          case R.id.bt_hide:// 隐藏
            viewStub.setVisibility(View.GONE);
            break;
        }
      }
    
    }
    
  4. 隐藏

    ViewStubHide
  5. 显示

    ViewStubShow
  6. 更换加载方式

    @Override
    public void onClick(View v) {
      switch (v.getId()) {
        case R.id.bt_show:// 显示
          viewStub.setVisibility(View.VISIBLE);// 方式二
          if (tv_show_title == null) {
            tv_show_title = (TextView) findViewById(R.id.tv_show_title);
            tv_show_title.setText("Title");
          }
          break;
      }
    }
    

04. 监听

  1. 加载监听回调

    @Override
    protected void onCreate(Bundle savedInstanceState) {
      super.onCreate(savedInstanceState);
      setContentView(R.layout.activity_view_stub);
      viewStub = (ViewStub) findViewById(R.id.viewStub);// 寻找控件
      // 设置加载监听回调,成功加载后回调[只会回调一次]
      viewStub.setOnInflateListener(new ViewStub.OnInflateListener() {
    
        /**
         * Listener used to receive a notification after a ViewStub has successfully inflated its layout resource.
         *
         * @param stub ViewStub 对象
         * @param inflated 被加载填充的布局
         */
        @Override
        public void onInflate(ViewStub stub, View inflated) {
          tv_show_title = (TextView) inflated.findViewById(R.id.tv_show_title);
          tv_show_title.setText("ShowTitle");
        }
      });
    }
    
  2. 按需使用

05. 源码

  1. setVisibility() 方法

    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();// 调用填充方法
        }
      }
    }
    
  2. inflate() 方法

    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) {// 被指定的布局的 ID
            view.setId(mInflatedId);//  即 android:inflatedId 指定的新 ID
          }
    
          final int index = parent.indexOfChild(this);// 获取位置
          parent.removeViewInLayout(this);// 移除 ViewStub 自己
    
          final ViewGroup.LayoutParams layoutParams = getLayoutParams();// ViewStub 指定的布局参数
          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");
      }
    }
    

06. 注意

  1. ViewStub 支持使用 <include> 标签的布局。
  2. ViewStub 不支持使用 <merge> 标签的布局。

07. 参考

  1. Android--UI之ViewStub
  2. Android中使用ViewStub提高布局性能
  3. Android ViewStub的使用
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,324评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,303评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,192评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,555评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,569评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,566评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,927评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,583评论 0 257
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,827评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,590评论 2 320
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,669评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,365评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,941评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,928评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,159评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,880评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,399评论 2 342

推荐阅读更多精彩内容