Android 简单解读了ViewStub的代码和注释,其他的不想多说

ViewStub应用的场景比较少,以前用过几次。时间一长又忘记了,很是苦恼。

于是把ViewStub的源码过了一遍,详细说明了每行代码的含义和目的,翻译了源码中的注释。

自己动眼看看吧,你懂得。再多说就画蛇添足了。

道士,请:


package android.view;

import android.annotation.IdRes;
import android.annotation.LayoutRes;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.util.AttributeSet;
import android.widget.RemoteViews.RemoteView;
import com.android.internal.R;
import java.lang.ref.WeakReference;

/**
 * ViewStub是一个不可见的,0 size的View,它可以在运行时加载一个布局,即懒加载。
 * 当ViewStub设置为可见(visible),或者调用了ViewStub的inflate()方法时,
 * 其android:layout属性所关联的layout文件才会被加载和生成view对象。
 * 此时,当前ViewStub所在的位置会被其加载的子布局霸占替换;并且当前ViewStub对象会从其父容器中移除。
 * 这也就是为什么ViewStub只能inflate()一次的原因。inflate一次后,ViewStub对象都没有了,还怎么inflate()第二次?
 * 
 * 下面这句话注意了:
 * 被ViewStub加载的View,替换了ViewStub的存在,当然是添加到了ViewStub的父容器中,即原ViewStub的爸爸成了ViewStub加载的View的爸爸。
 * 并且在ViewStub上设置的layout参数,会转移给ViewStub加载的View身上。
 * 同样的,你也可以在ViewStub的android:inflatedId属性上设置id,当其加载布局时,会直接把id重新设置到加载的布局根上。
 * 如果没设置android:inflatedId属性,那待加载的布局根还是用以前的id属性,没有拉到。
 * 
 * 举个栗子:
 *
 * <pre>
 *     <ViewStub android:id="@+id/viewStub的id"
 *               android:inflatedId="@+id/这个id会在运行时加载中,设置到下面aaaaa布局的根属性上"
 *               android:layout="@layout/aaaaa,即稍后会加载且替换当前ViewStub的布局"
 *               android:layout_width="120dp"
 *               android:layout_height="40dp" />
 * </pre>
 *
 * 如上面的代码,当aaaaa布局加载后,你可以使用inflatedId属性值(即id值)找到aaaaa对象
 * 或者用aaaaa内部的根布局id找到aaaaa对象。
 * 
 * 下面ViewStub的代码:
 * <pre>
 *     ViewStub stub = (ViewStub) findViewById(R.id.viewStub的id);
 *     View inflated = stub.inflate();
 * </pre>
 *
 * 当inflate()方法被调用后,当前的ViewStub将会被加载的布局替换,并且返回值为加载的布局对象。
 * 
 * 注意下面两个属性的定义:
 * @attr ref android.R.styleable#ViewStub_inflatedId 给待加载的布局设置id,也可以不设置,用布局内部设置的id值
 * @attr ref android.R.styleable#ViewStub_layout 待加载的布局
 */
@RemoteView
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);
    }

    /**
     * 使用给定的待加载的布局文件创建一个ViewStub对象。
     *
     * @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);
        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();

		//注意,ViewStub永远是隐藏的,不会展示任何UI信息,且不占位
        setVisibility(GONE);
        setWillNotDraw(true);
    }

    /**
     * 返回待加载的布局的id,如果没有设置(即保持布局内部根属性上设置的id)则返回NO_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
    public void setInflatedId(@IdRes int inflatedId) {
        mInflatedId = inflatedId;
    }

    /**
     * 获取待加载的布局资源
     *
     * @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;
    }

    /**
     * 代码形式设置待加载的布局资源
     * 
     * @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
    public void setLayoutResource(@LayoutRes int layoutResource) {
        mLayoutResource = layoutResource;
    }

    /**
     * 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) {
		//设置当前ViewStub的宽高都为0,即不可见了
        setMeasuredDimension(0, 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
    public void setVisibility(int visibility) {
		//判断mInflatedViewRef对象是否为null,即判断是否调用过下面的inflate()函数
        if (mInflatedViewRef != null) {
			//已经加载过view,直接获取。
            View view = mInflatedViewRef.get();
            if (view != null) {
				//直接设置view的可见性即可,此时跟ViewStub没什么关系了。ViewStub已经从父容器中移除了
                view.setVisibility(visibility);
            } else {
                throw new IllegalStateException("setVisibility called on un-referenced view");
            }
        } else {
            super.setVisibility(visibility);
            if (visibility == VISIBLE || visibility == INVISIBLE) {
				//如果设置了ViewStub为可见,或者占位的话,则触发下面的inflate()函数
				//注意,有了mInflatedViewRef的判断,inflate()函数是不会调用第二次的。
				//当调用了一次inflate()函数后,当前ViewStub对象没有父容器了,然而在调用inflate()会报异常的。
                inflate();
            }
        }
    }

    /**
     * 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() {
		//获取到当前ViewStub对象的父容器
        final ViewParent viewParent = getParent();

		//判断父容器是否为null
        if (viewParent != null && viewParent instanceof ViewGroup) {
			//判断是否给ViewStub指定了android:layout=""属性,即待加载的布局
            if (mLayoutResource != 0) {
                final ViewGroup parent = (ViewGroup) viewParent;
                final LayoutInflater factory;
                if (mInflater != null) {
                    factory = mInflater;
                } else {
                    factory = LayoutInflater.from(mContext);
                }
				//加载指定的资源文件,注意三个参数的含义
				//可参考:http://blog.csdn.net/fesdgasdgasdg/article/details/72870280
                final View view = factory.inflate(mLayoutResource, parent,
                        false);

				//如果设置了android:inflatedId=""属性值,则把设置的id值赋给被加载的布局
                if (mInflatedId != NO_ID) {
                    view.setId(mInflatedId);
                }
				//记录当前ViiewStub在父容器中的位置
                final int index = parent.indexOfChild(this);
				//把当前ViewStub对象从父容器中移除
                parent.removeViewInLayout(this);
				//获取当前ViewStub的layoutParams参数
                final ViewGroup.LayoutParams layoutParams = getLayoutParams();
                if (layoutParams != null) {
					//连同ViewStub的layoutParams参数一起,把加载获得到的View设置到父容器相应的位置,代替了ViewStub的存在
					//注意layoutParams参数包含哪些值
                    parent.addView(view, index, layoutParams);
                } else {
                    parent.addView(view, index);
                }
				//创建加载布局的弱引用,在上面setVisibility()函数中会用到
                mInflatedViewRef = new WeakReference<View>(view);

                if (mInflateListener != null) {
					//布局加载成功,并且成功替换了ViewStub的位置,和拥有了ViewStub的资源。于是执行回调通知调用者
                    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");
        }
    }

    /**
     * 设置回调,当ViewStub成功的加载了布局资源后,会回调此接口进行通知
     *
     * @param inflateListener The OnInflateListener to notify of successful inflation.
     *
     * @see android.view.ViewStub.OnInflateListener
     */
    public void setOnInflateListener(OnInflateListener inflateListener) {
        mInflateListener = inflateListener;
    }

    /**
     * 资源文件成功被ViewStub加载后的监听器
     *
     * @see android.view.ViewStub#setOnInflateListener(android.view.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);
    }
}






  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
viewStub是一个轻量级的View,它可以延迟加载布局资源,而不必在Activity或Fragment的onCreate方法中立即加载视图树,这样可以提高应用程序的性能。而viewBinding是一种新的方式,它允许您直接从布局文件中获取对视图的引用,而不必使用findViewById()方法。当您使用viewStub时,您需要在布局文件中定义一个viewStub元素,然后在代码中使用它来加载布局资源。而当您使用viewBinding时,您需要在Activity或Fragment的onCreate方法中初始化绑定对象,然后使用它来获取对布局文件中的视图的引用。如果您要在使用viewBinding的情况下使用viewStub,您可以使用ViewBinding.inflate()方法来创建ViewBinding对象,然后使用ViewBinding.getRoot()方法来获取根视图,然后将其传递给viewStubsetVisibility()方法来显示或隐藏它。例如,您可以使用以下代码来显示viewStub: ``` private lateinit var binding: ActivityMainBinding private lateinit var stub: ViewStub override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) binding = ActivityMainBinding.inflate(layoutInflater) setContentView(binding.root) stub = binding.viewStub val inflatedView = binding.viewStub.inflate() // Do something with inflatedView } ``` 在这个例子中,我们首先使用ViewBinding.inflate()方法初始化了绑定对象,然后使用ViewBinding.getRoot()方法获取根视图,并将其传递给viewStubsetVisibility()方法来显示它。然后,我们使用viewStub的inflate()方法来加载布局资源,并将返回的视图对象保存在一个变量中,以便我们可以在代码中使用它。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值