【Code-Snippet】ViewStub 太过爱你忘了你带给我的痛 2022-01-20 15:51 191阅读 0赞 ## 1. 特性 ## * ViewStub 是一个不可见,size为0的View,它通常用于在适当的时机去懒加载布局。 * 一旦 ViewStub 设置为 Visible 或者 调用了 inflate() 方法,ViewStub 的布局就会被加载。 * ViewStub 的布局在加载后会直接替换它自己,所以 ViewStub 存在于 View hierarchy 直到它调用了 **setVisiblity()** 或者 **inflate()**。 * ViewStub 设置的参数会直接传递给自身的布局。 ## 2. 示例 ## <ViewStub android:id="@+id/view_stub" android:layout_margin="10dp" android:inflatedId="@+id/tv_title" android:layout="@layout/tv_title_layout" android:layout_width="220dp" android:layout_height="50dp" /> 复制代码 * 如上面,我们可以使用 **id/view\_stub** 去寻找到 ViewStb, ViewStub在加载布局后,ViewStub 本身消失了,其 **android:inflatedId="@+id/tv\_title"** 指定的就是布局的ID,通过这个ID就可以找到加载后的布局。 * 最后这个布局的大小就是继承于 ViewStub 的 120dp 40dp。 * 还有,**android:inflatedId="@+id/tv\_title"** 这里指定的ID必须要和 **android:layout="@layout/tv\_title\_layout"** 里面的ID对应起来。 <?xml version="1.0" encoding="utf-8"?> <TextView xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/tv_title" android:text="I'm inflate view." android:textColor="#000" android:textSize="15sp" android:layout_width="match_parent" android:layout_height="match_parent"> </TextView> 复制代码 虽然,layout里面布局写的 width 和 height 为 **match\_parent**,但是不生效,只会继承于 viewstub 指定的大小。 我们在代码里面就可以这样写: ViewStub viewStub = (ViewStub) findViewById(R.id.view_stub); if (viewStub!=null) { viewStub.inflate(); } TextView tvTitle = (TextView) findViewById(R.id.tv_title); tvTitle.setText("I'm inflate: " + System.currentTimeMillis()); 复制代码 ## 3. 源码分析 ## #### 变量 #### private int mInflatedId;//viewstub传递给布局的id private int mLayoutResource;//viewstub的布局 private WeakReference<View> mInflatedViewRef; 复制代码 这里有一个弱引用,是用来干嘛的呢?我们先不管,往下看。 #### 构造函数 #### public ViewStub(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { super(context); final TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.ViewStub, defStyleAttr, defStyleRes); mInflatedId = a.getResourceId(R.styleable.ViewStub_inflatedId, NO_ID); mLayoutResource = a.getResourceId(R.styleable.ViewStub_layout, 0); mID = a.getResourceId(R.styleable.ViewStub_id, NO_ID); a.recycle(); setVisibility(GONE); setWillNotDraw(true); } 复制代码 很普通,直接从xml布局中读取相应的属性,并且在构造函数中就设置为 GONE,接着:**setWillNotDraw(true)**,设置一个标记,声明这个View不做 onDraw 绘制。 /** * If this view doesn't do any drawing on its own, set this flag to * allow further optimizations. By default, this flag is not set on * View, but could be set on some View subclasses such as ViewGroup. * * Typically, if you override {@link #onDraw(android.graphics.Canvas)} * you should clear this flag. * * @param willNotDraw whether or not this View draw on its own */ public void setWillNotDraw(boolean willNotDraw) { setFlags(willNotDraw ? WILL_NOT_DRAW : 0, DRAW_MASK); } 复制代码 #### onMeasure,draw #### @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { setMeasuredDimension(0, 0); } @Override public void draw(Canvas canvas) { } 复制代码 measure直接传入0,说明ViewStub初始化就是一个0大小的View。 #### setVisibility & inflate #### 从上面知道,inflate 或者 setVisibility 都可以加载布局,我们先看: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(); } } } 复制代码 先判断弱引用里面是否有View,有的话就直接设置为可见,为空,就执行 inflate(): public View inflate() { final ViewParent viewParent = getParent(); if (viewParent != null && viewParent instanceof ViewGroup) { if (mLayoutResource != 0) { final ViewGroup parent = (ViewGroup) viewParent; //mInflater 可以从外部传进来 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); } //获取当前ViewStub在ViewGroup中的index final int index = parent.indexOfChild(this); //ViewStub替换成inflate后的View 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); //inflate回调 if (mInflateListener != null) { mInflateListener.onInflate(this, view); } return view; } //省略异常 } //省略异常 } 复制代码 * 先获取到父容器,然后判断布局不为空情况下,进行加载布局 * 使用的mInflate可以从外部通过方法 **public void setLayoutInflater(LayoutInflater inflater)** 进来 * 加载布局后设置好布局的ID * 接着获取到当前 ViewStub 的位置 index,remove viewStub,add 新inflate 的 View 到 Index 位置上,也就是替换原来的 ViewStub * 初始化弱引用,从这里看到,弱引用包裹的是加载的 View * 回调器回调 #### 回调 #### public static interface OnInflateListener { void onInflate(ViewStub stub, View inflated); } 复制代码 在 inflate 的时候会进行回调 转载于:https://juejin.im/post/5c91b56af265da612647b45c
还没有评论,来说两句吧...