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