Android 使用View Gone 与 ViewStub的区别

Android 使用View Gone 与 ViewStub的区别

作者: 林子木 (wolinxuebin)

一、结论

为了部分同学迅速查找结果,所以把结论放在第一段。区别如下:

  • 设置为GONE的View不会占用布局空间,但是会进行类的初始化;如ImageView 将src设置为一个BitmapDrawable,那么该图片将会加载到内中
  • ViewStub只有在代码中进行inflate之后才会加载进来,不会占用内存

二、一个简单的内存实验

实验的布局代码如下:

<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout 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"
    tools:context=".MainActivity">

    <com.example.linxuebin.testviewstubandgone.MyImageView
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:src="@drawable/screen_bg"/>
</android.support.constraint.ConstraintLayout>

注:其中screen_bg是一张656x1167大小的png图片, MyImageView是完全继承ImageView,仅仅在关键部分打印一些信息。
通过将ImageView的visiable属性设置为visiable、invisiable、gone 和 使用viewStub得到如下内存图:
这里写图片描述
图1.1 通过将ImageView的visiable属性设置为visiable、invisiable、gone 和 使用viewStub的内存图

通过上图可以得到如下的分析结果:

  • Visiable VS InVisiable: 仅仅在Graphiscs上有差距,因为没有进行绘制操作
  • InVisiable VS Gone: 他两竟然没有如何的区别(忽略0.1的差距)
  • Gone VS ViewStub: Gone竟然比ViewStub多出3MB

Why?
下一章我们通过另外一个实验去探索这个原因可好?

三、ImageView的一些奥秘

以下是ImageView截取初始化的一段代码:

public ImageView(Context context, @Nullable AttributeSet attrs, int defStyleAttr,
            int defStyleRes) {
        super(context, attrs, defStyleAttr, defStyleRes);

        ... #省略不相干代码

        final TypedArray a = context.obtainStyledAttributes(
                attrs, R.styleable.ImageView, defStyleAttr, defStyleRes);

        final Drawable d = a.getDrawable(R.styleable.ImageView_src);
        if (d != null) {
            setImageDrawable(d);
        }

      .... #省略不相干代码
    }

发现ImageView在初始化的时候,会加载属性src指向的drawable资源。
为了获取加载的Drawable信息,我们通过写MyImageVeiw 继承ImageVeiw,重写setImageDrawable 方法,看看是否能得到相关信息。关键代码如下:

@Override
    public void setImageDrawable(@Nullable Drawable drawable) {
        super.setImageDrawable(drawable);
        if (drawable instanceof BitmapDrawable) {
            Log.d("lxb", "drawable is instanceof BitmapDrawable.");
            Bitmap bitmap = ((BitmapDrawable) drawable).getBitmap();
            if (bitmap != null) {
                Log.d("lxb", "bitmap's width = " + bitmap.getWidth()
                        + " height = " + bitmap.getHeight()
                        + " size = " + bitmap.getByteCount() + "b");
            }
        }
    }

运行程序,我们得到如下的日志信息:
这里写图片描述

hey, 这信息不就我们在第二章时候提到的png的图片信息吗?3,062,208b 换算下,不就是3MB吗? 那如何得到这个3MB的值? 默认Bitmap是以ARGB_8888的格式进行加载的,也就是一个像素用32位(4比特)进行存储,那么4 * 656 * 1167 = 3062208。所以:
ImageViewde的src 设置的图片原本尺寸越大,就越占用内存,和ImageView的大小无关。
所以,对异常UI(基本不显示的),使用ViewStub不比Gone节省内存。

四、 如何ViewStub的使用

上面说了那么多ViewStub的好处,那本章就讲讲如何使用ViewStub吧。
直接上例子,以第一章的例子为例。
将main_layout.xml改写如下:

<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout 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"
    tools:context=".MainActivity">

    <ViewStub
        android:id="@+id/front_img_view_stub"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout="@layout/activity_main_img"/>
</android.support.constraint.ConstraintLayout>

其中 acvitity_main_img.xml 如下:

<?xml version="1.0" encoding="utf-8"?>
<ImageView xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/front_bg"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:src="@drawable/screen_bg"/>

之后再代码中

private ViewStub mFrontImgViewStub;
private ImageView mFrontImg;
private void showFrontImg() {
    // 通过判断,是的ViewStub仅仅进行一次 inflate
    if (mFrontImgViewStub == null) {
        mFrontImgViewStub = findViewById(R.id.front_img_view_stub);
        mFrontImg = mFrontImgViewStub.inflate().findViewById(R.id.front_bg);
    }
    // ViewStub inflate 之后,改布局就加载到main_layout中,所以找到子布局的 id 进行操作
    if (mFrontImg.getVisibility() != View.VISIBLE) {
        mFrontImg.setVisibility(View.VISIBLE);
    }
}

注:ViewStub 的 inflate 仅仅只能调用一次

或者有人会为,我改如何隐藏该内容呢?简单!!!

private void dismissFrontImg() {
    // ViewStub inflate 之后,我们就必现要拿到被加载的layout的相关ID进行操作,而不是ViewStub的id
    if (mFrontImg != null) {
        mFrontImg.setVisibility(View.GONE);
    }
}

是不是很简单?So easy !!!

五、使用其他方法实现ViewStub

还有没有其他方法替代ViewStub的功能呢?
这个当然有:1、 直接使用代码编写layout里的内容, 2、既然ViewStub是通过inflate进行加载的,我们也可以直接使用不是吗?

1 直接使用代码编写layout里的内容:

private ViewGroup mViewContainer;
private void showFrontImgByCode() {
    if (mViewContainer == null) {
        mViewContainer = findViewById(R.id.view_container);
    }
    ImageView fontImg = new ImageView(this);
    fontImg.setImageResource(R.drawable.screen_bg);
    ViewGroup.LayoutParams layoutParams =
            new ViewGroup.LayoutParams(
                    ViewGroup.LayoutParams.MATCH_PARENT,
                    ViewGroup.LayoutParams.MATCH_PARENT);
    mViewContainer.addView(fontImg, layoutParams);
}

恩,一个ImageView还是容易搞定的,但是,一旦布局复杂,那代码量可不是一般的复杂,而且容易出错。

2、使用inflate进行加载

private void showFrontImgByInflate() {
   // 获取根view
    if (mViewContainer == null) {
        mViewContainer = findViewById(R.id.view_container);
    }
    View view = getLayoutInflater().inflate(R.layout.activity_main_img, mViewContainer);
    // view.findViewById() 获取里面的子View
}

恩,也很简单哇。好像也简单的样子。个人感觉使用ViewStub在布局文件中进行标记,比直接在代码中使用infalte更加的增加可读性。

©️2020 CSDN 皮肤主题: 编程工作室 设计师:CSDN官方博客 返回首页