Android UI布局优化之ViewStub[通俗易懂]

Android UI布局优化之ViewStub[通俗易懂]尊重原创,转载请注明出处:http://blog.csdn.net/a740169405/article/details/50351013前言:在设计模式的单利模式中,懒汉式和饿汉式是其中两种。一种是在类被加载的时候就完成单例对象的初始化,一种是在需要使用该单例的时候才初始化。在android的视图设计中,同样需要使用的这样的设计模式。这样的视图加载起来需要耗费很多的时间。在这几…

大家好,又见面了,我是你们的朋友全栈君。

尊重原创,转载请注明出处:http://blog.csdn.net/a740169405/article/details/50351013

前言:

在设计模式的单利模式中,懒汉式和饿汉式是其中两种。
一种是在类被加载的时候就完成单例对象的初始化,一种是在需要使用该单例的时候才初始化。
在android的视图设计中,同样需要使用的这样的设计模式。
这样的视图加载起来需要耗费很多的时间。在这几百个视图里面,可能有部分视图是在点击某一按钮也就是并不是马上加载,
而是延迟到要使用的时候才加载这部分视图。也就是类似于单例模式中的懒加载。

特性:

1.  ViewStub是一个继承了View类的视图。
2.  ViewStub是不可见的,实际上是把宽高都设置为0
3.  可以通过布局文件的android:inflatedId或者调用ViewStub的setInflatedId方法为懒加载视图的跟节点设置ID
4.  ViewStub视图在首次调用setVisibility或者inflate方法之前,一直存在于视图树中
5.  只需要调用ViewStub的setVisibility或者inflate方法即可显示懒加载的视图
6.  调用setVisibility或者inflate方法之后,懒加载的视图会把ViewStub从父节点中替换掉
7.  ViewStub的inflate只能被调用一次,第二次调用会抛出异常,setVisibility可以被调用多次,但不建议这么做(后面说原因)
8.  为ViewStub赋值的android:layout_属性会替换待加载布局文件的根节点对应的属性
9.  inflate方法会返回待加载视图的根节点

使用:

我在一个activity上放置了一个按钮,点击后加载懒加载的视图。

Activity布局文件定义my_sub_activity.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:orientation="vertical">

    <Button  android:onClick="onClick" android:layout_width="match_parent" android:layout_height="wrap_content" android:text="加载视图"/>

    <ViewStub  android:id="@+id/stub" android:inflatedId="@+id/subTree" android:layout="@layout/my_sub_tree" android:layout_width="wrap_content" android:layout_height="wrap_content" />

</LinearLayout>

其中android:inflatedId指定了懒加载视图跟节点的ID。android:layout指定了懒加载的视图。android:layout_width和android:layout_height分别指定了懒加载视图的宽和高。

懒加载布局文件my_sub_tree.xml:

<?xml version="1.0" encoding="utf-8"?>
<TextView xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:background="#ffffff" android:gravity="center" android:padding="10dip" android:text="懒加载视图" android:textColor="#000000" android:textSize="22sp">
</TextView>

懒加载视图里只有一个TextView(这里只是做测试,正常情况下这里应该是一个复杂的视图)。

ViewStubActivity的代码:

public class ViewStubActivity extends Activity implements View.OnClickListener { 
   

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.my_sub_activity);
    }

    @Override
    public void onClick(View v) {
        // 这里调用的是inflate方法,当然,也可以调用setVisibility方法(但是不建议这么做)
        // 只能点击一次加载视图按钮,因为inflate只能被调用一次
        // 如果再次点击按钮,会抛出异常"ViewStub must have a non-null ViewGroup viewParent"
        ((ViewStub) findViewById(R.id.stub)).inflate();
    }
}

代码里设置了布局,并在点击后查到到ViewStub对象,并加载视图。

下面看看加载视图前后的对比图:
加载前 加载后

为了说明视图树在加载前后的对比,我使用hierarchyviewer视图树查看工具,做了一个前后对比图:
加载前视图树:
加载前视图树

加载后视图树:
加载后视图树

从上面的两个视图树中我们明显发现,ViewStub节点被TextView替换。
也就是说,在调用inflate方法之前,ViewStub一直存在于视图树中,当调用inflate之后,ViewStub被加载的视图替换,到此,ViewStub的作用完成,之后ViewStub可能被内存回收(如果没有声明成成员变量的话,也就是没有强引用)

源码解析:

下面针对ViewStub的特性对源码进行解析:
特性一:ViewStub是一个继承了View类的视图。

public final class ViewStub extends View {

特性二:ViewStub是不可见的,实际上是把宽高都设置为0

@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在计算的时候,为自己请求的宽高都为0,并重写了绘制相关的方法,但不做任何事情。

特性三:可以通过布局文件的android:inflatedId或者调用ViewStub的setInflatedId方法为懒加载视图的跟节点设置ID(如果跟视图未设置ID的话)

public ViewStub(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
    TypedArray a = context.obtainStyledAttributes(
            attrs, com.android.internal.R.styleable.ViewStub, defStyleAttr, defStyleRes);
    // 通过自属性inflatedId来获取加载的视图跟节点ID,默认返回NO_ID,也就是-1,代表没有赋值id
    mInflatedId = a.getResourceId(R.styleable.ViewStub_inflatedId, NO_ID);
    // 需要加载的视图资源ID
    mLayoutResource = a.getResourceId(R.styleable.ViewStub_layout, 0);

    a.recycle();

    a = context.obtainStyledAttributes(
            attrs, com.android.internal.R.styleable.View, defStyleAttr, defStyleRes);
    // 为自己赋值ID,没有则赋值为-1
    mID = a.getResourceId(R.styleable.View_id, NO_ID);
    a.recycle();

    // 初始化视图
    initialize(context);
}

private void initialize(Context context) {
    mContext = context;
    // 设置视图不可见
    setVisibility(GONE);
    // 设置当前视图不可绘制
    setWillNotDraw(true);
}

初始化的时候,从配置文件中取出了inflatedId和待加载的资源文件id以及自身的id,
最后,调用了initialize将自身设置为不可见,并设置为不可重绘,最大限度减少资源占用。

最后看看什么时候ViewStub执行加载视图操作:
首先是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() {
    // 拿到父节点
    final ViewParent viewParent = getParent();

    if (viewParent != null && viewParent instanceof ViewGroup) {
        // 判断父节点不为空,并且是容器,则进行视图加载
        if (mLayoutResource != 0) {
            // 必须在布局文件中,或者是调用setLayoutResource方法设置待加载的视图资源文件ID
            final ViewGroup parent = (ViewGroup) viewParent;
            final LayoutInflater factory;
            // mInflater是外部设置进来的,通过setLayoutInflater方法设置
            if (mInflater != null) {
                factory = mInflater;
            } else {
                // 如果外部未设置视图加载器,初始化
                factory = LayoutInflater.from(mContext);
            }
            // 加载视图,得到视图根节点
            final View view = factory.inflate(mLayoutResource, parent,
                    false);

            if (mInflatedId != NO_ID) {
                // 如果有设置inflateId,则赋值给根节点(如果根节点自己有id,会被覆盖)
                view.setId(mInflatedId);
            }

            // 得到ViewStub在父节点中的位置
            final int index = parent.indexOfChild(this);
            // 从父节点中移除ViewStub(到此,ViewStub从视图树种移除)
            parent.removeViewInLayout(this);

            // 得到ViewStub在布局文件中定义的android:layout_*的属性
            final ViewGroup.LayoutParams layoutParams = getLayoutParams();
            // 将懒加载视图添加到ViewStub的父节点(到此,ViewStub被完全替换)
            if (layoutParams != null) {
                parent.addView(view, index, layoutParams);
            } else {
                parent.addView(view, index);
            }

            // 将懒加载的视图使用弱引用进行引用(给setVisibility方法使用,后面会讲)
            mInflatedViewRef = new WeakReference<View>(view);

            // 视图加载成功后调用回调方法。
            if (mInflateListener != null) {
                mInflateListener.onInflate(this, view);
            }

            // 返回加载后布局文件的根节点
            return view;
        } else {
            // 如果未设置layoutResource也就是待加载的视图,则抛出异常
            throw new IllegalArgumentException("ViewStub must have a valid layoutResource");
        }
    } else {
        // 如果ViewStub的父节点为空,因为ViewStub成功执行inflate方法后
        // 会调用parent.removeViewInLayout(this);将自己从父节点移除
        // 所以ViewStub的inflate只能调用一次
        throw new IllegalStateException("ViewStub must have a non-null ViewGroup viewParent");
    }
}

ViewStub的inflate方法简要的讲就是把自己从父亲从移除,把待加载的视图加入到父节点中,
并把自己所有的layout属性给待加载的视图,
什么是layout属性呢,也就是下面以”android:layout_”打头的属性:
如android:layout_width以及layout_height,
所以这里大家需要小心自己的待加载视图的根节点的android:layout_属性被替换掉。

<ViewStub  android:id="@+id/stub" android:inflatedId="@+id/subTree" android:layout="@layout/my_sub_tree" android:layout_width="wrap_content" android:layout_height="wrap_content" />

接着我们有提到,调用ViewStub的setVisibility也可以加载待加载视图:

public void setVisibility(int visibility) {
    if (mInflatedViewRef != null) {
        // 如果对待加载视图的软引用不为空,说明已经执行过inflate方法了
        // 因为在inflate方法执行成功后有对其赋值
        View view = mInflatedViewRef.get();
        if (view != null) {
            // 如果引用的视图未被垃圾回收器回收,则设置其可见性
            view.setVisibility(visibility);
        } else {
            // 如果引用的视图已经被垃圾回收器回收,则抛出异常
            // 这也就是为什么setVisibility可以调用多次,但是并不推荐这样做的原因
            throw new IllegalStateException("setVisibility called on un-referenced view");
        }
    } else {
        // 如果弱引用对象未初始化,则说明未调用inflate
        // 设置自身可见性
        super.setVisibility(visibility);
        if (visibility == VISIBLE || visibility == INVISIBLE) {
                // 如果传入的是VISIBLE或者INVIBLE,则调用inflate加载视图
            inflate();
        }
    }
}

另外ViewStub还提供了一系列方法,供用户设置属性:

/** 获取待加载视图的根节点ID */
public int getInflatedId() {
    return mInflatedId;
}

/** 设置待加载视图的根节点ID */
public void setInflatedId(int inflatedId) {
    mInflatedId = inflatedId;
}

/** 获取待加载视图的资源文件ID */
public int getLayoutResource() {
    return mLayoutResource;
}

/** 设置待加载视图的资源文件ID */
public void setLayoutResource(int layoutResource) {
    mLayoutResource = layoutResource;
}

/** 设置布局加载器 */
public void setLayoutInflater(LayoutInflater inflater) {
    mInflater = inflater;
}

总结

  1. ViewStub标签需要必须通过android:layout属性指定待加载的视图资源文件ID,否则会抛异常(在inflate方法被调用前,通过setLayoutResource也可以设置待加载的视图资源文件ID,但不建议这样做)。
  2. ViewStub标签的所有android:layout_打头的属性,都会替换待加载视图的跟布局对应属性
  3. 最好通过ViewStub的inflate方法加载视图,该方法会返回视图根节点。
  4. inflate方法只能调用一次,不建议通过setVisibility加载视图
  5. 如果需要通过findViewById查找待加载视图中的节点,需要在inflate方法执行之后,否则会找不到

关于UI布局的优化,还有include和merge两种方式,大家可以参阅:Android 布局优化之include与merge

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

发布者:全栈程序员-用户IM,转载请注明出处:https://javaforall.cn/153990.html原文链接:https://javaforall.cn

【正版授权,激活自己账号】: Jetbrains全家桶Ide使用,1年售后保障,每天仅需1毛

【官方授权 正版激活】: 官方授权 正版激活 支持Jetbrains家族下所有IDE 使用个人JB账号...

(0)
blank

相关推荐

  • 权重衰减(weight decay)与学习率衰减(learning rate decay)

    权重衰减(weight decay)与学习率衰减(learning rate decay)“微信公众号”1.权重衰减(weightdecay)L2正则化的目的就是为了让权重衰减到更小的值,在一定程度上减少模型过拟合的问题,所以权重衰减也叫L2正则化。1.1L2正则化与权重衰减系数L2正则化就是在代价函数后面再加上一个正则化项:其中C0代表原始的代价函数,后面那一项就是L2正则化项,它是这样来的:所有参数w的平方的和,除以训练集的样本大小n。λ就是正则项系数,权衡正则项与C0项的比…

  • mysql数据类型tinyint_mysql字段类型长度

    mysql数据类型tinyint_mysql字段类型长度在MySQL的数据类型中,Tinyint的取值范围是:带符号的范围是-128到127。无符号的范围是0到255(见官方《MySQL5.1参考手册》http://dev.mysql.com/doc/refman/5.1/zh/column-types.html#numeric-types)。Tinyint占用1字节的存储空间,即8位(bit)。那么Tinyint的取值范围怎么来的呢?我们先看无符号…

  • navicat mac激活码【2021.7最新】

    (navicat mac激活码)2021最新分享一个能用的的激活码出来,希望能帮到需要激活的朋友。目前这个是能用的,但是用的人多了之后也会失效,会不定时更新的,大家持续关注此网站~IntelliJ2021最新激活注册码,破解教程可免费永久激活,亲测有效,下面是详细链接哦~https://javaforall.cn/100143.html…

  • 免费的uml建模工具「建议收藏」

    Bullmind-uml建模工具,它是一个免费基于Web的绘图软件,支持UML,流程图,思维导图和许多其他图表类型,Bullmind-uml建模工具附带了一个功能强大且易于使用的图表编辑器,可以快速,顺畅地在线创建任何类型的图表。Bullmind-uml建模工具地址:https://www.bullmind.com/转载于:https://blog.51cto.com/14125675/2388…

  • matlab插值实验目的,matlab插值实验报告数学实验.doc

    matlab插值实验目的,matlab插值实验报告数学实验.docmatlab插值实验报告数学实验.doc新乡学院数学与信息科学系实验报告实验项目名称插值实验所属课程名称数学实验实验类型综合性实验实验日期班级学号姓名成绩一、实验概述【实验目的】掌握用MATLAB插值的方法,了解拉格朗日插值、线性插值、样条插值的基本思想,了解三种网格节点数据的插值方法的基本思想,了解掌握用MATLAB计算一维差值和二维插值的方法。【实验原理】拉格朗日LAGRANGE插值。已知函…

  • MJKDZ PS2手柄控制OskarBot小车(一):Arduino串口发送数据

    MJKDZ PS2手柄控制OskarBot小车(一):Arduino串口发送数据MJKDZPS2手柄控制OskarBot小车(一):Arduino串口发送数据【目录】    -1、无线通信模块设置        -1.1设置参数        -1.2调试步骤    -2、按键与通信格式        -2.1PS2按键定义        -2.2发送数据格式    -3、源代码        -3.1串口手…

发表回复

您的电子邮箱地址不会被公开。

关注全栈程序员社区公众号