Android源码分析(ViewStub源码解析)

源码基于安卓8.0分析结果

  • ViewStub是一种不可见的并且大小为0的试图,它可以延迟到运行时才填充inflate 布局资源,当Viewstub设为可见或者是inflate的时候,就会填充布局资源,这个布局和普通的试图就基本上没有任何区别,比如说,加载网络失败,或者是一个比较消耗性能的功能,需要用户去点击才可以加载!从而这样更加的节约了性能。对安卓布局很友好!
  • 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" />
  • 这篇文章安卓代码、图片、布局、网络和电量优化说如果这个根布局是个View,比如说是个ImagView,那么找出来的id为null,得必须注意这一点 -----2018.6.7修正这个说法,以前我说的是错误的,根本上的原因是ViewStub设置了 inflateid ,这才是更本身的原因
  • 在这里记住一点,如果在 ViewStub标签中设置了android:inflatedId="@+id/view_stub_inflateid",在layout布局中的根布局在设置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设置了 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,具体的骚操作,后续我介绍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_idlayout的根布局的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函数直接返回ListView对象
        commLv2 = (View)mViewStub.inflate();
      } else {
         // ViewStub已经加载
     }
  • ViewStub构造方法,注意获取了几个值mInflatedId就是android:inflatedId="@+id/find_view_stub"这个值, mLayoutResource就是layout的resId,ViewStub的 mIDid。可以看出ViewStubView的子类.
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 中设置的标签id 如果设置了 这里就一定有值 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);
        // 设置不绘制
        setWillNotDraw(true);
    }
}
  • 在构造方法中:同时注意不可见 setVisibility(GONE); ,设置不绘制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) {
    }
  • 关于inflate()方法
  public View inflate() {
        // 1、获取ViewStub的parent view,也是目标布局根元素的parent view
        final ViewParent viewParent = getParent();

        if (viewParent != null && viewParent instanceof ViewGroup) {
            if (mLayoutResource != 0) {
                final ViewGroup parent = (ViewGroup) viewParent;
                /// 2、加载目标布局  牛逼的方法
                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 必须设置布局的文件
                throw new IllegalArgumentException("ViewStub must have a valid layoutResource");
            }
        } else {
            // TODO: 2018/5/23 iewParent instanceof ViewGroup 不属于的话,就好比在一个TextView创建一个ViewStub直接爆炸
            throw new IllegalStateException("ViewStub must have a non-null ViewGroup viewParent");
        }
    }
  • 第一点,ViewStup也只能在ViewGroup中使用,不能在View中去使用,要不然会抛出异常IllegalStateException("ViewStub must have a non-null ViewGroup viewParent");
  • 第二点,也必须设置layout属性,要不然也会抛出异常throw new IllegalArgumentException("ViewStub must have a valid layoutResource");;
  • 关于方法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一个道理,设置了,ViewStub 引用进来的根布局的id找出来为null  非常有些意思
        if (mInflatedId != NO_ID) {
            view.setId(mInflatedId);
        }
        return view;
    }
   if (mInflatedId != NO_ID) {
         view.setId(mInflatedId);
 }
  • 将ViewStub自身从parent中移除replaceSelfWithView(view, parent);,具体的原因,这里不做分析,因为有点小复杂,这里就大概明白就行,对于理解这个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、将目标布局的根元素添加到parent中,有参数
            parent.addView(view, index, layoutParams);
        } else {
           // 5、将目标布局的根元素添加到parent中
            parent.addView(view, index);
        }
    }

  • 这里使用到了弱引用,只有弱引用指向的对象的生命周期更短,当垃圾回收器扫描到只有具有弱引用的对象的时候,不论当前空间是否不足,都会对弱引用对象进行回收,当然弱引用也可以和一个队列配合着使用,为了更好地释放内存,安卓代码、图片、布局、网络和电量优化这篇文章有很好的解释,而且这个mInflatedViewRef只在这里初始化,如果说没有调用inflate的方法的话,这个对象一定为null;
 //更好的释放内存
  private WeakReference<View> mInflatedViewRef;
  mInflatedViewRef = new WeakReference<>(view);
                if (mInflateListener != null) {
               mInflateListener.onInflate(this, view)
  }
  • 为啥setVisibility(View.VISIBLE)等同于inflate,原因是ViewStub进行了重写。可以看出代码的逻辑,只要没有调用过,inflate()方法,setVisibility(VISIBLE )setVisibility(INVISIBLE)这个两个参数走的方法一样,只不过,一个看不到,实际上的位置已经确定了(INVISIBLE)。但是如果调用多次的话setVisibility()记得也得判断下null!=mViewStub.getParent()
@Override
    @android.view.RemotableViewMethod(asyncImpl = "setVisibilityAsync")
    public void setVisibility(int visibility) {
        // TODO: 2018/5/23  弱引用的使用
        //如果已经加载过则只设置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();// 调用inflate来加载目标布局
            }
        }
    }
  • 贴出全部的代码,有空的话,可以研究下。
@RemoteView
public final class ViewStub extends View {
    private int mInflatedId;
    private int mLayoutResource;
    // TODO: 2018/5/23 弱引用:弱引用是比软引用更弱的一种的引用的类型,
    // 只有弱引用指向的对象的生命周期更短,当垃圾回收器扫描到只有具有弱引用的对象的时候,
    // 不敢当前空间是否不足,都会对弱引用对象进行回收,当然弱引用也可以和一个队列配合着使用

    //更好的释放内存
    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 中设置的标签id 如果设置了 这里就一定有值 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);
        // 设置不绘制
        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  弱引用的使用
        //如果已经加载过则只设置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();// 调用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);
        //和 LayoutInflater一个道理,设置了,ViewStub 引用进来的根布局的id找出来为null  非常有些意思
        if (mInflatedId != NO_ID) {
            view.setId(mInflatedId);
        }
        return view;
    }

    // TODO: 2018/5/23 关注他
    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、将目标布局的根元素添加到parent中,有参数
            parent.addView(view, index, layoutParams);
        } else {
           // 5、将目标布局的根元素添加到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,也是目标布局根元素的parent view
        final ViewParent viewParent = getParent();

        if (viewParent != null && viewParent instanceof ViewGroup) {
            if (mLayoutResource != 0) {
                final ViewGroup parent = (ViewGroup) viewParent;
                /// 2、加载目标布局  牛逼的方法
                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 必须设置布局的文件
                throw new IllegalArgumentException("ViewStub must have a valid layoutResource");
            }
        } else {
            // TODO: 2018/5/23 iewParent instanceof ViewGroup 不属于的话,就好比在一个TextView创建一个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源码解析.jpg
  • 说明一下
    • ViewStub的原理很简单!好吧,这个有点皮
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 214,837评论 6 496
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,551评论 3 389
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 160,417评论 0 350
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,448评论 1 288
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,524评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,554评论 1 293
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,569评论 3 414
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,316评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,766评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,077评论 2 330
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,240评论 1 343
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,912评论 5 338
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,560评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,176评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,425评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,114评论 2 366
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,114评论 2 352

推荐阅读更多精彩内容