• 从源代码角度分析ViewStub 疑问与原理



    一、提出疑问

        ViewStub比較简单。之前文章都提及到《Android 性能优化 三 布局优化ViewStub标签的使用》。可是在使用过程中有一个疑惑,究竟是ViewStub上设置的參数有效还是在其包含的layout中设置參数有效?假设不明确描写叙述的问题,能够看下下面布局伪代码。

    res/layout/main.xml
    <LinearLayout >
        <ViewStub
            android:id="@+id/viewstub"
            android:layout_width="100dip"
              android:layout_marginTop="100dip"
            android:layout_height="wrap_content"
            android:layout="@layout/sub_layout"
            />
       
    </LinearLayout>
    
    res/layout/sub_layout.xml
    <TextView
         android:layout_width="50dip"
         android:layout_marginTop="50dip"
         android:layout_height="wrap_content"
         android:text="ViewStub中包括的TextVeiw"/>

        上面的代码中width终于效果是100dip还是50dip?marginTop是100dip还是50dip?带着这个问题一起看下Android 5.0源代码看看ViewStub原理。


        为了便于把ViewStub与其infalte()载入出来的android:layout视图做个区分,下文中针对前者统一命名“ViewStub视图”。后者命名“被 载入视图”,仅为了描写叙述统一并不一定是专业名称。

    二、分析ViewStub源代码

        让ViewStub有两种方式一种是调用ViewStub.inflate() 第二种是设置ViewStub.setVisibility(View.VISIBLE); 事实上第二种方式依旧是调用的infalte方法,能够看例如以下ViewStub源代码。
        @Override
        @android.view.RemotableViewMethod
        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();
                }
            }
        }

    ViewStub复写了setVisibility方法,并在当中调用infalte方法。以下来看此方法源代码


    public final class ViewStub extends View {
         ......
    
        public View inflate() {
            final ViewParent viewParent = getParent(); // 1 为什么能够直接获取父视图?
    
              // ViewStub的父视图必须是ViewGroup的子类
            if (viewParent != null && viewParent instanceof ViewGroup) {
                if (mLayoutResource != 0) { // ViewStub必须设置android:layout属性
                    final ViewGroup parent = (ViewGroup) viewParent;
                    final LayoutInflater factory;
                    if (mInflater != null) {
                        factory = mInflater;
                    } else {
                        factory = LayoutInflater.from(mContext);
                    }
                    // 2 inflate被载入视图 
                    final View view = factory.inflate(mLayoutResource, parent,
                            false);
    
                    if (mInflatedId != NO_ID) {
                        view.setId(mInflatedId);
                    }
    
                    // 从父视图中获取当前ViewStub在父视图中的位置
                    final int index = parent.indexOfChild(this);
                    // 当前ViewStub也是个View只不过用来占位。所以先把占位的ViewStub视图删除
                    parent.removeViewInLayout(this);
    
                    // 3 此处获取的是ViewStub上面设置的參数
                    final ViewGroup.LayoutParams layoutParams = getLayoutParams();
                    if (layoutParams != null) {
                        parent.addView(view, index, layoutParams);
                    } else {
                        parent.addView(view, index);
                    }
    
                    // 目的是在复写的setVisibility方法中使用
                    // 由于ViewStub.setVisibility操作的是被载入视图并不是当前ViewStub视图
                    mInflatedViewRef = new WeakReference<View>(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");
            }
        }
         ......
    }


    以下说下源代码中列出的几点。

     1. 为什么能够直接获取父视图?

    ViewStub 继承自View其自身就是一个视图,其调用getParent()能够从父类View上入手、

    public class View {    
        public final ViewParent getParent() {
            return mParent;
        }
    
        void assignParent(ViewParent parent) {
            if (mParent == null) {
                mParent = parent;
            } else if (parent == null) {
                mParent = null;
            } else {
                throw new RuntimeException("view " + this + " being added, but"
                        + " it already has a parent");
            }
        }
    }


    从View的源代码中获取到,改动mParent參数的仅有assignParent方法且View中并未调用此方法,以下查看下其子类ViewGroup是否有调用。

    public class ViewGroup {    
        public void addView(View child, int index, LayoutParams params) {
        
            ......
             
            addViewInner(child, index, params, false);
        }
    
        private void addViewInner(View child, int index, LayoutParams params,
                boolean preventRequestLayout) {
                  
            ......           
             
            // tell our children
            if (preventRequestLayout) {
                child.assignParent(this);
            } else {
                child.mParent = this;
            }
             
              ......
         }    
    }


    从上面源代码能够看到在addView方法中会调用addViewInner,当中调用child.assignParent(this);,把自己全部子视图mParent都设置成当前ViewGroup。

    从这一点也能够看出,ViewStub本身是一个View且载入的时候就已经加入到视图树中(View Tree)中,仅接着有另外一个问题既然页面显示的时候ViewStub已经被加入到界面上。为什么有看不到ViewStub视图呢?

    疑问:为什么ViewStub尽管是懒载入。可是其自身是一个视图且展示界面就会加入到视图树中,为什么看不到ViewStub?

    public final class ViewStub extends View {
    
        public ViewStub(Context context) {
            initialize(context);
        }
         
         private void initialize(Context context) {
            mContext = context;
            setVisibility(GONE); // 初始化时把自己设置为隐藏
            setWillNotDraw(true);
        }
         
        @Override
        protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
            setMeasuredDimension(0, 0); // 全部子视图都设置为宽高为0
        }
    
        @Override
        public void draw(Canvas canvas) { // 不正确自身与子视图进行绘制
        }
    
        @Override
        protected void dispatchDraw(Canvas canvas) {
        }
    }


    从以上源代码能够看出ViewStub用尽全部办法让自己加入到视图树上是不显示ViewStub自身。



    2. inflate被载入视图
        再来看下载入android:layout视图的源代码。
    final View view = factory.inflate(mLayoutResource, parent, false);
        能够看到通过infalte方法记载的。其三个參数(int resource, ViewGroup root, boolean attachToRoot),各自是:
    mLayoutResource : 设置的android:layout的值
                    parent : 通过getParent()获取即ViewStub的父视图
                      false : attachToRoot设置为false说明忽略androd:layout中根节点的layoutParams參数,即width=50dip与margin50dip

    3. 视图加入ViewStub.getLayoutParams參数
    此处源代码的是获取ViewStub.getLayoutParams參数设置到anroid:layout载入的视图上。 即width=100dip与marginTop=100dip生效。


    三、总结
    开头的疑问的答案。inflate出来的视图width=100dip与marginTop=100dip而android:layout视图中设置的width50dip和marginTop=50dip失效,等于没有设置。

    ViewStub的原理简单描写叙述是
    1. ViewStub本身是一个视图。会被加入到界面上,之所以看不到是由于其源代码设置为隐藏与不绘制。
    2. 当调用infalte或者ViewStub.setVisibility(View.VISIBLE);时(两个都使用infalte方法逻辑),先从父视图上把当前ViewStub删除。再把载入的android:layotu视图加入上
    3. 把ViewStub layoutParams 加入到载入的android:layotu视图上。而其根节点layoutParams 设置无效。
    4. ViewStub是指用来占位的视图,通过删除自己并加入android:layout视图达到懒载入效果




  • 相关阅读:
    Python读写Excel文件和正则表达式
    R Language Learn Notes
    Electron小记
    Unity商店下载的文件保存路径?
    Unity LineRenderer制作画版
    unity图形圆形展开
    [转]资深CTO:关于技术团队打造与管理的10问10答
    unity游戏在ios11上不显示泰语解决办法
    jQuery火箭图标返回顶部代码
    jQuery火箭图标返回顶部代码
  • 原文地址:https://www.cnblogs.com/bhlsheji/p/5174505.html
Copyright © 2020-2023  润新知