精华内容
下载资源
问答
  • 这里介绍一个在某些场景下提升布局性能的View,它就是ViewStub. ViewStub是什么 ViewStub是View的子类 它不可见,大小为0 用来延迟加载布局资源 注,关于Stub的解释 A stub is a small program routine that ...
  • 相信大家经常听到include、merge、ViewStub这样的标签,官方也提到这三种布局可用于布局的优化。今天就介绍下这三种布局的使用,记录下来,便于后续app中的使用。 include布局重用 app开发过程中,会遇到不同页面里...
  • 主要介绍了Android在layout xml中使用ViewStub完成动态加载问题,非常不错,具有参考借鉴价值,需要的朋友可以参考下
  • 在开发应用程序的时候,经常会遇到这样的情况,会在运行时动态根据条件来决定显示哪个View或某个布局。那么最通常的想法就是把...推荐的做法是使用android.view.ViewStubViewStub是一个轻量级的View,它一个看不见的
  • Android 使用ViewStub 实现Activity 页面不同状态动态切换。可以直接继承使用,也可以根据自己的需求进行优化。。
  • ViewStub可以在运行时动态的添加布局。帮助文档给定的定义是: “A ViewStub is an invisible, zero-sized View that can be used to lazily inflate layout resources at runtime. When a ViewStub is made visible...
  • ViewStub是一个非常轻量级的View,这篇文章主要为大家详细介绍了Android布局优化之ViewStub控件的使用方法以及注意事项,感兴趣的小伙伴们可以参考一下
  • DataBinding中ViewStub例子

    2016-03-17 15:31:01
    DataBinding中ViewStub例子
  • ViewStub

    2021-03-09 10:03:19
    ViewStub什么时候使用ViewStub?为什么使用?基本介绍总结 什么时候使用ViewStub?为什么使用? 需要控制某个View显示或者隐藏的话,会把需要的View写在布局上,然后通过View.GONE/View.InVisible设置可见性;这样虽然...

    什么时候使用ViewStub?为什么使用?

    需要控制某个View显示或者隐藏的话,会把需要的View写在布局上,然后通过View.GONE/View.InVisible设置可见性;这样虽然操作简单,但是还是耗费资源,因为即使view不可见,还是可以被父窗体控制,还是会被创建对象,还是会被实例化,还是会被设置属性。
    Android.view.ViewStub是一个大小为0,默认不可见的控件,只有给它设置成了View.Visiable或者调用了inflate()才会填充布局资源,占用的资源少。推荐使用ViewStub;

    基本介绍

    ViewStub是一个不可见,大小为0的视图,可以在运行过程中延时加载布局资源;
    当ViewStub被设置成可见,或者它的inflate()被调用的时候,布局资源才会被填充,然后ViewStub本身就会被填充起来的布局资源替换掉,它在视图树里面就不存在了,被填充的布局在替换ViewStub的时候会使用ViewStub的布局参数(LayoutParameters),如height,width等;也可以通过ViewStub的inflateId属性定义或者重写被填充布局资源的Id。

    总结

    o ViewStub引用布局时,使用layout属性,取值@layout/XXXX;

    o InflateId表示给被引用布局的ID,也就是被控制显示或隐藏的布局的id;

    o 如果不写inflateId的话,如果需要可以在被引用的布局中直接给出id属性;

    o inflate()方法只能被调用一次,如果再调用会报异常信息,ViewStub must have a non null ViewGrounp ViewParent,
    ViewStub使用inflate()将其引用的布局view展示出来之后,ViewStub本身就会从视图树里面被移除,此时ViewStub就获取不到它的父布局,而inflate()一上来就需要获取它的父布局,然后根据父布局是否为空再去执行具体的填充逻辑,如果一开始就是空,就会报出上面的错误。
    所以如果inflate()之后如果还想调用ViewStub引用的布局view,就需要在调用inflate()的时候使用try–catch(),当catch到异常的时候调用setVisibility()设置ViewStub的View.Visible即可。

    o ViewStub里面的setVisibility()里面也多次调用inflate(),这个时候为什么不会崩溃?
    ViewStub的setVisibility()里面会先判断WeakReference类型的成员变量mInflatedViewRef是否为空,第一次调用setVisibility()的时候,mInflatedViewRef没有被初始化,所以为Null,这个时候就会走inflate,在inflate()里面创建起来的布局填充一个WeakReference弱引用,并且赋值给mInflatedViewRef,从而完成mInflateViewRef的初始化;
    第二次调用setVisibility()的时候,mInflatedViewRef不是null,就会调用WeakReference的父类Reference里面的get()方法获取该引用指向的实体对象,也就是说通过get()拿到被填充的对象view,然后再走View的setVisibility()。

    o ViewStub的inflate()只能被调用一次;
    如果想控制/修改被填充布局里面的内容并重复显示被填充的view,就用try将viewStub.inflate()方法以及修改内容的代码包裹起来,并在catch里面setVisibility。

    o 在xml中定义ViewStub结点的时候,内部不能包含其他节点,也就是说ViewStub是一个自闭和结点,如果一个布局/View想通过ViewStub显示,只能定义在单独的xml文件中。

    展开全文
  • 利用ViewStub显示和隐藏布局 运用View.VISIBLE和View.GONE去改变布局的可见性.
  • 在4.5.6节介绍过一个标签,该标签可以在布局文件中引用另外一个布局文件,并可以覆盖被引用布局文件根节点所有与布局相关的属性,也就是以android:layout开头的属性。通过标签可以将一个非常庞大的布局文件分解成...
  • ViewStub的简单使用

    2019-03-25 01:17:11
    NULL 博文链接:https://edison-cool911.iteye.com/blog/940103
  • 理解 ViewStub 原理

    2019-03-18 11:52:38
    本人只是Android小菜一个,写...本文基于原生Android 9.0源码来解析 ViewStub的实现原理 android/view/ViewStub.java android/view/View.java 复制代码1. 概述 在进行Android程序开发时,除了要实现基本功能外,还...

    本人只是Android小菜一个,写技术文档只是为了总结自己在最近学习到的知识,从来不敢为人师,如果里面有些不正确的地方请大家尽情指出,谢谢!

    本文基于原生Android 9.0源码来解析 ViewStub的实现原理

    android/view/ViewStub.java
    android/view/View.java
    复制代码

    1. 概述

    在进行Android程序开发时,除了要实现基本功能外,还要关注程序的性能,例如使用更少的内存、消耗更少的电量、更快地响应用户操作以及更快地启动显示等等。这个特点注定在我们平时工作中,有很大一部分精力都在进行性能优化,其中一个优化方向就是让程序在尽可能短的时间内启动并显示,让用户感觉不到延迟,保证良好的用户体验。

    “懒加载”就是为了让程序尽可能快地启动而提出的一个优化策略,即让那些对用户不重要或者不需要立即显示的布局控件做延迟加载,只在需要显示的时候才进行加载,这样就可以让程序在启动显示的过程中加载更少的控件,占用更少的内存空间,从而更快启动并显示。“懒加载”策略的具体实现方式多种多样,Android系统也提供了一种用于实现布局控件懒加载的工具ViewStub,它能够让相关布局在显示时再进行加载,从而提升程序启动速度。

    本文先简单介绍ViewStub的使用方法,再介绍其实现“懒加载”的原理,以帮助大家加深对它的理解。

    2. ViewStub 使用方法

    在讲解ViewStub的使用方法前,按照惯例,我们还是先来看看它的声明:

    /**
     * A ViewStub is an invisible, zero-sized View that can be used to lazily inflate
     * layout resources at runtime.
     *
     * When a ViewStub is made visible, or when {@link #inflate()}  is invoked, the layout resource 
     * is inflated. The ViewStub then replaces itself in its parent with the inflated View or Views.
     * Therefore, the ViewStub exists in the view hierarchy until {@link #setVisibility(int)} or
     * {@link #inflate()} is invoked.
     * ......
     */
    public final class ViewStub extends View { ... }
    复制代码

    从这段介绍中可以知道ViewStub是一个不可见并且大小为0的控件,其作用就是用来实现布局资源的“懒加载”,当调用setVisibility()或者inflate()时,和ViewStub相关的布局资源就会被加载并在控件层级结构中替代ViewStub,同时ViewStub会从控件层级结构中移除,不再存在。

    现在再来看下它的使用方法,首先需要在布局文件配置:

    <ViewStub
        android:id="@+id/view_stub_id"
        android:layout="@layout/view_stub"
        android:inflatedId="@+id/view_stub_id"
        android:layout_width="200dp"
        android:layout_height="50dp" />
    复制代码

    由于ViewStub是直接继承自View的,所以它在xml里的基本使用方法和其他控件是一样的,只是有一些重要属性需要注意,其中android:layout指的是真正需要加载的布局资源,android:inflatedId指的是布局资源被加载后的View ID,总结如下:

    属性含义/作用属性级别是否可选
    android:idViewStub 在布局文件中的ID,用于在代码中访问。View 共有必写
    android:layout在显示 ViewStub 时真正加载并显示的布局文件ViewStub 特有必写
    android:inflatedId真正布局文件加载后创建的控件IDViewStub 特有可选

    xml中定义ViewStub后就可以在代码里直接使用并根据具体业务逻辑在需要显示的时候对其进行加载:

    ViewStub viewStub = (ViewStub) findViewById(R.id.view_stub_id);
    if (viewStub != null) {
        // 调用 inflate 加载真正的布局资源并返回创建的 View 对象
        View inflatedView = viewStub.inflate();
        // 在得到真正的 View 对象后,就可以和直接加载的控件一样使用了。
        TextView textView = inflatedView.findViewById(R.id.view_stub_textview);
    }
    复制代码

    ViewStub的“懒加载”能起多大效果,取决于是否能在最合适的时机显示它,由于每个模块的业务逻辑不同,其最合适的显示时机也不相同,但基本的原则就是在使用它的前一刻进行加载,这才能使ViewStub的"懒加载"作用最大化,也才能使性能最好。

    3. ViewStub 原理解析

    3.1 构造过程

    前面在ViewStub的声明中看到它是一个不可见且大小为0的控件,那么是如何做到这点的呢?首先看下它的构造过程:

    public final class ViewStub extends View {
        // 在 xml 中定义的 android:inflatedId 值,用于加载后的 View Id。
        private int mInflatedId;
        // 在 xml 中定义的 android:layout 值,是需要真正加载的布局资源。
        private int mLayoutResource;
        // 保存布局创建的 View 弱引用,方便在 setVisibility() 函数中使用。
        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);
            // 获取 xml 中定义的 android:inflatedId 和 android:layout 属性值
            mInflatedId = a.getResourceId(R.styleable.ViewStub_inflatedId, NO_ID);
            mLayoutResource = a.getResourceId(R.styleable.ViewStub_layout, 0);
            // 获取 ViewStub 在 xml 中定义的 id 值
            mID = a.getResourceId(R.styleable.ViewStub_id, NO_ID);
            a.recycle();
    
            // 设置 ViewStub 控件的显示属性,直接设置为不显示。
            setVisibility(GONE);
            // 设置 ViewStub 不进行绘制
            setWillNotDraw(true);
        }
        
        ...
        
        @Override
        protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
            // 设置宽高都为 0
            setMeasuredDimension(0, 0);
        }
    
        @Override
        public void draw(Canvas canvas) {
            // 空方法,不进行任何绘制。
        }
    
        @Override
        protected void dispatchDraw(Canvas canvas) {
        }
    }
    复制代码

    ViewStub的构造过程比较简单,相信很容易看懂,小菜也在代码的关键点都增加了注释,从中可以发现几点关键信息:

    1. ViewStub的不可见性:在ViewStub的构造函数中,利用setVisibility(GONE)将可见性设置为不可见,所以无论在 xml里如何设置,都是不可见的。
    2. ViewStub的不绘制性:在 ViewStub的构造函中,利用setWillNotDraw(true)使其不进行绘制并且把draw()实现为空方法,这些都保证了ViewStub在加载的时候并不会进行实际的绘制工作。
    3. ViewStub的零大小性:在onMeasure()中把宽高都直接指定为0,保证了其大小为0。

    正是由于ViewStub的这些特性,其加载过程几乎不需要时间,可以认为它的存在不会对相关程序的启动产生影响。

    3.2 懒加载过程

    前面提到:在调用inflate()或者setVisibility()时,ViewStub才会加载真正的布局资源并在控件层级结构中替换为真正的控件,同时ViewStub从控件层级结构中移除,这是“懒加载”的核心思想,是如何实现的呢?既然是调用inflate()setVisibility(),就直接分析相关代码。

    首先看下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();
    
        if (viewParent != null && viewParent instanceof ViewGroup) {
            if (mLayoutResource != 0) {
                final ViewGroup parent = (ViewGroup) viewParent;
                // 根据 android:layout 指定的 mLayoutResource 加载真正的布局资源,渲染成 View 对象。
                final View view = inflateViewNoAdd(parent);
                // 在控件层级结构中把 ViewStub 替换为新创建的 View 对象。
                replaceSelfWithView(view, parent);
    
                // 保存 View 对象的弱引用,方便其他地方使用。
                mInflatedViewRef = new WeakReference<>(view);
                // 渲染回调,默认不存在。
                if (mInflateListener != null) {
                    mInflateListener.onInflate(this, view);
                }
                // 返回新创建的 View 对象
                return view;
            } else {
                // 如果没有在 xml 指定 android:layout 会走到这个路径,所以 android:layout 是必须指定的。
                throw new IllegalArgumentException("ViewStub must have a valid layoutResource");
            }
        } else {
            // 在第一次调用 inflate() 后,ViewStub 会从控件层级结构中移除,不再有父控件,
            // 此后再调用 inflate() 会走到这个路径,所以 inflate() 只能调用一次。
            throw new IllegalStateException("ViewStub must have a non-null ViewGroup viewParent");
        }
    }
    
    private View inflateViewNoAdd(ViewGroup parent) {
        // 获取布局渲染器
        final LayoutInflater factory;
        if (mInflater != null) {
            factory = mInflater;
        } else {
            factory = LayoutInflater.from(mContext);
        }
        // 把真正需要加载的布局资源渲染成 View 对象。
        final View view = factory.inflate(mLayoutResource, parent, false);
        // 如果在 xml 中指定 android:inflatedId 就设置到新创建的 View 对象中,可以不指定。
        if (mInflatedId != NO_ID) {
            view.setId(mInflatedId);
        }
        return view;
    }
    
    private void replaceSelfWithView(View view, ViewGroup parent) {
        final int index = parent.indexOfChild(this);
        // 把 ViewStub 从控件层级中移除。
        parent.removeViewInLayout(this);
    
        // 把新创建的 View 对象加入控件层级结构中,并且位于 ViewStub 的位置,
        // 并且在这个过程中,会使用 ViewStub 的布局参数,例如宽高等。
        final ViewGroup.LayoutParams layoutParams = getLayoutParams();
        if (layoutParams != null) {
            parent.addView(view, index, layoutParams);
        } else {
            parent.addView(view, index);
        }
    }
    复制代码

    inflate()函数详细地展示了如何渲染布局资源以及如何在控件层级结构中把ViewStub替换为新创建的View对象,代码比较简单,小菜也在关键地方增加了注释。

    再来看下setVisibility()函数代码:

    /**
     * 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) {
        if (mInflatedViewRef != null) {
            // 如果已经调用过 inflate() 函数,mInflatedViewRef 会保存新创建 View 对象的弱引用,
            // 此时直接更新其可见性即可。
            View view = mInflatedViewRef.get();
            if (view != null) {
                view.setVisibility(visibility);
            } else {
                throw new IllegalStateException("setVisibility called on un-referenced view");
            }
        } else {
            // 如果没有调用过 inflate() 函数就会走到这个路径,会在设置可见性后直接调用 inflate() 函数。
            super.setVisibility(visibility);
            if (visibility == VISIBLE || visibility == INVISIBLE) {
                inflate();
            }
        }
    }
    复制代码

    setVisibility()的代码逻辑也很简单:如果inflate()已经被调用过就直接更新控件可见性,否则更新可见性并调用inflate()加载真正的布局资源,渲染成 View 对象。

    4. 总结

    一些布局控件在开始时并不需要显示,在程序启动后再根据业务逻辑进行显示,通常的做法是在 xml中将其定义为不可见,然后在代码中通过setVisibility()更新其可见性,但是这样做会对程序性能产生不利影响,因为虽然该控件的初始状态是不可见,但仍然会在程序启动时进行创建和绘制,增加了程序的启动时间。正是由于这种情况的存在,Android系统提供了ViewStub框架,能够很容易实现“懒加载”以提升程序性能,本文从“使用方法”和“实现原理”两个方面对其进行讲解,希望能对大家有所帮助。

    展开全文
  • ViewStub--使用介绍

    2021-03-24 11:04:45
    (1)什么时候使用ViewStub?为什么使用ViewStub? 当我们需要根据某个条件控制某个View的显示或者隐藏的时候,通常是把可能用到的View都写在布局上,然后设置可见性为View.GONE或View.InVisible ,之后在代码中根据...

    (1)什么时候使用ViewStub?为什么使用ViewStub?

    当我们需要根据某个条件控制某个View的显示或者隐藏的时候,通常是把可能用到的View都写在布局上,然后设置可见性为View.GONE或View.InVisible ,之后在代码中根据条件动态控制可见性。虽然操作简单,但是耗费资源,因为即便该view不可见,仍会被父窗体绘制,仍会创建对象,仍会被实例化,仍会被设置属性。

    而android.view.ViewStub,是一个大小为0 ,默认不可见的控件,只有给他设置成了View.Visible或调用了它的inflate()之后才会填充布局资源,也就是说占用资源少。所以,推荐使用viewStub

    (2)ViewStub基本介绍

    官方文档地址:https://developer.android.com/reference/android/view/ViewStub.html

    ViewStub 继承自View。

    官方文档原文:A ViewStub is an invisible, zero-sized View that can be used to lazily inflate layout resources at runtime. When a ViewStub is made visible, or when inflate() is invoked, the layout resource is inflated. The ViewStub then replaces itself in its parent with the inflated View or Views. Therefore, the ViewStub exists in the view hierarchy until setVisibility(int) or inflate() is invoked. The inflated View is added to the ViewStub's parent with the ViewStub's layout parameters. Similarly, you can define/override the inflate View's id by using the ViewStub's inflatedId property. For instance:

    我的翻译:

    ViewStub 是一个不可见的,大小为0的视图,可以在运行过程中延时加载布局资源。当ViewStub被设置成可见,或者它的inflate() 方法被调用的时候,布局资源才会被填充,然后ViewStub本身就会被填充起来的布局资源替换掉。也就是说 ViewStub 被设置成可见或者它的inflate() 方法被调用之后,在视图树中就不存在了。被填充的布局在替换ViewStub的时候会使用ViewStub的布局参数(LayoutParameters),比如 width ,height等。此外,你也可以通过ViewStub的inflateId 属性定义或者重写 被填充布局资源的id。

    <ViewStub android:id="@+id/stub"
                   android:inflatedId="@+id/subTree"
                   android:layout="@layout/mySubTree"
                   android:layout_width="120dip"
                   android:layout_height="40dip" />

    官方文档原文:The ViewStub thus defined can be found using the id "stub." After inflation of the layout resource "mySubTree," the ViewStub is removed from its parent. The View created by inflating the layout resource "mySubTree" can be found using the id "subTree," specified by the inflatedId property. The inflated View is finally assigned a width of 120dip and a height of 40dip. The preferred way to perform the inflation of the layout resource is the following:

    我的翻译:

    在上面的示例代码中,可以通过id 获取到ViewStub,当layout属性引用的布局资源 mySubTree 被填充之后,ViewStub就会从它的父窗体中移除,取而代之的就是mySubTree。通过inflatedId 属性的值可以获取到mySubTree。mySubTree 在替代ViewSub的时候会使用ViewStub的layoutParametes, 也就是说mySubTree 的宽高会被定义成 120dp 、40dp。 推荐用如下方式去实现mySubTree布局资源的填充:

    ViewStub stub = (ViewStub) findViewById(R.id.stub);
    View inflated = stub.inflate();

    When inflate() is invoked, the ViewStub is replaced by the inflated View and the inflated View is returned. This lets applications get a reference to the inflated View without executing an extra findViewById().

    我的翻译:

    当ViewStub 的inflate() 方法被调用之后,ViewStub就会被填充起来的布局替换掉,并返回填充起来的View。这样,当我们想使用被填充起来的View时就不再需要调用findViewById () 方法。(实际使用的时候,如果我们需要操作被填充布局里面的数据时用inflate(),否则可以直接使用setVisibility)

    (3)示例代码:

    最终效果展示:

    ViewStub.gif
    • activity_viewstub_test02.xml
    <?xml version="1.0" encoding="utf-8"?>
    <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
                    android:layout_width="match_parent"
                    android:layout_height="match_parent"
                    android:orientation="vertical">
        <LinearLayout android:layout_width="match_parent"
                      android:layout_height="wrap_content">
            <Button
                android:id="@+id/btn_vs_showView"
                android:layout_width="0dp"
                android:layout_height="wrap_content"
                android:layout_weight="1"
                android:text="显示ViewStub"/>
            <Button
                android:id="@+id/btn_vs_changeHint"
                android:layout_width="0dp"
                android:layout_height="wrap_content"
                android:layout_weight="1"
                android:text="更改ViewStub"/>
            <Button
                android:id="@+id/btn_vs_hideView"
                android:layout_width="0dp"
                android:layout_height="wrap_content"
                android:layout_alignParentRight="true"
                android:layout_weight="1"
                android:text="隐藏ViewStub"/>
        </LinearLayout>
    
    
        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_centerInParent="true"
            android:text="正在加载。。。"/>
    
        <!--ViewStub 展示或者隐藏内容-->
        <ViewStub
            android:id="@+id/viewstub_test"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:inflatedId="@+id/iv_VsContent"
            android:layout="@layout/iv_vs_content"/>
    
    </RelativeLayout>
    • ViewStubTestActivitiy.java
    /**
     * Created by CnPeng on 2017/1/11. ViewStub 的使用示例
     */
    
    public class ViewStubTestActivitiy extends AppCompatActivity implements View.OnClickListener {
    
        private ViewStub viewStub;
        private TextView hintText;
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_viewstub_test02);
    
            init();
        }
    
        /**
         * 初始化
         */
        private void init() {
            viewStub = (ViewStub) findViewById(R.id.viewstub_test);
    
            Button btn_show = (Button) findViewById(R.id.btn_vs_showView);
            Button btn_hide = (Button) findViewById(R.id.btn_vs_hideView);
            Button btn_change = (Button) findViewById(R.id.btn_vs_changeHint);
    
            btn_show.setOnClickListener(this);
            btn_hide.setOnClickListener(this);
            btn_change.setOnClickListener(this);
    
        }
    
        @Override
        public void onClick(View v) {
            switch (v.getId()) {
                case R.id.btn_vs_showView:
    
                    //inflate 方法只能被调用一次,因为调用后viewStub对象就被移除了视图树;
                    // 所以,如果此时再次点击显示按钮,就会崩溃,错误信息:ViewStub must have a non-null ViewGroup viewParent;
                    // 所以使用try catch ,当此处发现exception 的时候,在catch中使用setVisibility()重新显示
                    try {
                        View iv_vsContent = viewStub.inflate();     //inflate 方法只能被调用一次,
                        hintText = (TextView) iv_vsContent.findViewById(R.id.tv_vsContent);
                        //                    hintText.setText("没有相关数据,请刷新");
                    } catch (Exception e) {
                        viewStub.setVisibility(View.VISIBLE);
                    } finally {
                        hintText.setText("没有相关数据,请刷新");
                    }
                    break;
                case R.id.btn_vs_hideView:  //如果显示
                    viewStub.setVisibility(View.INVISIBLE);
                    break;
                case R.id.btn_vs_changeHint:
                    if (hintText!=null) { 
                        hintText.setText("网络异常,无法刷新,请检查网络");
                    }
                    break;
            }
        }
    }
    • iv_vs_content.xml --要通过ViewStub展示出来的内容
    <?xml version="1.0" encoding="utf-8"?>
    <LinearLayout
        xmlns:android="http://schemas.android.com/apk/res/android"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical">
        <ImageView
            xmlns:android="http://schemas.android.com/apk/res/android"
            android:layout_width="match_parent"
            android:layout_height="0dp"
            android:layout_weight="1"
            android:src="@mipmap/ic_launcher"/>
    
        <TextView
            android:id="@+id/tv_vsContent"
            android:layout_width="match_parent"
            android:layout_height="@dimen/dp30"
            android:gravity="center"
            android:text="eeee"/>
    </LinearLayout>

    (4) 使用总结

    总结:

    1)ViewStub 引用布局时使用layout 属性,取值@layout/xxxxx

    2)InflateId 表示给被引用的布局的id。也就是被控制显示或隐藏的布局的id.

    3)如果不写InflateId ,如果需要的话也可以直接在被引用的布局中给出id属性

    4)inflate() 方法只能被调用一次,如果再次调用会报异常信息 ViewStub must have a non-null ViewGroup viewParent

    这是因为,当ViewStub 调用inflate() 将其引用的 布局/view 展示出来之后,ViewStub本身就会从视图树中被移除,此时viewStub 就获取不到他的 父布局, 而 inflate() 方法中,上来就需要获取它的父布局,然后根据父布局是否为空再去执行具体的填充逻辑,如果为空就报上面的错,所以,inflate() 之后如果还想再次显示ViewStub 引用的布局/view 就需要 在调用inflate() 的时候try catch,当 catch 到异常的时候,调用setVisibility()设置viewStub 的View.Visible即可。ViewStub类中Inflate() 的具体逻辑如下:

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

    5) ViewStub的setVisibility()中也调用了inflate(),但是为什么多次调用setVisibility()不会导致崩溃呢?

    ViewStub 的setVisibility() 方法中,会先判断 WeakReference 类型的成员变量 mInflatedViewRef 是否为空。第一次调用setVisibility()的时候,mInflatedViewRef并没有初始化,也就是说是null,那么这时候就会走inflate(),在inflate() 方法中给被填充起来的布局/view创建一个WeakReference弱引用,并赋值给mInflatedViewRef,从而完成mInflatedViewRef的初始化。当第二次走setVisibility() 的时候,mInflatedViewRef已经不再是null,就会调用 WeakReference 的父类Reference 中的get() 方法获取该引用指向的实体对象,也就是说通过get() 拿到 被填充的view对象,然后再走View类的setVisibility()。ViewStub类中的setVisibility()具体实现如下:

    @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();
            }
        }
    }
    1. 根据上面 第4点和第5点可以得出如下结论:

    (1) ViewStub 的inflate() 只能被调用一次!

    (2) 如果想控制/修改 被填充布局中的内容并重复显示被填充的view,就用try 将viewStub.inflate() 以及修改内容的代码包裹起来,并在catch 中setVisibility.

    7) 在xml 中定义ViewStub 节点时,内部不能包含其他节点,也就是说,ViewStub 是一个自闭合节点,如果一个布局/view如果想通过ViewStub显示,只能定义在单独的xml 文件中。

    版权声明:

    ****以上内容均为原创,如需转载请注明出处****
    如文章中有不当之处,敬请指正。

    展开全文
  • ViewStub延迟加载

    2019-07-30 16:53:51
    使用时可以为ViewStub指定一个布局,在Inflate布局的时候,只有ViewStub会被初始化,然后当ViewStub被设置为可见的时或调用了ViewStub.inflate()的时候,ViewStub所指向的布局就会被inflate实例化,且此布局文件直接...

    在项目中,难免会遇到这种需求,在程序运行时需要动态根据条件来决定显示哪个View或某个布局,最通常的想法就是把需要动态显示的View都先写在布局中,然后把它们的可见性设为View.GONE,最后在代码中通过控制View.VISIABLE动态的更改它的可见性。这样的做法的优点是逻辑简单而且控制起来比较灵活。但是它的缺点就是,耗费资源,虽然把View的初始可见View.GONE但是在Inflate布局的时候View仍然会被Inflate,也就是说仍然会创建对象,会被实例化,会被设置属性。也就是说,会耗费内存等资源。

    推荐的做法是使用android.view.ViewStub,ViewStub是一个轻量级的View,使用非常简单:

    mViewStub = (ViewStub) this.findViewById(R.id.viewstub);

    mViewStub.inflate();

    它一个不可见的,不占布局位置,占用资源非常小的控件,相当于一个“占位控件”。使用时可以为ViewStub指定一个布局,在Inflate布局的时候,只有ViewStub会被初始化,然后当ViewStub被设置为可见的时或调用了ViewStub.inflate()的时候,ViewStub所指向的布局就会被inflate实例化,且此布局文件直接将当前ViewStub替换掉,然后ViewStub的布局属性(layout_margin***、layout_width等)都会传给它所指向的布局。这样,就可以使用ViewStub在运行时动态显示布局,节约内存资源。

    下面我们从ViewStub源码来看下inflate()方法的实现原理:

     public View inflate() {
            final ViewParent viewParent = getParent();
     
            if (viewParent != null && viewParent instanceof ViewGroup) {
                if (mLayoutResource != 0) {
                    final ViewGroup parent = (ViewGroup) viewParent;
                    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);
                    }
     
                    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);
                    }
     
                    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、在第2行,首先是得到ViewStub它的父视图对象。

    2、然后在第4行一开始肯定是能进入判断的,mLayoutResource就是需要inflate的布局资源,然后在第13行填充这个布局资源。

    3、然后在第21行,重要的来了,parent.removeViewInLayout(this);这段代码是什么意思呢?看方法名字就知道了,this是代表ViewStub对象,意思就是把当前ViewStub对象从父视图中移除了。

    4、然后第23~28行,就是得到ViewStub的LayoutParams布局参数对象,如果存在就把它赋给被inflate的布局对象,然后把inflate的布局对象添加到父视图中。
    5、最后返回inflate的布局对象。

    好了,源码解析完毕!!!

    从上述可知,当我们第二次调用ViewStub.inflate()方法的时候,因为已经移除了ViewStub对象,在第2、4行,得到的viewParent就为null,此时判断时候就会走else抛出一个IllegalStateException异常:ViewStub must have a non-null ViewGroup viewParent。

    需要注意的几点:
    1.ViewStub之所以常称之为“延迟化加载”,是因为在教多数情况下,程序无需显示ViewStub所指向的布局文件,只有在特定的某些较少条件下,此时ViewStub所指向的布局文件才需要被inflate,且此布局文件直接将当前ViewStub替换掉,具体是通过viewStub.infalte()或viewStub.setVisibility(View.VISIBLE)来完成。

    2.正确把握住ViewStub的应用场景非常重要,因为使用ViewStub可以优化布局,一般应用在当前布局或控件在用户使用较少情况下,这样可以提高性能,节约内存,加快界面渲染。

    3.对ViewStub的inflate操作只能进行一次,因为inflate的时候是将它指向的布局实例化并替换掉当前ViewStub本身(由此体现出了ViewStub“占位”性质),一旦替换后,此时原来的布局文件中就没有ViewStub控件了,因此,如果多次对ViewStub进行infalte,会出现错误信息:ViewStub must have a non-null ViewGroup viewParent。

    4.3中所讲到的ViewStub指向的布局文件解析inflate并替换掉当前ViewStub本身,并不是完全意义上的替换(与include标签不太一样),替换时,布局文件的layout params是以ViewStub为准,其他布局属性是以布局文件自身为准。

    5.ViewStub本身是不可见的,对ViewStub.setVisibility(int visibility)与其他View控件不一样,我们可以从源码角度来看一下ViewStub.setVisibility()方法的作用:
    在这里插入图片描述
    这个方法意思就是ViewStub的setVisibility()设置成View.VISIBLE或INVISIBLE如果是首次使用,都会自动inflate其指向的布局文件,并替换ViewStub本身,再次使用则是相当于对其指向的布局文件设置可见性。

    好了,原理讲了那么多,来看看代码怎么实现吧:
    使用了ViewStub的activity_main.xml:

    <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:orientation="vertical"
        tools:context="com.example.viewstub.MainActivity" >
     
        <Switch
            android:id="@+id/switch1"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content" />
     
        <ViewStub
            android:id="@+id/viewstub"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:layout_marginBottom="10dp"
            android:layout_marginLeft="10dp"
            android:layout_marginRight="10dp"
            android:layout_marginTop="10dp"
            android:layout="@layout/hide_layout" />
     
    </LinearLayout>
    

    hide_layout.xml

    <?xml version="1.0" encoding="utf-8"?>
    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:background="#00ffff"
        android:orientation="vertical" >
        <Button
            android:id="@+id/hide_layout_btn"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_gravity="center"
            android:text="Click me" />
    </LinearLayout>
    

    代码文件:

    public class MainActivity extends ActionBarActivity {
    	private ViewStub mViewStub;
    	private Switch mSwitch;
    	private boolean flag = false;
    	@Override
    	protected void onCreate(Bundle savedInstanceState) {
    		super.onCreate(savedInstanceState);
    		setContentView(R.layout.activity_main);
    		mViewStub = (ViewStub) this.findViewById(R.id.viewstub);//实例化ViewStub
    		mSwitch = (Switch) findViewById(R.id.switch1);
    		mSwitch.setOnCheckedChangeListener(new OnCheckedChangeListener() {
    			@Override
    			public void onCheckedChanged(CompoundButton buttonView,
    					boolean isChecked) {
    				if (isChecked) {
    					if(!flag){
    						mViewStub.inflate();//ViewStub只能被inflate一次,会返回一个填充资源的View对象
    						//mViewStub.setVisibility(View.VISIBLE);)
    						flag = true;
    					}else{
    						mViewStub.setVisibility(View.VISIBLE);
    					}
    					Button mBtn = (Button) findViewById(R.id.hide_layout_btn);//ViewStub被替换的布局内的控件
    					mBtn.setOnClickListener(new OnClickListener() {
    						@Override
    						public void onClick(View v) {
    							Toast.makeText(getApplicationContext(), "Click me!",
    									Toast.LENGTH_SHORT).show();
    						}
    					});
    				} else {
    					mViewStub.setVisibility(View.GONE);
    				}
    			}
    		});
    	}
    }
    

    注:使用ViewStub被替换的布局中的控件,直接findViewById即可。

    减少视图层级merge

    <merge />标签在UI的结构优化中起着非常重要的作用,它可以删减多余的层级,优化UI。<merge />多用于替换FrameLayout(因为所有的
    Activity视图的根结点都是FrameLayout,如果当前的布局根结点是Framelayout,那么可以用merge替代,减少多余的层级)或者当一个布局
    包含另一个时,<merge />标签消除视图层次结构中多余的视图组。例如你的主布局文件是垂直布局,又include引入了一个垂直布局,这是
    如果include布局使用的LinearLayout就没意义了,使用的话反而减慢你的UI渲染。这时可以使用<merge />标签进行优化。
    
    <merge xmlns:android="http://schemas.android.com/apk/res/android">
        <Button
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text=""/>
        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text=""/>
    </merge>
    
    展开全文
  • ViewStub使用

    2020-09-28 17:18:33
    一、ViewStub是什么? <ViewStub>标签实质上是一个宽高都为 0 的不可见 的轻量级View。通过延迟按需加载布局的方式提升页面加载速度。 二、ViewStub使用场景 某布局默认是不可见,当满足特定场景才显示。...
  • ViewStub你真的了解吗

    2019-11-21 12:53:43
    01.什么是ViewStub 02.ViewStub构造方法 03.inflate()方法解析 04.WeakReference使用 05.ViewStub为何无大小 06.ViewStub为何不绘制 07.可以多次inflate()吗 08.ViewStub不支持merge 09.ViewStub使用场景 10....
  • ViewStub的奇淫巧技

    2018-10-03 23:36:11
    昨天写了个约束布局的博客,写了很久,虽然文章写得还是不尽人意,但真的感觉到对约束布局的理解更加清晰了。 众所周知,include可以复用布局,...使用ViewStub标记的View或ViewGroup在UI初始化时并不会消耗性能...
  • 针对这种情况,谢天谢地,Android还为我们提供了一个特别的构件——ViewStub,它可以使你充分享受的好处而不会造成无用View的浪费。 ViewStub是一个看不见的,轻量级的View。它没有尺寸,也不会绘制以及以某种形式...
  • 文章目录1.ViewStub的简单使用1.1 简单说明1.2 简单示例2.结合源码分析问题2.1 第二次调用inflate()加载会抛出空指针异常2.2 第二次通过调用setVisibility()加载也会抛出空指针异常2.3 监听事件的设置 1.ViewStub的...
  • 本文档福利发放... 里面是android代码,实现的是动态添加控件和viewstub使用的例子。东西比较简单,仅适合初学者。

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 9,767
精华内容 3,906
关键字:

viewstub