天天看點

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得到如下記憶體圖:

Android 使用View 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");
            }
        }
    }
           

運作程式,我們得到如下的日志資訊:

Android 使用View Gone 與 ViewStub的差別

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