android知識點——include、merge和ViewStub的布局優化

先扯兩句

上次寫的部分主要還是一些封裝的抽象方法,這部分只是單純的為我這種懶漢提供了便利罷了,而本次寫的內容呢,則是對Title的封裝,不過這篇是我自己寫的title封裝,并沒有使用Toolbar,也不是閑得沒事干,之前使用Toolbar時UI要求title下邊加上一條1px的分割線,結果Toolbar的左側出現了16dp左右的空白無法處理,沒找到解決方案就放棄了Toolbar的使用。過些時間我會好好研究一下Toolbar,畢竟除去這點還是不錯的,畢竟自己封裝,對于我這種懶漢來說還是太麻煩了不是。而關于上述的情況,如果大家誰知道如何解決方法也歡迎分享,小老兒不勝感激。

好了,閑言少敘,老規矩還是先上我的Git。
MyBaseApplication (https://github.com/BanShouWeng/MyBaseApplication)
并給大家展示個神器,叫Android知識點——目錄,好了,閑言少敘,下面進入正題。

正文

時間已經過去好久了,不知道大家還記得我之前的封裝嗎?好吧,反正我是忘得差不多了,只能重新查了查之前的博客。
首先是創建一個title_layout.xml存放title布局:

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="@dimen/title_height">

    <RelativeLayout
        android:id="@+id/base_bg"
        android:layout_width="match_parent"
        android:layout_height="@dimen/title_height"
        android:background="@color/blue">

        <ImageView
            android:id="@+id/base_back"
            android:layout_width="50dp"
            android:layout_height="50dp"
            android:padding="@dimen/size_13"
            android:src="@mipmap/back"
            android:tint="@android:color/white" />

        <TextView
            android:id="@+id/base_title"
            android:layout_width="wrap_content"
            android:layout_height="match_parent"
            android:layout_centerInParent="true"
            android:gravity="center"
            android:text="@string/title"
            android:textColor="@android:color/white"
            android:textSize="@dimen/size_20" />


        <ImageView
            android:id="@+id/base_right_icon2"
            android:layout_width="50dp"
            android:layout_height="50dp"
            android:layout_toLeftOf="@+id/base_right_icon1"
            android:contentDescription="@string/second_function_key"
            android:padding="@dimen/size_13"
            android:src="@mipmap/add"
            android:tint="@android:color/white"
            android:visibility="gone" />

        <ImageView
            android:id="@+id/base_right_icon1"
            android:layout_width="50dp"
            android:layout_height="50dp"
            android:layout_alignParentRight="true"
            android:contentDescription="@string/first_function_key"
            android:padding="@dimen/size_13"
            android:src="@mipmap/more"
            android:tint="@android:color/white"
            android:visibility="gone" />

        <TextView
            android:id="@+id/base_right_text"
            android:layout_width="50dp"
            android:layout_height="50dp"
            android:layout_alignParentRight="true"
            android:gravity="center"
            android:text="@string/make_sure"
            android:textColor="@android:color/white"
            android:textSize="@dimen/size_17"
            android:visibility="gone" />
    </RelativeLayout>
</RelativeLayout>

隨后在BaseActivity的布局文件activity_base.xml中添加如下布局:

<include layout="@layout/title_layout"/>

就完成title的添加,不過在項目中,有些時候并不需要title,或者是需要根據UI設計,去創建一些比較復雜的title,這個時候,就需要把這個刻板的title隱藏起來。因此這里便給title添加了一個id

<include 
    android:id="@+id/base_title_layout"
    layout="@layout/title_layout"/>

隨后在BaseActivity中添加了如下方法:

/**
 * 隱藏頭布局
 */
public void hideTitle() {
    if (baseTitle == null) {
        baseTitle = getView(R.id.base_title_layout);
    }
    baseTitle.setVisibility(View.GONE);
}

這樣就實現了上述的要求,可是雖然這里加了隱藏,但是大家都知道,實際上這個title的布局資源缺已經加載了,所以在這些頁面中,如果依舊這么使用的話,就會造成資源的浪費。因此,在這次修改中,我將這個部分也做了修改,而使用到的控件就是ViewStub。
(其中getView方法看過我之前博客的應該知道,就是封裝的findViewById方法,大家可以直接使用findViewById替換,只是需要強轉一下類型即可)

布局優化

說起ViewStub就不得不先說說重用布局文件(也就是傳說中的布局優化),其實網上可以查到大量的文章,而我卻是其中理解的相對淺顯的,所以如果大家想要具體了解的話,郭霖郭神的 Android最佳性能實踐(四)——布局優化技巧
,而我這里呢,而我呢,則根據自己的應用簡單說兩句就好了(無奈我這話癆的毛病,大家就忍耐一下這段“狗尾續貂”吧)

include

使用

include一看就知道是英語單詞,所以想知道他是做什么的,最簡單的就是查查翻譯

image

除了那個包住感覺有點不靠譜以外,其他的意思還都差不多,那就簡單了,我就理解為它的作用就是將關聯布局內的所有控件都包括在當前include的位置。而既然可以把其他布局引用過來,那樣自然也就復用了控件,從而優化了布局代碼。
就比如上述我之前的封裝,就是單獨封裝了title布局,然后在BaseActivity以及BaseFragment中進行了復用。而因為這個布局文件已經包含在了這個Iinclude標簽下,也就相當于其中的所有控件都在當前的這個頁面中。因此,在使用其中的控件時,就與當前布局中的其他控件一樣,直接根據ID獲取即可。當然,在我封裝的這個框架里,直接調用getView即可。

xml:

<include 
    android:id="@+id/base_title_layout"
    layout="@layout/title_layout"/>

java:

/**
 * 隱藏頭布局
 */
public void hideTitle() {
    if (baseTitle == null) {
        baseTitle = getView(R.id.base_title_layout);
    }
    baseTitle.setVisibility(View.GONE);
}
注意事項

有些東西就是這樣,在提供方便的同時,自然就會出現一些隱患,而在使用include的時候,自然也有需要注意的地方。

  1. include在沒有設置約束參數的時候,會自動根據layout引入的去定義。而當我們需要更改的時候,則需要復寫這些約束參數即可,這也就是為什么我們常用的控件必須填寫“android:layout_width”與“android:layout_height”兩個參數,而當使用include的時候則不需要,因為include復用布局的最外層布局已經夠約束好了其長寬,至于有什么可以設置,郭神的博客中已經說明了:

非layout屬性則無法在<include>標簽當中進行覆寫。另外需要注意的是,如果我們想要在<include>標簽當中覆寫layout屬性,必須要將layout_width和layout_height這兩個屬性也進行覆寫,否則覆寫效果將不會生效。

  1. 在同一個布局中,大家應該都會出現過id設置重復的時候吧,當然,不會再程序運行中出問題,那是因為在程序運行之前,就AS已經告訴我們了,那樣做是不對滴。可是在使用include的時候,AS還無法那么職能的判斷出究竟id是否設置重復,因此只有在運行過程中才會出現錯誤(會調用include中的控件,而忽略當前布局中同id的控件),所以使用時一定要慎之又慎。

merge

同樣,看到這英文還是查一下什么意思


這里寫圖片描述

在使用git或者svn做版本控制的時候呢,我們稱其為“和并”,這部分我在良秋 Android 布局優化之include與merge中查到了如下介紹:

merge翻譯成中文是合并的意思,在Android中通過使用merge能夠減少視圖的節點數,從而減少視圖在繪制過程消耗的時間,達到提高UI性能的效果。

對于一個菜鳥來說,看到這么玄乎其玄的解釋,第一反應就是蒙,如果是個勤懇好學的菜鳥,下一反應應該是去查一下什么是“節點”。而懶點的估計就直接放棄了。實際上,對于我來說,就是所謂android開發中的常用布局方式。
下面就開始研究這東西怎么用,說實話,就我這種菜鳥,還真沒什么機會用到merge,不是不知道怎么用,其一真的是使用環境沒有include或者后面要說到的ViewStub那么清晰,再者就是使用的條件比較苛刻,最后就是在該使用的時候,估計merge早不知道被我們忘哪去了。
下面先列舉一下良秋列舉的merge注意事項:

  1. merge必須放在布局文件的根節點上;
  2. merge并不是一個ViewGroup,也不是一個View,它相當于聲明了一些視圖,等待被添加。
  3. merge標簽被添加到A容器下,那么merge下的所有視圖將被添加到A容器下。
  4. 因為merge標簽并不是View,所以在通過LayoutInflate.inflate方法渲染的時候, 第二個參數必須指定一個父容器,且第三個參數必須為true,也就是必須為merge下的視圖指定一個父親節點。
  5. 如果Activity的布局文件根節點是FrameLayout,可以替換為merge標簽,這樣,執行setContentView之后,會減少一層FrameLayout節點。
  6. 自定義View如果繼承LinearLayout,建議讓自定義View的布局文件根節點設置成merge,這樣能少一層結點。
  7. 因為merge不是View,所以對merge標簽設置的所有屬性都是無效的。

具體的內容參見良秋的博客說,有詳細的說明,這里我就不加以贅述了,只是說兩點自己使用中走的彎路,算是對上述內容的一個佐證吧。

這里寫圖片描述

上圖就是對圖1的佐證,也就是在布局的時候Android Studio會給予的提示,但是對于一些開發是不那么嚴謹的,可能注意不到merge的黃色提示,畢竟ImageView中沒有設置android:contentDescription(這個屬性是方便一些生理功能有缺陷的人使用應用程序的,比如一些視力有障礙的用戶,如果用戶安裝了輔助瀏覽工具比如TalkBack,TalkBack就會大聲朗讀出用戶目前正在瀏覽的內容。TextView控件TalkBack可以直接讀出里面的內容(contentDescription的值),告訴用戶這個圖片到底是什么)會出現這類提示,沒有使用到的一些布局控件也會出現黃色提示(添加一個為沒有子控件,且沒有id的RelativeLayout)等。畢竟大多數出現這個提示是因為不建議,而不是不能用。
所以我也任性的在測試機上跑了一下,說不定能用呢,結果:

這里寫圖片描述

于是我不得不死心了。

作為一個“生命不息,逗逼不止”的人,在使用中自然不可能只吃這一次虧,這次我創建了一個merge_test.xml布局文件:

<?xml version="1.0" encoding="utf-8"?>
<merge xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <Button
        android:id="@+id/merge_btn"
        android:layout_height="wrap_content"
        android:layout_width="match_parent"/>
</merge>

這次是在根節點上了吧,創建之后自然要使用了:

@SuppressLint("InflateParams")
    @Override
    protected void findViews() {
        view = (Button) LayoutInflater.from(context).inflate(R.layout.merge_test, null).findViewById(R.id.merge_btn);
        view.setOnClickListener(this);
    }

    @Override
    public void onClick(View v) {
        switch (v.getId()) {
            case R.id.merge_btn:
                view.setText(count++ + "");
                break;
        }
    }

Shift + F10,merge,是時候展示你真正的技術了:

這里寫圖片描述

mmp,跪求我心里的陰影面積!難倒這就進入了一條死路嗎!!!好吧,作為一個行業菜鳥,這個時候不得不去翻看大神們的博客以求幫助。

<merge>標簽是作為<include>標簽的一種輔助擴展來使用的,它的主要作用是為了防止在引用布局文件時產生多余的布局嵌套。

既然郭神已經在博客中這么說了,作為小菜鳥,暫時先這么用著,至于有沒有其他的玩法,就看日后的使用與積累了。

ViewStub

看到這個詞呢,這里就不翻譯了,實在是字面理解真不知道它是做什么的。所以這次就直接說如何使用了。
其實在用法上,ViewStub與include還真有些類似,都是定義好的xml布局,然后在使用的時候,通過控件直接引用布局文件:

<ViewStub
    android:id="@+id/base_title_layout"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:layout="@layout/title_layout" />

大家在看看include的布局:

<include 
    android:id="@+id/base_title_layout"
    layout="@layout/title_layout"/>

是不是區別并不大,在這最基本的引用上:

  1. include引用布局時使用的是layout="@layout/title_layout",而ViewStub使用的是android:layout="@layout/title_layout";
  2. ViewStub必須設置android:layout_width與android:layout_height,而從上面include中已經說明,include是不必須設置這兩個屬性的。
  3. 至于使用的環境,ViewStub是一些不常使用到的地方,這樣在不使用的時候,ViewStub可以節省資源,當需要使用的時候,通過代碼解析一下,便可以得到對應的布局;而include則更偏向于同一個布局的在多個頁面中的復用。

而關于ViewStub的是引用方法,在良秋 Android UI布局優化之ViewStub中,關于有如下介紹:

  1. ViewStub是一個繼承了View類的視圖。
  2. ViewStub是不可見的,實際上是把寬高都設置為0
  3. 可以通過布局文件的android:inflatedId或者調用ViewStub的setInflatedId方法為懶加載視圖的跟節點設置ID
  4. ViewStub視圖在首次調用setVisibility或者inflate方法之前,一直存在于視圖樹中
  5. 只需要調用ViewStub的setVisibility或者inflate方法即可顯示懶加載的視圖
  6. 調用setVisibility或者inflate方法之后,懶加載的視圖會把ViewStub從父節點中替換掉
  7. ViewStub的inflate只能被調用一次,第二次調用會拋出異常,setVisibility可以被調用多次,但不建議這么做
  8. 為ViewStub賦值的android:layout_屬性會替換待加載布局文件的根節點對應的屬性
  9. inflate方法會返回待加載視圖的根節點

這已經基本上說明了ViewStub的用法以及相關的原理,反正至少作為懶漢的我是沒辦法說的更明白了。當然,想了解更具體的原理的,可以去良秋的這篇博客中仔細學習,而單純的使用而言,則不需要那么麻煩。
上面已經給出了ViewStub在xml文件中如何引用,不過往上翻多費勁啊,還是在下面再貼一下吧:

<ViewStub
    android:id="@+id/base_title_layout"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:layout="@layout/title_layout" />

總共四行屬性(具體參數可以修改):

  1. android:layout="@layout/title_layout":所要引用布局的layout xml文件;
  2. android:layout_width="match_parent" android:layout_height="wrap_content":所要引用布局的長寬設置;
  3. android:id="@+id/base_title_layout":需要解析時查找對應ViewStub的id。

誰都別問我為什么四行屬性,我這里只寫了三點啊,至于強迫癥患者的話,那我只能非常抱歉的說一句————你管我!!!
xml部分完成了,下面就該進行java部分了,也就是ViewStub的inflate,其實也很簡單:

/**
 * Title ViewStub
 */
private ViewStub titleStub;

/**
 * 控件初始化
 */
protected void initBaseView() {
    if (titleStub == null) {
        titleStub = getView(R.id.base_title_layout);
        titleStub.inflate();
    }
}

想要把ViewStub解析,總共分幾步?四步!

  1. 創建ViewStub對象;
  2. 判斷ViewStub是否已經解析過了,若沒有進行解析;
  3. 根據id找到需要解析的ViewStub;
  4. viewStub.inflate()執行解析操作;

如此,viewStub就展示到我們的界面上了,再對其中的各個控件進行操作,實際上就與include相同了,例如:

/**
 * 設置標題
 *
 * @param title    標題的文本
 * @param showBack 是否顯示返回鍵
 */
public void setTitle(String title, boolean showBack) {
    initBaseView();
    ((TextView) getView(R.id.base_title)).setText(title);
    if (showBack) {
        baseBack = getView(R.id.base_back);
        baseBack.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                finish();
            }
        });
    }
}

最后的最后,思考了很久怎么寫結尾的我,終于還是抵不過懶漢性格,還是直接摘一段郭神的內容放到這里吧:

另外需要提醒大家一點,ViewStub所加載的布局是不可以使用<merge>標簽的,因此這有可能導致加載出來的布局存在著多余的嵌套結構,具體如何去取舍就要根據各自的實際情況來決定了,對于那些隱藏的布局文件結構相當復雜的情況,使用ViewStub還是一種相當不錯的選擇的,即使增加了一層無用的布局結構,仍然還是利大于弊。

原本是打算將BaseActivity的封裝放到這里的,不過寫得篇幅過長,而且布局優化的部分總歸是要寫的,所以這里就將布局優化的部分單獨提出來寫了一篇,而BaseActivity封裝的內容只能放到后一篇再去寫了。

附錄

《一個Android工程的從零開始》- 目錄

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容