基于Android 8.0分析源碼的ViewStub源碼解析
源碼基于安卓8.0分析結(jié)果
ViewStub是一種不可見的并且大小為0的試圖,它可以延遲到運行時才填充inflate 布局資源,當(dāng)Viewstub設(shè)為可見或者是inflate的時候,就會填充布局資源,這個布局和普通的試圖就基本上沒有任何區(qū)別,比如說,加載網(wǎng)絡(luò)失敗,或者是一個比較消耗性能的功能,需要用戶去點擊才可以加載!從而這樣更加的節(jié)約了性能。對安卓布局很友好!
ViewStub用法
- <ViewStub
- android:padding="10dp"
- android:background="@color/colorPrimary"
- android:layout_gravity="center"
- android:inflatedId="@+id/view_stub_inflateid"
- android:id="@+id/view_stub"
- android:layout="@layout/view_stub_imageview"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content" />
這篇文章安卓代碼、圖片、布局、網(wǎng)絡(luò)和電量優(yōu)化說如果這個根布局是個View,比如說是個ImagView,那么找出來的id為null,得必須注意這一點 -----2018.6.7修正這個說法,以前我說的是錯誤的,根本上的原因是ViewStub設(shè)置了 inflateid ,這才是更本身的原因
在這里記住一點,如果在 ViewStub標(biāo)簽中設(shè)置了android:inflatedId="@+id/view_stub_inflateid",在layout布局中的根布局在設(shè)置android:id="@+id/view_stub_layout",這個id永遠找出來都是為null的,原因會在下面說明
- <?xml version="1.0" encoding="utf-8"?>
- <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
- android:padding="10dp"
- android:id="@+id/view_stub_layout"
- android:src="@drawable/ic_launcher_background"
- android:layout_width="match_parent"
- android:layout_height="match_parent">
- <TextView
- android:text="如果這個根布局是個View,比如說是個ImagView,那么找出來的id為null,得必須注意這一點 -----2018.6.7修正這個說法,以前我說的是錯誤的,根本上的原因是ViewStub設(shè)置了 inflateid ,這才是更本身的原因"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content" />
- <ImageView
- android:layout_marginTop="20dp"
- android:id="@+id/imageview"
- android:padding="10dp"
- android:src="@drawable/ic_launcher_background"
- android:layout_width="match_parent"
- android:layout_height="match_parent"/>
- </FrameLayout>
在activity或者是fragment中的使用,mViewStub.getParent()==null就是說明沒有被填充,需要填充,如果填充了,那么它的parent不會為null,具體的騷操作,后續(xù)我介紹View的繪制流程的時候在詳細說明。
第一種使用的方法
- mViewStub = findViewById(R.id.view_stub);
- if (null!=mViewStub.getParent()){
- View inflate = mViewStub.inflate();
- ....
- }
第二種方式:mViewStub.setVisibility(View.VISIBLE);和inflate()方法一樣。
- mViewStub = findViewById(R.id.view_stub);
- if (null!=mViewStub.getParent()){
- mViewStub.setVisibility(View.VISIBLE);
- ....
- }
第三種方式,my_title_parent_id是layout的根布局的id
- mViewStub = findViewById(R.id.view_stub);
- // 成員變量commLv2為空則代表未加載 commLv2 的id為ViewStub中的根布局的id
- View commLv2=findViewById(R.id.my_title_parent_id);
- if ( commLv2 == null ) {
- // 加載評論列表布局, 并且獲取評論ListView,inflate函數(shù)直接返回ListView對象
- commLv2 = (View)mViewStub.inflate();
- } else {
- // ViewStub已經(jīng)加載
- }
ViewStub構(gòu)造方法,注意獲取了幾個值mInflatedId就是android:inflatedId="@+id/find_view_stub"這個值, mLayoutResource就是layout的resId,ViewStub的 mIDid。可以看出ViewStub是View的子類.
- public final class ViewStub extends View {
- public ViewStub(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
- super(context);
- final TypedArray a = context.obtainStyledAttributes(attrs,
- R.styleable.ViewStub, defStyleAttr, defStyleRes);
- // TODO: 2018/5/23 ViewStub 中設(shè)置的標(biāo)簽id 如果設(shè)置了 這里就一定有值 mInflatedId!=NO_Id
- 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);
- // 設(shè)置不繪制
- setWillNotDraw(true);
- }
- }
在構(gòu)造方法中:同時注意不可見 setVisibility(GONE); ,設(shè)置不繪制setWillNotDraw(true);,同時通過下面的方法看出,ViewStub 是一個大小為0的視圖。
- @Override
- protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
- // 寬高都為0 onMeasure的時候 寬高都為0
- setMeasuredDimension(0, 0);
- }
- //todo 為啥這個控件 是個大小為0的控件 ,那是因為他媽的這里更不就沒有畫
- @Override
- public void draw(Canvas canvas) {
- }
- @Override
- protected void dispatchDraw(Canvas canvas) {
- }
關(guān)于inflate()方法
- public View inflate() {
- // 1、獲取ViewStub的parent view,也是目標(biāo)布局根元素的parent view
- final ViewParent viewParent = getParent();
- if (viewParent != null && viewParent instanceof ViewGroup) {
- if (mLayoutResource != 0) {
- final ViewGroup parent = (ViewGroup) viewParent;
- /// 2、加載目標(biāo)布局 牛逼的方法
- final View view = inflateViewNoAdd(parent);
- // 3、將ViewStub自身從parent中移除
- replaceSelfWithView(view, parent);
- mInflatedViewRef = new WeakReference<>(view);
- if (mInflateListener != null) {
- mInflateListener.onInflate(this, view);
- }
- return view;
- } else {
- // TODO: 2018/5/23 必須設(shè)置布局的文件
- throw new IllegalArgumentException("ViewStub must have a valid layoutResource");
- }
- } else {
- // TODO: 2018/5/23 iewParent instanceof ViewGroup 不屬于的話,就好比在一個TextView創(chuàng)建一個ViewStub直接爆炸
- throw new IllegalStateException("ViewStub must have a non-null ViewGroup viewParent");
- }
- }
- 第一點,ViewStup也只能在ViewGroup中使用,不能在View中去使用,要不然會拋出異常IllegalStateException("ViewStub must have a non-null ViewGroup viewParent");
- 第二點,也必須設(shè)置layout屬性,要不然也會拋出異常throw new IllegalArgumentException("ViewStub must have a valid layoutResource");;
關(guān)于方法inflateViewNoAdd(parent);
- private View inflateViewNoAdd(ViewGroup parent) {
- final LayoutInflater factory;
- if (mInflater != null) {
- factory = mInflater;
- } else {
- factory = LayoutInflater.from(mContext);
- }
- final View view = factory.inflate(mLayoutResource, parent, false);
- //和 LayoutInflater一個道理,設(shè)置了,ViewStub 引用進來的根布局的id找出來為null 非常有些意思
- if (mInflatedId != NO_ID) {
- view.setId(mInflatedId);
- }
- return view;
- }
- 第一點,底層調(diào)用的還是LayoutInflater.from(mContext).inflate(mLayoutResource, parent, false);
- 第二點,又看到這個方法,似曾相識,對,這也是為什么ViewStub找不到根布局id的原因,因為mInflatedId != NO_ID,就會view.setId(mInflatedId);
- if (mInflatedId != NO_ID) {
- view.setId(mInflatedId);
- }
將ViewStub自身從parent中移除replaceSelfWithView(view, parent);,具體的原因,這里不做分析,因為有點小復(fù)雜,這里就大概明白就行,對于理解這個ViewStub不困難,哈哈
- private void replaceSelfWithView(View view, ViewGroup parent) {
- final int index = parent.indexOfChild(this);
- // 3、將ViewStub自身從parent中移除
- parent.removeViewInLayout(this);
- final ViewGroup.LayoutParams layoutParams = getLayoutParams();
- if (layoutParams != null) {
- // 4、將目標(biāo)布局的根元素添加到parent中,有參數(shù)
- parent.addView(view, index, layoutParams);
- } else {
- // 5、將目標(biāo)布局的根元素添加到parent中
- parent.addView(view, index);
- }
- }
這里使用到了弱引用,只有弱引用指向的對象的生命周期更短,當(dāng)垃圾回收器掃描到只有具有弱引用的對象的時候,不論當(dāng)前空間是否不足,都會對弱引用對象進行回收,當(dāng)然弱引用也可以和一個隊列配合著使用,為了更好地釋放內(nèi)存,安卓代碼、圖片、布局、網(wǎng)絡(luò)和電量優(yōu)化這篇文章有很好的解釋,而且這個mInflatedViewRef只在這里初始化,如果說沒有調(diào)用inflate的方法的話,這個對象一定為null;
- //更好的釋放內(nèi)存
- private WeakReference<View> mInflatedViewRef;
- mInflatedViewRef = new WeakReference<>(view);
- if (mInflateListener != null) {
- mInflateListener.onInflate(this, view)
- }
為啥setVisibility(View.VISIBLE)等同于inflate,原因是ViewStub進行了重寫??梢钥闯龃a的邏輯,只要沒有調(diào)用過,inflate()方法,setVisibility(VISIBLE )和setVisibility(INVISIBLE)這個兩個參數(shù)走的方法一樣,只不過,一個看不到,實際上的位置已經(jīng)確定了(INVISIBLE)。但是如果調(diào)用多次的話setVisibility()記得也得判斷下null!=mViewStub.getParent()
- @Override
- @android.view.RemotableViewMethod(asyncImpl = "setVisibilityAsync")
- public void setVisibility(int visibility) {
- // TODO: 2018/5/23 弱引用的使用
- //如果已經(jīng)加載過則只設(shè)置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 {
- // 如果未加載,這加載目標(biāo)布局
- super.setVisibility(visibility);
- if (visibility == VISIBLE || visibility == INVISIBLE) {
- inflate();// 調(diào)用inflate來加載目標(biāo)布局
- }
- }
- }
貼出全部的代碼,有空的話,可以研究下。
- @RemoteView
- public final class ViewStub extends View {
- private int mInflatedId;
- private int mLayoutResource;
- // TODO: 2018/5/23 弱引用:弱引用是比軟引用更弱的一種的引用的類型,
- // 只有弱引用指向的對象的生命周期更短,當(dāng)垃圾回收器掃描到只有具有弱引用的對象的時候,
- // 不敢當(dāng)前空間是否不足,都會對弱引用對象進行回收,當(dāng)然弱引用也可以和一個隊列配合著使用
- //更好的釋放內(nèi)存
- private WeakReference<View> mInflatedViewRef;
- private LayoutInflater mInflater;
- private OnInflateListener mInflateListener;
- public ViewStub(Context context) {
- this(context, 0);
- }
- /**
- * Creates a new ViewStub with the specified layout resource.
- *
- * @param context The application's environment.
- * @param layoutResource The reference to a layout resource that will be inflated.
- */
- public ViewStub(Context context, @LayoutRes int layoutResource) {
- this(context, null);
- mLayoutResource = layoutResource;
- }
- public ViewStub(Context context, AttributeSet attrs) {
- this(context, attrs, 0);
- }
- public ViewStub(Context context, AttributeSet attrs, int defStyleAttr) {
- this(context, attrs, defStyleAttr, 0);
- }
- public ViewStub(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
- super(context);
- final TypedArray a = context.obtainStyledAttributes(attrs,
- R.styleable.ViewStub, defStyleAttr, defStyleRes);
- // TODO: 2018/5/23 ViewStub 中設(shè)置的標(biāo)簽id 如果設(shè)置了 這里就一定有值 mInflatedId!=NO_Id
- 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);
- // 設(shè)置不繪制
- setWillNotDraw(true);
- }
- /**
- * Returns the id taken by the inflated view. If the inflated id is
- * {@link View#NO_ID}, the inflated view keeps its original id.
- *
- * @return A positive integer used to identify the inflated view or
- * {@link #NO_ID} if the inflated view should keep its id.
- *
- * @see #setInflatedId(int)
- * @attr ref android.R.styleable#ViewStub_inflatedId
- */
- @IdRes
- public int getInflatedId() {
- return mInflatedId;
- }
- /**
- * Defines the id taken by the inflated view. If the inflated id is
- * {@link View#NO_ID}, the inflated view keeps its original id.
- *
- * @param inflatedId A positive integer used to identify the inflated view or
- * {@link #NO_ID} if the inflated view should keep its id.
- *
- * @see #getInflatedId()
- * @attr ref android.R.styleable#ViewStub_inflatedId
- */
- @android.view.RemotableViewMethod(asyncImpl = "setInflatedIdAsync")
- public void setInflatedId(@IdRes int inflatedId) {
- mInflatedId = inflatedId;
- }
- /** @hide **/
- public Runnable setInflatedIdAsync(@IdRes int inflatedId) {
- mInflatedId = inflatedId;
- return null;
- }
- /**
- * Returns the layout resource that will be used by {@link #setVisibility(int)} or
- * {@link #inflate()} to replace this StubbedView
- * in its parent by another view.
- *
- * @return The layout resource identifier used to inflate the new View.
- *
- * @see #setLayoutResource(int)
- * @see #setVisibility(int)
- * @see #inflate()
- * @attr ref android.R.styleable#ViewStub_layout
- */
- @LayoutRes
- public int getLayoutResource() {
- return mLayoutResource;
- }
- /**
- * Specifies the layout resource to inflate when this StubbedView becomes visible or invisible
- * or when {@link #inflate()} is invoked. The View created by inflating the layout resource is
- * used to replace this StubbedView in its parent.
- *
- * @param layoutResource A valid layout resource identifier (different from 0.)
- *
- * @see #getLayoutResource()
- * @see #setVisibility(int)
- * @see #inflate()
- * @attr ref android.R.styleable#ViewStub_layout
- */
- @android.view.RemotableViewMethod(asyncImpl = "setLayoutResourceAsync")
- public void setLayoutResource(@LayoutRes int layoutResource) {
- mLayoutResource = layoutResource;
- }
- /** @hide **/
- public Runnable setLayoutResourceAsync(@LayoutRes int layoutResource) {
- mLayoutResource = layoutResource;
- return null;
- }
- /**
- * Set {@link LayoutInflater} to use in {@link #inflate()}, or {@code null}
- * to use the default.
- */
- public void setLayoutInflater(LayoutInflater inflater) {
- mInflater = inflater;
- }
- /**
- * Get current {@link LayoutInflater} used in {@link #inflate()}.
- */
- public LayoutInflater getLayoutInflater() {
- return mInflater;
- }
- @Override
- protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
- // 寬高都為0 onMeasure的時候 寬高都為0
- setMeasuredDimension(0, 0);
- }
- //todo 為啥這個控件 是個大小為0的控件 ,那是因為他媽的這里更不就沒有畫
- @Override
- public void draw(Canvas canvas) {
- }
- @Override
- protected void dispatchDraw(Canvas canvas) {
- }
- /**
- * When visibility is set to {@link #VISIBLE} or {@link #INVISIBLE},
- * {@link #inflate()} is invoked and this StubbedView is replaced in its parent
- * by the inflated layout resource. After that calls to this function are passed
- * through to the inflated view.
- *
- * @param visibility One of {@link #VISIBLE}, {@link #INVISIBLE}, or {@link #GONE}.
- *
- * @see #inflate()
- */
- @Override
- @android.view.RemotableViewMethod(asyncImpl = "setVisibilityAsync")
- public void setVisibility(int visibility) {
- // TODO: 2018/5/23 弱引用的使用
- //如果已經(jīng)加載過則只設(shè)置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 {
- // 如果未加載,這加載目標(biāo)布局
- super.setVisibility(visibility);
- if (visibility == VISIBLE || visibility == INVISIBLE) {
- inflate();// 調(diào)用inflate來加載目標(biāo)布局
- }
- }
- }
- /** @hide **/
- public Runnable setVisibilityAsync(int visibility) {
- if (visibility == VISIBLE || visibility == INVISIBLE) {
- ViewGroup parent = (ViewGroup) getParent();
- return new ViewReplaceRunnable(inflateViewNoAdd(parent));
- } else {
- return null;
- }
- }
- private View inflateViewNoAdd(ViewGroup parent) {
- final LayoutInflater factory;
- if (mInflater != null) {
- factory = mInflater;
- } else {
- factory = LayoutInflater.from(mContext);
- }
- final View view = factory.inflate(mLayoutResource, parent, false);
- //和 LayoutInflater一個道理,設(shè)置了,ViewStub 引用進來的根布局的id找出來為null 非常有些意思
- if (mInflatedId != NO_ID) {
- view.setId(mInflatedId);
- }
- return view;
- }
- // TODO: 2018/5/23 關(guān)注他
- private void replaceSelfWithView(View view, ViewGroup parent) {
- final int index = parent.indexOfChild(this);
- // 3、將ViewStub自身從parent中移除
- parent.removeViewInLayout(this);
- final ViewGroup.LayoutParams layoutParams = getLayoutParams();
- if (layoutParams != null) {
- // 4、將目標(biāo)布局的根元素添加到parent中,有參數(shù)
- parent.addView(view, index, layoutParams);
- } else {
- // 5、將目標(biāo)布局的根元素添加到parent中
- parent.addView(view, index);
- }
- }
- /**
- * Inflates the layout resource identified by {@link #getLayoutResource()}
- * and replaces this StubbedView in its parent by the inflated layout resource.
- *
- * @return The inflated layout resource.
- *
- */
- public View inflate() {
- // 1、獲取ViewStub的parent view,也是目標(biāo)布局根元素的parent view
- final ViewParent viewParent = getParent();
- if (viewParent != null && viewParent instanceof ViewGroup) {
- if (mLayoutResource != 0) {
- final ViewGroup parent = (ViewGroup) viewParent;
- /// 2、加載目標(biāo)布局 牛逼的方法
- final View view = inflateViewNoAdd(parent);
- // 3、將ViewStub自身從parent中移除
- replaceSelfWithView(view, parent);
- mInflatedViewRef = new WeakReference<>(view);
- if (mInflateListener != null) {
- mInflateListener.onInflate(this, view);
- }
- return view;
- } else {
- // TODO: 2018/5/23 必須設(shè)置布局的文件
- throw new IllegalArgumentException("ViewStub must have a valid layoutResource");
- }
- } else {
- // TODO: 2018/5/23 iewParent instanceof ViewGroup 不屬于的話,就好比在一個TextView創(chuàng)建一個ViewStub直接爆炸
- throw new IllegalStateException("ViewStub must have a non-null ViewGroup viewParent");
- }
- }
- /**
- * Specifies the inflate listener to be notified after this ViewStub successfully
- * inflated its layout resource.
- *
- * @param inflateListener The OnInflateListener to notify of successful inflation.
- *
- * @see ViewStub.OnInflateListener
- */
- public void setOnInflateListener(OnInflateListener inflateListener) {
- mInflateListener = inflateListener;
- }
- /**
- * Listener used to receive a notification after a ViewStub has successfully
- * inflated its layout resource.
- *
- * @see ViewStub#setOnInflateListener(ViewStub.OnInflateListener)
- */
- public static interface OnInflateListener {
- /**
- * Invoked after a ViewStub successfully inflated its layout resource.
- * This method is invoked after the inflated view was added to the
- * hierarchy but before the layout pass.
- *
- * @param stub The ViewStub that initiated the inflation.
- * @param inflated The inflated View.
- */
- void onInflate(ViewStub stub, View inflated);
- }
- /** @hide **/
- public class ViewReplaceRunnable implements Runnable {
- public final View view;
- ViewReplaceRunnable(View view) {
- this.view = view;
- }
- @Override
- public void run() {
- replaceSelfWithView(view, (ViewGroup) getParent());
- }
- }
- }
最后做了一張圖
說明一下ViewStub的原理很簡單!好吧,這個有點皮