Android 你的自定義View是否比別人多了一個層級

前言

最近部門內對View的加載做了一波優化操作,主要是針對一些在特定時機才會顯示在頁面上的View進行ViewStub化改造。優化后,頁面的啟動速度確實得到了提升。這確實是一個項目優化的入手點。在做完這波優化后,我偶然間腦海中閃過一個念頭,就是我們項目中的自定義View的層級是否存在可改進的地方,于是我在閑暇之余自己寫了個小demo,并閱讀了下LayoutInflater的一些源碼,發現果然我們項目中的大部分組合自定義View都存在一層多余的層級。

“多一層外套”的組合自定義View

組合自定義View在日常開發中的引用場景應該還是相對比較多的,下面我粘上一個簡單的組合自定義View的實現:

/**
 * 簡單的一個組合自定義View,在它的中央會顯示一個TextView文本
 * 此案例的布局填充方式也是目前網上包括一些書籍描述的填充方式
 */
public class GroupNormalView extends RelativeLayout {
    
    public GroupNormalView(Context context) {
        this(context, null);
    }

    public GroupNormalView(Context context, AttributeSet attrs) {
        super(context, attrs);
        LayoutInflater.from(context).inflate(R.layout.group_normal_view, this, true);
    }
}

xml布局:

<?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="match_parent">

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_centerInParent="true"
        android:textSize="25sp"
        android:textColor="#ff0000"
        android:text="Normal" />
</RelativeLayout>

然后,我在MainActivity的布局中引入這個自定義View。

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout 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="activity.MainActivity">

    <view.GroupNormalView
        android:layout_width="match_parent"
        android:layout_height="match_parent" />
</RelativeLayout>

運行demo,當頁面打開后,我們使用AS自帶的Layout Inspector工具來查看當前頁面的布局組成。如下圖:


normal.png

當前頁面的布局內容中僅含一個GroupNormalView,在它的里面包含一層相對布局,然后還有一個TextView。看最初定義GroupNormalView的時候我直接讓它繼承了RelativeLayout,也就是說GroupNormalView本身就含有RelativeLayout的全部布局屬性了,那么現在有沒有覺著上圖中第二個箭頭所指的RelativeLayout有些多余呢?事實證明,真的很多余。下面就讓我們把這多余的一層布局給干掉。

“比基尼裝扮”的組合自定義View

其實,干掉上面所說的那一層多余的布局的方式有兩種。一種是在自定義控件的xml文件中,直接把布局根標簽改成merge。但是這種方式有一個缺點,就是在開發過程中我們無法實時的在右側preview中瀏覽到正確的布局排列樣式。因此我更傾向于接下來要說的這種方式,就是直接在布局文件中引用自定義View。
代碼實現:

/**
 * 組合自定義View,View直接在xml布局中被引用。如需遍歷布局中的控件,可重寫onFinishInflate方法
 */
public class GroupBetterView extends RelativeLayout {
    
    private TextView betterTxt;
    
    public GroupBetterView(Context context) {
        super(context);
    }

    public GroupBetterView(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    @Override
    protected void onFinishInflate() {
        super.onFinishInflate();
        betterTxt = findViewById(R.id.txt_better);
    }
}

xml布局(group_better_view):

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

    <TextView
        android:id="@+id/txt_better"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_centerInParent="true"
        android:textSize="25sp"
        android:textColor="#ff0000"
        android:text="Better" />
</view.GroupBetterView>

然后,在MainActivity的布局中使用include標簽引入自定義控件的布局:

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout 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="activity.MainActivity">
    
    <include layout="@layout/group_better_view"
        android:layout_width="match_parent"
        android:layout_height="match_parent"/>
</RelativeLayout>

同樣,運行demo,打開Layout Inspector工具,結果如下圖:


better.png

看到了吧,實現了一模一樣的效果,但是現在比之前少了一層相對布局。我們都知道,在View的繪制過程中,多一層布局的話它的繪制時間肯定就更長。試想下,如果一個頁面存在上千個View的話,如果我們采用第一種方式實現,無非是會增加頁面整體的繪制時間的。因此以后再使用組合自定義View的時候,要優先選擇第二種方式啊。

關于inflate()方法:

現在,我們只是從結果上證實了,確實第二種方式減少了一層層級,但是我們還不知道為什么會是這樣子呢。下面,我們就再來看下LayoutInflater的源碼吧。點進inflate方法,一路行走至483行:


mergeinflate.png

我們可以看到,如果發現xml布局中根標簽的名字為merge的話,會進入if條件中,而不是merge的話會進入else中,而第一種方式我們布局中的根標簽為RelativeLayout,因此我們繼續看else中的代碼,第492行,在這里創建了一個temp對象,上面的注釋寫的很清楚,這個對象就是在xml布局中的根View。然后繼續往下走,至524行如下:


addview.png

看到了吧,當root(也就是當前自定義View)不為空,并且attachRoot(我們在調用inflate方法時傳入的第三個參數)為true時,是將xml中的根View當做一個子View添加進了自定義View中,這下知道為什么第一種方式比第二種方式多了一層布局了吧!

總結

本文算是一個自定義View上使用的一個小優化,希望能夠對大家有所幫助。

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

推薦閱讀更多精彩內容

  • 【Android 自定義View】 [TOC] 自定義View基礎 接觸到一個類,你不太了解他,如果貿然翻閱源碼只...
    Rtia閱讀 4,003評論 1 14
  • 發現 關注 消息 iOS 第三方庫、插件、知名博客總結 作者大灰狼的小綿羊哥哥關注 2017.06.26 09:4...
    肇東周閱讀 12,251評論 4 61
  • 看完阿黛爾的生活,我哭濕了枕頭。迷茫成長.不說題材,而說看到兩個吵架被絕情的趕出來,沒有追和彌補,然后三年過去,阿...
    燕coco閱讀 1,026評論 0 5
  • 想起曾經的初中同學z,z的父母在她從小就離異了,把她留給了父親,因為父親愧對于沒有給她個完美的家庭,所以對她萬千寵...
    蘭漠子閱讀 234評論 0 0
  • 發心:我今不是為了我個人而聞思修,而是為了六道輪回一切如母有情眾生,愿一切如母有情眾生能夠早日離苦得樂,清凈業障,...
    曉茂閱讀 201評論 5 7