RTFSC-ViewStub

作者: 二毛_coder | 来源:发表于2019-05-21 18:20 被阅读3次

    前言

    ViewStub控件,惰性装载控件。在介绍ViewStub之前,可以先了解一下<include/>标签,这是一个把其它布局资源包含进某个特定的布局中。可以和ViewStub做一个对照。区别在于使用include的方式,在初始化时就会加载进入布局,而ViewStub作为惰性装载控件,只有在被inflate或者设置为可见时,布局内容才会被加载。形成一种惰性装载的效果,可以在初始化时对不必要显示的资源做一个惰性装载,达到优化初始化的效果

    示例

     <!--activity_main.xml-->
    <?xml version="1.0" encoding="utf-8"?>
    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:tools="http://schemas.android.com/tools"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:gravity="center_horizontal"
        android:orientation="horizontal"
        tools:context=".MainActivity">
    
        <ViewStub
            android:id="@+id/viewStud"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:inflatedId="@+id/subTree"
            android:layout="@layout/text_layout" />
    </LinearLayout>
    

    text_layout就是需要惰性加载的布局,

     <!--text_layout.xml-->
    <?xml version="1.0" encoding="utf-8"?>
    <FrameLayout
        xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent"
        android:layout_height="match_parent">
    <TextView
        android:id="@+id/tv_show"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:text="view_stub"/>
    </FrameLayout>
    

    如何使用?

    //MainActivity .java
    public class MainActivity extends AppCompatActivity {
        ViewStub viewStub;
        private static final String TAG = "MainActivity";
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
            viewStub = findViewById(R.id.viewStud);
            viewStub.setOnInflateListener(new ViewStub.OnInflateListener() {
                @Override
                public void onInflate(ViewStub stub, View inflated) {
                    Log.i(TAG, "onInflate: ");
                    TextView textView = inflated.findViewById(R.id.tv_show);
                    textView.setText("chang text content");
                }
            });
            viewStub.inflate();
        }
    }
    

    使用方法的代码很简单,就是让viewstub做一个站位的操作,将需要懒加载的布局传递给viewstub控件(通过viewstub的layout属性),在代码中找到viewstub实例,可以给它设置一个inflate的监听,在真正调用inflate()方法或者设置可见的时候会监听到对应的懒加载view。

    源码分析

    (PS:ViewStub.java文件总共也就300多行,这可能是最好分析的Android SDK源码了吧!既然这样,代码全贴出来也不过分 ^.^ )

    public final class ViewStub extends View {
        private int mInflatedId;
        private int mLayoutResource;
    
        private WeakReference<View> mInflatedViewRef;
    
        private LayoutInflater mInflater;
        private OnInflateListener mInflateListener;
    
        public ViewStub(Context context) {
            this(context, 0);
        }
    
        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);
            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);
        }
        @IdRes
        public int getInflatedId() {
            return mInflatedId;
        }
        @android.view.RemotableViewMethod(asyncImpl = "setInflatedIdAsync")
        public void setInflatedId(@IdRes int inflatedId) {
            mInflatedId = inflatedId;
        }
    
        /** @hide **/
        public Runnable setInflatedIdAsync(@IdRes int inflatedId) {
            mInflatedId = inflatedId;
            return null;
        }
    
        @LayoutRes
        public int getLayoutResource() {
            return mLayoutResource;
        }
        @android.view.RemotableViewMethod(asyncImpl = "setLayoutResourceAsync")
        public void setLayoutResource(@LayoutRes int layoutResource) {
            mLayoutResource = layoutResource;
        }
    
        /** @hide **/
        public Runnable setLayoutResourceAsync(@LayoutRes int layoutResource) {
            mLayoutResource = layoutResource;
            return null;
        }
    
        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) {
            setMeasuredDimension(0, 0);
        }
    
        @Override
        public void draw(Canvas canvas) {
        }
    
        @Override
        protected void dispatchDraw(Canvas canvas) {
        }
    
        @Override
        @android.view.RemotableViewMethod(asyncImpl = "setVisibilityAsync")
        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();
                }
            }
        }
    
        /** @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);
    
            if (mInflatedId != NO_ID) {
                view.setId(mInflatedId);
            }
            return view;
        }
    
        private void replaceSelfWithView(View view, ViewGroup parent) {
            final int index = parent.indexOfChild(this);
            parent.removeViewInLayout(this);
    
            final ViewGroup.LayoutParams layoutParams = getLayoutParams();
            if (layoutParams != null) {
                parent.addView(view, index, layoutParams);
            } else {
                parent.addView(view, index);
            }
        }
    
        public View inflate() {
            final ViewParent viewParent = getParent();
    
            if (viewParent != null && viewParent instanceof ViewGroup) {
                if (mLayoutResource != 0) {
                    final ViewGroup parent = (ViewGroup) viewParent;
                    final View view = inflateViewNoAdd(parent);
                    replaceSelfWithView(view, parent);
    
                    mInflatedViewRef = new WeakReference<>(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");
            }
        }
        public void setOnInflateListener(OnInflateListener inflateListener) {
            mInflateListener = inflateListener;
        }
    
        public static interface OnInflateListener {
            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也是继承自View,所以可以成为xml布局中的一个站位控件,除了ViewStub外,里面还包含了一个接口OnInflateListener 和一个ViewReplaceRunnable类,OnInflateListener 用于监听ViewStub加载布局时的inflate监听。ViewReplaceRunnable是一个异步加载布局的Runnable.在调用setVisibilityAsync()方法时会返回runnable对象。用于异步加载控件。
    mInflatedId:需要加载的布局id,
    mLayoutResource:需要加载的布局
    mInflatedViewRef:对需要加载的布局view的一个弱引用的持有
    我们主要关注一下ViewStub的三个方法

    onMeasure(int widthMeasureSpec, int heightMeasureSpec)

    onMeasure方法,只有一句代码setMeasuredDimension(0, 0);所以说,不管ViewStub设置多大,在初始化时都是一个没有大小的(A ViewStub is an invisible, zero-sized View)。至于不可见,可以在初始化里面看到,默认是设置了setVisibility(GONE);

    setVisibility(int visibility)

     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()方法中去

    inflate()

    public View inflate() {
            final ViewParent viewParent = getParent();
    
            if (viewParent != null && viewParent instanceof ViewGroup) {
                if (mLayoutResource != 0) {
                    final ViewGroup parent = (ViewGroup) viewParent;
                    final View view = inflateViewNoAdd(parent);
                    replaceSelfWithView(view, parent);
    
                    mInflatedViewRef = new WeakReference<>(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");
            }
        }
    

    在inflate方法中首先会调用inflateViewNoAdd方法

    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);
    
            if (mInflatedId != NO_ID) {
                view.setId(mInflatedId);
            }
            return view;
        }
    

    inflateViewNoAdd方法就是通过factory加载设置的视图mLayoutResource,并返回view对象。然后会调用replaceSelfWithView(view, parent)方法;

    private void replaceSelfWithView(View view, ViewGroup parent) {
            final int index = parent.indexOfChild(this);
            parent.removeViewInLayout(this);
    
            final ViewGroup.LayoutParams layoutParams = getLayoutParams();
            if (layoutParams != null) {
                parent.addView(view, index, layoutParams);
            } else {
                parent.addView(view, index);
            }
        }
    

    将inflateViewNoAdd方法返回的view对象加入到ViewStub的父view上,完成ViewStub占位的替换。最后

    mInflatedViewRef = new WeakReference<>(view);
                    if (mInflateListener != null) {
                        mInflateListener.onInflate(this, view);
                    }
    

    将加载好的view放在弱引用中持有,用于后面的显示隐藏对view的重新操作,同时回调onInflate。

    其中还有一个重要的setVisibilityAsync(int visibility)方法,同样会通过LayoutInflater把mLayoutResource转换成view对象,此方法和setVisibility(int visibility)唯一的区别就时,在于它会返回一个Runnable对象,然后可以在想要加载的时候将view加载到界面中。

    以上,分析完毕!如有错误,欢迎指出~

    相关文章

      网友评论

        本文标题:RTFSC-ViewStub

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