VirtualView Android 實現詳解(三)—— 新增一個自定義控制元件

Longerian發表於2018-03-20

本系列文章

前文介紹了模板的基本格式、虛擬控制元件與原生控制元件混合使用的方式。本文重點在把這兩塊內容串起來介紹一下,如何實現從模板生成一個執行時的控制元件,並如何註冊一個自定義控制元件使用。

相關開源庫

Android

iOS

名詞解釋

從 XML 到執行時例項

舉個簡單的例子,在 XML 模板裡,可能會有這麼一塊控制元件的使用:

<NText
    id="1"
    text="title"
    textSize="12"
    textColor="#333333"
    layoutWidth="wrap_content"
    layoutHeight="wrap_content"
    lineSpaceMultiplier="1.1"
    lines="2"
    flag="flag_event|flag_exposure|flag_clickable"
 />
複製程式碼

這在 VirtualView 表示引用一個文字控制元件(VirtualView 內建支援的所有控制元件見文件),在《VirtualView Android實現詳解(一)—— 檔案格式與模板編譯》裡曾講過會將 XML 裡的字串等編譯成整型數值或者索引來降低解析成本。因此從在 XML 裡使用一個控制元件到執行時渲染它,就要經過一系列的轉換過程,其中有一半的過程是事先離線執行的,另一半的過程才是在客戶端裡執行時執行。以下這張圖概括了整個流程:

VirtualView Android 實現詳解(三)—— 新增一個自定義控制元件

說明一下每個步驟:

  • 先編寫 XML 檔案,如圖所示引用了一個 NText 控制元件;
  • 在 Config 檔案裡配置 NText 的標籤名及屬性的對映關係;像這種內建控制元件都已經配置好了,如果是自定義屬性和控制元件,才要操作自己新增配置;配置檔案含義參考這篇文章:VirtualView 工具大更新啦。在這個示例中,NText 標籤被編譯工具編譯成數字 7,屬性名 id、text、textSize、textColor 都被編譯成一個 hashcode 索引,真正的字串值會儲存到字串資源區;屬性值 title 也是被編譯成一個 hashcode 索引,真正的字串值會儲存到字串資源區;屬性值 12 被直接編譯成數字 12; 屬性值 #333333 被編譯成顏色值 -13421773;
  • 編譯工具根據 XML 檔案和 Config 檔案編譯出一份二進位制檔案,交給客戶端使用;
  • 客戶端初始化框架的時候會根據 id 註冊控制元件,在這個示例中 7 代表了 NativeText 類控制元件,它就用來例項化 XML 裡的 NText 標籤;
  • 最後將 XML 裡 NText 下的屬性傳給 NativeText 例項進一步用於渲染;

建立控制元件例項的過程

以建立一個 PicassoImage 為例(雖然內建了 VImage 和 NImage 兩個控制元件,但在實際業務場景中,還是使用一個自定義的圖片控制元件比較合適,這樣可以更好利用起結合圖片庫的記憶體管理、效能優化等 feature)。

目標

  • 實現一個原生 Image 控制元件,使用 Picasso 載入圖片
  • 支援繫結 url 屬性用來載入網路圖片
  • 支援繫結 degree 屬性用來旋轉圖片

1. 定義標籤名及其 id,屬性名及型別

在編譯工具裡配置檔案裡定義:

  • VIEW_ID_PicassoImage=1014,其中 PicassoImage 就是 XML 裡的標籤名,id 值為 1014,這個是自定義的,建議從 1001開始,前 1000 保留給系統使用
  • degree=Float,表示屬性名是 degree ,屬性值按 Float 型別編譯解析;
  • url=String,表示屬性名是 url,屬性值按 String 型別編譯,不過未在配置檔案裡宣告的屬性都是按 String 型別編譯的,所以可以省略

2. 定義控制元件的載體 View

取名 PicassoImageView,繼承 ImageView,實現 IView 介面,因為 demo 比較簡單,除此之外不做其他邏輯,主要實現 IView 的介面呼叫對應的系統 measure、layout 方法,因為這些方法是不能在外部呼叫的,只能通過 IView 的介面封裝一下暴露出去。

詳細程式碼:PicassoImageView.java

3. 定義控制元件 model

取名 PicassoImage,繼承 ViewBase,在建構函式裡例項化 PicassoImageView,並獲取自定義屬性的 id;

public PicassoImage(VafContext context,
        ViewCache viewCache) {
        super(context, viewCache);
        mPicassoImageView = new PicassoImageView(context.getContext());
        StringSupport mStringSupport = context.getStringLoader();
        // 這裡會取載入的模板資料裡取獲取對應的 id,第一個引數是屬性名,第二個引數應當為 false;
        urlId = mStringSupport.getStringId("url", false);
        degreeId = mStringSupport.getStringId("degree", false);
    }
複製程式碼

由於 ViewBase 本身也是實現 IView 介面的,所以複寫幾個 IView 的 measure、layout 介面,去呼叫對應的 PicassoImageView 裡的介面。在 VirtualView 體系內部,都是通過 ViewBase 物件來驅動佈局計算的,因此必須通過 IView 介面呼叫系統 View 真正的計算介面。

@Override
public void onComMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    mPicassoImageView.onComMeasure(widthMeasureSpec, heightMeasureSpec);
}

@Override
public void onComLayout(boolean changed, int l, int t, int r, int b) {
    mPicassoImageView.onComLayout(changed, l, t, r, b);
}

@Override
public void comLayout(int l, int t, int r, int b) {
    super.comLayout(l, t, r, b);
    //這一步很關鍵,否則 view 不顯示。
    mPicassoImageView.comLayout(l, t, r, b);
}
複製程式碼

剩下的主要邏輯是處理自定義屬性,有幾個 setAttributesetRPAttribute 過載的方法,它們用於接收不同型別的屬性值:

  • boolean setAttribute(int key, int value) 處理編譯成整數型別的屬性;
  • boolean setAttribute(int key, float value) 處理編譯成浮點數型別的屬性;
  • boolean setAttribute(int key, String stringValue) 處理編譯成字串型別的屬性,包括那些本該編譯成整數或者浮點數但因為寫了表示式被編譯成字串型別的;
  • boolean setRPAttribute(int key, int value) 處理編譯成整數型別的尺寸屬性,單位是 rp(介紹在此);
  • boolean setRPAttribute(int key, float value) 處理編譯成浮點數型別的尺寸屬性,單位是 rp;

基礎 ViewBase 裡解析處理了大量基礎屬性,所以自定義控制元件只要處理新增的自定義屬性就行了。以上這些過載方法都有一個 Boolean 返回值,它遵循冒泡邏輯,當你返回 true 的時候,當前層級處理了這個屬性,否則表示當前層級處理不了這個屬性,需要進一步交給子類解析;在本文的示例裡,是這麼處理的:

@Override
protected boolean setAttribute(int key, float value) {
    boolean ret = true;
    if (key == degreeId) {
    	//從模板裡直接獲取到旋轉角度屬性值
        degrees = value;
    } else {
        ret = super.setAttribute(key, value);
    }
    return ret;
}

@Override
protected boolean setAttribute(int key, String stringValue) {
    boolean ret = true;
    if (key == degreeId) {
    	//從模板裡直接獲取到旋轉角度屬性值是一個表示式,暫存到 viewCache 裡,等傳入資料的時候再次解析,然後回撥到上述 setAttribute(int key, float value) 方法裡獲取最終值
        if (Utils.isEL(stringValue)) {
            mViewCache.put(this, degreeId, stringValue, Item.TYPE_FLOAT);
        }
    } else if (key == urlId) {
	    //從模板裡直接獲取到url屬性值可能是一個表示式,也可能是個直接的 url,如果是表示式,暫存到 viewCache 裡,等傳入資料的時候再次解析,然後回撥本方法裡獲取最終值
        if (Utils.isEL(stringValue)) {
            mViewCache.put(this, urlId, stringValue, Item.TYPE_STRING);
        } else {
            url = stringValue;
        }
    } else {
        ret = super.setAttribute(key, stringValue);
    }
    return ret;
}
複製程式碼

最後就是使用這些屬性值,在 onParseValueFinised() 裡一次性應用屬性:

@Override
public void onParseValueFinished() {
    super.onParseValueFinished();
    Picasso.with(mContext.getContext()).load(url).rotate(degrees).into(mPicassoImageView);
}
複製程式碼

詳細程式碼:PicassoImage.java

4. 註冊控制元件

通過 ViewManager 裡的 ViewFactory 註冊,如下:

sViewManager.getViewFactory().registerBuilder(1014,new PicassoImage.Builder());
複製程式碼

5. 使用與執行效果

XML 裡這麼寫:

<VHLayout
    flag="flag_exposure|flag_clickable"
    orientation="V"
    layoutWidth="match_parent"
    layoutHeight="match_parent"
>
<VText
        text="Title: Loading Image with Picasso"
        textSize="12"
        textColor="#333333"
        background="#008899"
        layoutWidth="match_parent"
        layoutHeight="20" />

<PicassoImage
        url="${url}"
        degree="90"
        layoutWidth="match_parent"
        layoutHeight="300" />
</VHLayout>
複製程式碼

繫結的資料:

{
  "url": "https://user-gold-cdn.xitu.io/2018/3/20/16242d5e3adadb1f?w=200&h=200&f=png&s=16211"
}
複製程式碼

執行的結果:

VirtualView Android 實現詳解(三)—— 新增一個自定義控制元件

圖片原圖是這樣的:

VirtualView Android 實現詳解(三)—— 新增一個自定義控制元件

可以看到,通過新增自定義的 degree 屬性,並呼叫 Picasso 的 ratate 方法,最終載入了圖片,也旋轉了圖片,可以根據此思路繼續為 PicassImage 新增更多 Picasso 支援的屬性。

本文裡用到的例子也上傳到了 demo 裡,從上午的原始碼連結裡可以獲取到完整的 demo。

體驗一下

還是那句話,講得再多,不如親自上手體驗一下,可以參考《天貓客戶端元件動態化的方案——VirtualView 上手體驗》《提升開發體驗,預覽 VirtualView》來體驗。

相關文章