性能优化之布局优化篇一 使用ViewStub视图

一、概述
ViewStub是一个不显示且不占用布局空间的视图。ViewStub需要指定一个布局layout,在ViewStub初始化时,其指定的layout并未初始化。只有当ViewStub的setVisibility(VISIBLE/INVISIBLE)或inflate()方法被调用,ViewStub所指向的布局layout才会实例化,该layout会使用ViewStub的布局参数LayoutParam,被添加到ViewStub的父视图中来替代ViewStub,即ViewStub将不在布局中存在。

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

二、使用场景
在开发中,会遇到这样的情况,需要在运行时动态的决定显示某个视图。通常的做法是,将可能会用到的所有视图都添加到布局中,之后在代码中通过View.VISIBILITY和View.GONE来改变视图的可见性。即使在xml布局中为视图设置了android:visibility="gone",该视图仍然会被实例化,这样做带来的缺点是增加了视图加载的时间和内存的消耗。

ViewStub的一个典型的使用案例,当我们启动一个页面,如果请求到数据就显示内容视图,遇到网络出错时显示网络异常视图。

添加网络异常视图net_error_view.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:gravity="center"
              android:orientation="vertical">

    <ImageView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:src="@drawable/net_icon"/>

    <Button
        android:id="@+id/button"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="网络异常\n点击屏幕重新加载"
        android:textColor="#666666"
        android:textSize="16sp"/>
</LinearLayout>

Activity的布局分两部分,一个内容视图,一个网络异常视图。网络异常视图使用ViewStub,并将layout指定为上面的net_error_view。
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
             android:layout_width="match_parent"
             android:layout_height="match_parent"
             android:background="#ffffff">

    <!-- 内容视图 -->
    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical">
    </LinearLayout>

    <!-- 网络异常视图 -->
    <ViewStub
        android:id="@+id/viewstub"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:inflatedId="@+id/inflatedview"
        android:layout="@layout/net_error_view"/>
</FrameLayout>

在Activity中,如果需要显示网络异常视图,使用如下代码。
private View netErrorView;

ViewStub viewStub = (ViewStub) findViewById(R.id.net_error);
netErrorView = viewStub.inflate();

viewStub.setVisibility(View.VISIBLE);
netErrorView = findViewById(R.id.inflatedview);


当ViewStub的inflate()方法被调用时,其指定的@layout/net_error_view被实例化并显示出来。此时,ViewStub的android:inflatedId即inflatedview,就成为net_error_view视图根布局的id。这里我们将网络异常视图对象保存到成员变量netErrorView中,之后可以直接操作netErrorView,不用再调用findViewById()。


三、源码分析

ViewStub继承自View类,所以本质上还是一个View。
public final class ViewStub extends View {
}

ViewStub视图在加载时,为何没有显示,且指定的layout没有被初始化,原因在于ViewStub重写了View类的onMeasure()和draw()方法。在onMeasure()方法中,调用setMeasuredDimension,传递的参数measuredWidth和measuredHeight都是0。draw()方法的内部实现被置空,也就不会绘制了。
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    setMeasuredDimension(0, 0);
}

@Override
public void draw(Canvas canvas) {
}

ViewStub除了拥有父类View的属性外,新增了android:inflatedId和android:layout,这两个属性在构造方法中完成初始化。
private int mInflatedId;
private int mLayoutResource;

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();

    setVisibility(GONE);
    setWillNotDraw(true);
}
获取到属性inflatedId和layout的值后分别赋值给成员变量mInflatedId和mLayoutResource。并在最后调用了setWillNotDraw(true),只有View就不会绘制自己。

当ViewStub的inflate()方法被调用时,指定的layout被实例化显示出来,并返回。我们来看看inflate()方法的实现。
public View inflate() {
    final ViewParent viewParent = getParent();

    if (viewParent != null && viewParent instanceof ViewGroup) {
        if (mLayoutResource != 0) {
	    // 获取ViewStub的父视图,强转为ViewGroup类型
            final ViewGroup parent = (ViewGroup) viewParent;
	    // 获取LayoutInflater对象
            final LayoutInflater factory;
            if (mInflater != null) {
                factory = mInflater;
            } else {
                factory = LayoutInflater.from(mContext);
            }
	    // 实例化mLayoutResource,赋值给view变量
            final View view = factory.inflate(mLayoutResource, parent,
                    false);

            // 如果设置了mInflatedId,赋值给view的id
            if (mInflatedId != NO_ID) {
                view.setId(mInflatedId);
            }

            // 移除ViewStub自身
            final int index = parent.indexOfChild(this);
            parent.removeViewInLayout(this);

            // getLayoutParams()获取的是ViewStub的布局参数
            final ViewGroup.LayoutParams layoutParams = getLayoutParams();
	    // 把view添加到ViewStub的父视图中
            if (layoutParams != null) {
                parent.addView(view, index, layoutParams);
            } else {
                parent.addView(view, index);
            }

            // 把view赋值给成员变量mInflatedViewRef
            mInflatedViewRef = new WeakReference<View>(view);

            if (mInflateListener != null) {
                mInflateListener.onInflate(this, view);
            }

            // 最后返回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");
    }
}
首先,使用LayoutInflater将指定的layout实例化赋值给变量view。如果设置了inflatedId,赋值给该view的id属性。然后,从ViewStub的父视图parent中移除ViewStub自身,使用ViewStub的布局参数把view添加到parent中。最后,把view赋值给成员变量mInflatedViewRef,返回view。

ViewStub的setVisibility()方法被调用时,指定的layout同样也可以被实例化。ViewStub对View中的setVisibility()方法进行了重写。
@Override
public void setVisibility(int visibility) {
    if (mInflatedViewRef != null) {// 如果ViewStub的inflate()已经被调用,执行if
        View view = mInflatedViewRef.get();
	// 此时ViewStub已经被inflate(),view已经有值,直接执行view的setVisibility()方法
        if (view != null) {
            view.setVisibility(visibility);
        } else {
            throw new IllegalStateException("setVisibility called on un-referenced view");
        }
    } else {// 如果ViewStub的inflate()还未被调用,执行else
        // 先调用父类View的setVisibility()
        super.setVisibility(visibility);
	// 如果传入的参数为VISIBLE或INVISIBLE,执行inflate()方法
        if (visibility == VISIBLE || visibility == INVISIBLE) {
            inflate();
        }
    }
}
在setVisibility()方法被调用之前,如果ViewStub的inflate()已经调用,此时成员变量mInflatedViewRef即是ViewStub指定的layout对象,直接执行view的setVisibility()方法。如果ViewStub的inflate()未被调用,先将参数传递到父类View的setVisibility(),然后判断传入的参数为VISIBLE或INVISIBLE时,执行inflate()方法,去实例化layout对象。

所以,当ViewStub的inflate()方法被调用,或setVisibility()方法被调用并传入参数VISIBLE或INVISIBLE,ViewStub所指向的布局layout会被实例化,并添加到ViewStub的父视图中显示出来。
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
Android性能优化是为了提高Android应用程序的运行效率和响应速度,以提升用户体验。有多个方面需要考虑和优化,如布局优化、绘制优化、网络优化、安装包优化、内存优化、卡顿优化、耗电优化、列表和图片优化、数据库优化、启动优化、数据结构优化和稳定性优化等。 在布局优化方面,可以通过减少布局层次、使用ConstraintLayout等来提高布局效率。绘制优化可以通过使用ViewStub延迟加载视图使用ViewHolder模式优化列表视图等来加快绘制速度。网络优化可以通过合理使用缓存、减少网络请求次数等来提高网络传输效率。安装包优化可以通过混淆代码、删除无用资源等来减小APK大小。 内存优化可以通过及时释放不再使用的资源、避免内存泄漏等措施来减少内存占用。卡顿优化可以通过使用异步加载、优化耗时操作等来提高应用的流畅度。耗电优化可以通过合理管理后台任务、优化网络请求等来降低电量消耗。 列表和图片优化可以通过使用分页加载、缓存图片等方式来提高列表和图片的加载速度和效率。数据库优化可以通过使用合适的索引、批量操作等来提高数据库的查询和写入性能。启动优化可以通过延迟初始化、减少启动时的资源加载等来加快应用的启动速度。数据结构优化可以通过选择合适的数据结构来提高数据的存储和检索效率。稳定性优化则需要通过全面的测试和错误处理来保证应用的稳定性。 综上所述,Android性能优化是一个多方面的工作,需要从各个方面入手,以提高应用程序的性能和用户体验。<span class="em">1</span><span class="em">2</span><span class="em">3</span>

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值