【Code-Snippet】ViewStub

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 指定的大小。android

咱們在代碼裏面就能夠這樣寫:canvas

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;
複製代碼

這裏有一個弱引用,是用來幹嗎的呢?咱們先無論,往下看。bash

構造函數

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 繪製。ide

/**
 * 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 的時候會進行回調ui

相關文章
相關標籤/搜索