Android 控件ViewStub

作者: JustDo23 | 来源:发表于2017-09-21 15:10 被阅读168次
    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的使用

    相关文章

      网友评论

      本文标题:Android 控件ViewStub

      本文链接:https://www.haomeiwen.com/subject/ilozsxtx.html