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更加的增加可读性。