Android動(dòng)態(tài)界面開(kāi)發(fā)框架VirtualView使用完整教程

閱讀本文大概需要20分鐘

Android動(dòng)態(tài)界面開(kāi)發(fā)框架Tangram使用完整教程我們學(xué)習(xí)了Tangram的使用。

在 Tangram 體系里,頁(yè)面結(jié)構(gòu)可以通過(guò)配置動(dòng)態(tài)更新,然而業(yè)務(wù)組件是通過(guò) Java 代碼實(shí)現(xiàn)的,無(wú)法動(dòng)態(tài)更新。VirtualView 就是為了解決業(yè)務(wù)組件的動(dòng)態(tài)更新而生的,它提供了一系列基礎(chǔ) UI 組件和布局組件能力,通過(guò) XML 來(lái)搭建業(yè)務(wù)組件,并將 XML 模板編譯成二進(jìn)制數(shù)據(jù),然后主體框架解析二進(jìn)制數(shù)據(jù)并渲染出視圖。當(dāng) XML 模板數(shù)據(jù)能動(dòng)態(tài)下發(fā)的時(shí)候,客戶(hù)端上的業(yè)務(wù)組件視圖也就能動(dòng)態(tài)更新了。

要學(xué)習(xí)使用 VirtualView,需要從四個(gè)方面入手:

  • 了解 VirtualView 模板數(shù)據(jù)的格式;
  • 了解 VirtualView 的基本原理,包括從模板編譯、解析、綁定數(shù)據(jù)幾個(gè)主要流程;
  • 了解 VirtualView 的基本接入方式,初始化、添加自定義基礎(chǔ)控件、添加與外部的邏輯交互等;
  • 了解 VirtualView 內(nèi)置基礎(chǔ)控件的特性,避免重復(fù)開(kāi)發(fā)。

下面我們來(lái)學(xué)習(xí)如何使用VirtualView。


目錄

1 VirtualView介紹

VirtualView 是 Tangram 升級(jí)過(guò)程中引入的新的組件開(kāi)發(fā)技術(shù),主要功能包括:

  • 一份模板,兩端支持。
  • 提供基礎(chǔ)的原子控件與容器控件,支持加入自定義組件。
  • 支持一種虛擬化實(shí)現(xiàn)控件的協(xié)議,在模板里混合使用虛擬控件和實(shí)體控件。
  • 支持在模板里編寫(xiě)數(shù)據(jù)綁定的表達(dá)式。
  • 支持在模板里寫(xiě)事件觸發(fā)的邏輯表達(dá)式。
  • 提供配套的開(kāi)發(fā)工具,輔助模板開(kāi)發(fā)工具。

2 接入教程

其實(shí)我們?cè)?a target="_blank">Android動(dòng)態(tài)界面開(kāi)發(fā)框架Tangram使用完整教程中已經(jīng)接入了,這里再?gòu)?fù)述一遍,在APP的build.gradle中添加:

implementation ('com.alibaba.android:virtualview:1.4.6@aar') {
    transitive = true
}

VirtualView的最新版本號(hào)可以在這里找到:https://github.com/alibaba/Virtualview-Android/releases

3 使用步驟

VirtualView的工作流程分為3大部分:創(chuàng)建UI組件、創(chuàng)建界面模板和客戶(hù)端加載界面,具體如下:


工作流程

下面按照上圖的流程分步驟講解。

3.1 創(chuàng)建UI組件

根據(jù)業(yè)務(wù)需求,創(chuàng)建所需要的UI組件,有2種創(chuàng)建方式:使用框架內(nèi)置(封裝好)的UI組件或自定義組件。

3.1.1 使用框架內(nèi)置組件

VirtualView內(nèi)置了各種常用的組件,包括NText、VText、NImage和FrameLayout等,類(lèi)似于Android內(nèi)置的TextView、ImageView和FrameLayout等。一般來(lái)說(shuō)這些內(nèi)置的組件就夠用了。

3.1.2 自定義UI組件

若框架內(nèi)置的UI組件無(wú)法滿(mǎn)足需求,我們還可以自定義UI組件。自定義組件的方法詳見(jiàn)http://tangram.pingguohe.net/docs/android/add-a-custom-element

3.2 創(chuàng)建界面模板 & 下發(fā)

3.2.1 創(chuàng)建XML界面模板

根據(jù)業(yè)務(wù)需求,使用XML編寫(xiě)模板,需要使用專(zhuān)門(mén)的工具virtualview_tools。

首先到https://github.com/alibaba/virtualview_tools將virtualview_tools的源碼下載下來(lái),然后使用Android Studio打開(kāi)這個(gè)項(xiàng)目,當(dāng)然不用Android Studio也可以,我們也可以用SublimeText等文本編輯器進(jìn)行開(kāi)發(fā)。我們編寫(xiě)XML模板是不能在Android Studio實(shí)時(shí)預(yù)覽的,但是在手機(jī)上可以,如下圖:

實(shí)時(shí)預(yù)覽

為支持實(shí)時(shí)預(yù)覽,需要在PC上安裝如下依賴(lài):

  • java:用于編譯 VirtualView;
  • python 2:用于跑 WebServer;
  • fswatch:用于監(jiān)聽(tīng)文件修改,安裝命令:brew install fswatch
  • qrencode:用于生成二維碼(可選),安裝命令:brew install qrencode

打開(kāi)命令行,進(jìn)入<b>compiler-tools/RealtimePreview</b>目錄,目錄結(jié)構(gòu)如下:

.
├── compiler.jar   (VirtualView 的編譯工具)
├── config.properties   (VirtualView 編譯模版需要的描述文件)
├── run.sh    (主運(yùn)行腳本)
├── .dir (目錄配置,json數(shù)組格式,新增模板目錄需要在此配置,并重新運(yùn)行腳本)
└── templates (按文件夾分割存放模版)
    └── helloworld
        ├── helloworld.json   (該模版所需參數(shù))
        ├── helloworld.out    (該模版編譯后的二進(jìn)制)
        ├── helloworld.xml    (該模版源文件)
        └── helloworld_QR.png (該模版 URL 供于掃碼加載)

模版統(tǒng)一存放在 templates 目錄中,且模版相關(guān)文件名必須和目錄名一致。

執(zhí)行 run.sh 即可開(kāi)啟服務(wù),啟動(dòng)服務(wù)后正常情況會(huì)有如下輸出:

$ ./run.sh
############# Begin Scripts #############
############# Copy All .xml files #############
############# Prebuild : templatelist.properties #############
############# Build: out files #############
compile name: helloworld path: /realtime-preview/template/helloworld.xml
############# Run HTTP Server #############
Start HTTP Server : http://127.0.0.1:7788

之后在https://github.com/alibaba/Virtualview-Android下載VirtualView源碼,用Android Studio打開(kāi),找到HttpUtil.java文件,找到這一行:

public static String getHostIp() {
    return "10.0.2.2";
}

將IP改成你PC的IP地址,注意你的手機(jī)和PC要在同一網(wǎng)絡(luò)環(huán)境下。運(yùn)行APP,在“模板實(shí)時(shí)預(yù)覽”里即可看到所有 templates 目錄下的模版。

下面實(shí)現(xiàn)一個(gè)示例,在 templates 目錄下新建vvtest目錄,在vvtest目錄下新建vvtest.xml,代碼如下:

<?xml version="1.0" encoding="utf-8" ?>
<VHLayout
    action="${action}"
    background="${bgColor}"
    flag="flag_exposure|flag_clickable"
    layoutHeight="wrap_content"
    layoutWidth="match_parent"
    orientation="V">

    <VImage
        layoutGravity="h_center"
        layoutHeight="72rp"
        layoutMarginTop="10rp"
        layoutWidth="72rp"
        src="${imageUrl}"/>

    <NText
        layoutGravity="h_center"
        layoutHeight="wrap_content"
        layoutMarginBottom="10rp"
        layoutMarginTop="10rp"
        layoutWidth="wrap_content"
        text="${text}"/>
</VHLayout>

其中類(lèi)似于<b>${action}</b>、<b>${bgColor}</b>這種字段是用來(lái)綁定json數(shù)據(jù)的,其支持多種表達(dá)式,具體使用請(qǐng)見(jiàn)http://tangram.pingguohe.net/docs/virtualview/simple-expr

然后再新建測(cè)試數(shù)據(jù)vvtest.json,代碼如下:

{
  "bgColor": "#00FF00",
  "imageUrl": "https://gw.alicdn.com/tfs/TB1yGIdkb_I8KJjy1XaXXbsxpXa-72-72.png",
  "text": "VirtualView測(cè)試",
  "action": "您點(diǎn)擊了VirtualView"
}

執(zhí)行 run.sh ,運(yùn)行VirtualView APP,打開(kāi)“模板實(shí)時(shí)預(yù)覽”-“vvtest”,即可看到效果,如圖:

image

此時(shí)如果修改XML文件或者json文件,改完稍等一下,然后點(diǎn)擊APP上“REFRESH”按鈕,就可以看到修改后的效果。

3.2.2 編譯成二進(jìn)制數(shù)據(jù)

使用專(zhuān)門(mén)的工具virtualview_tools將編寫(xiě)好的XML界面模板編譯成二進(jìn)制數(shù)據(jù),編譯后的文件的后綴名是<b>.out</b>。

virtualview_tools項(xiàng)目里,打開(kāi)<b>compiler-tools/TemplateWorkSpace/</b>目錄,其包含以下幾個(gè)文件/目錄(或運(yùn)行后產(chǎn)生):

文件 作用
config.properties 配置組件 ID、xml 屬性對(duì)應(yīng)的 value 類(lèi)型
templatelist.properties 編譯的模板文件列表
build 二進(jìn)制文件的輸出目錄
template xml 的存放路徑
compiler.jar java 代碼編譯后 jar 文件,執(zhí)行 xml 的編譯邏輯
buildTemplate.sh 編譯執(zhí)行文件

將我們編寫(xiě)好的模板文件vvtest.xml放到template目錄下,然后按下面步驟操作:

(1)配置 templatelist.properties

格式:xmlFileName=outFileName,Version[,platform]

  • xmlFileName:標(biāo)識(shí) template 目錄下需要編譯的 xml 文件名,建議不帶 .xml 后綴,但目前官方做了兼容;
  • outFileName:輸出到 build 目錄下的 .out 文件名,也是模板名;
  • Version:表示 xml 編譯后的版本號(hào);
  • platform:同時(shí)兼容 iOS 和 android 時(shí)不寫(xiě),可填的值為 android 和iphone。

那么我們?cè)?strong>templatelist.properties文件里添加下面一行,注意outFileName的大小寫(xiě)很重要,后面注冊(cè)模板名的時(shí)候要用到:

vvtest=VVTest,1

其實(shí)這個(gè)文件里的其它內(nèi)容可以刪掉,然后template目錄下對(duì)應(yīng)的xml文件也可以刪掉,因?yàn)檫@些都是示例文件,我們并不需要。

(2)配置 config.properties

這個(gè)文件只有在自定義View和自定義屬性的時(shí)候使用,我們暫且先忽略。

  • VIEW_ID_XXXX
    • 配置 xml 節(jié)點(diǎn) id
    • 如配置 VIEW_ID_FrameLayout=1,則 xml 節(jié)點(diǎn)中的 <FrameLayout> 在編譯后會(huì)用數(shù)值1代替
    • 節(jié)點(diǎn)配置以 VIEW_ID_ 開(kāi)頭 - 自定義控件 id 從 1000 開(kāi)始分配,前面 1000 保留給系統(tǒng)使用
  • property=ValueType
    • 配置屬性值的類(lèi)型,配置對(duì)所有模板生效,不支持在 1.xml 和 2.xml 中對(duì)相同的屬性用不同的 ValueType 解析
    • 目前已經(jīng)支持
      • 常規(guī)類(lèi)型:String(默認(rèn),不需要配置)、Float、Color、Expr、Number、Int、Bool
      • 特殊類(lèi)型:Flag、Type、Align、LayoutWidthHeight、TextStyle、DataMode、Visibility
      • 枚舉類(lèi)型:Enum<name:value,……>
        • 枚舉說(shuō)明:
          • 如配置 flexDirection=Enum<row:0,row-reverse:1,column:2,column-reverse:3>
          • 在解析屬性是配置 row 直接轉(zhuǎn)化成 int:0,row-reverse轉(zhuǎn)成 int:1
  • DEFAULT_PROPERTY_XXXX
    • 為了兼容就模板的編譯,寫(xiě)的強(qiáng)制在二進(jìn)制中寫(xiě)入一些屬性類(lèi)型定義,可以忽略

(3)構(gòu)建產(chǎn)物

打開(kāi)命令行,執(zhí)行 sh buildTemplate.sh,模板編譯后的文件會(huì)輸出到 build 路徑下,其包含以下幾個(gè)目錄:

  • out目錄:XML 模板編譯成二進(jìn)制數(shù)據(jù)的文件,其他內(nèi)容都是以此為基礎(chǔ)生成,上傳到 cdn,通過(guò)模板管理后臺(tái)下發(fā)的也是這里的文件;
  • java目錄:XML 模板編譯成二進(jìn)制數(shù)據(jù)之后的 Java 字節(jié)數(shù)組形式,可以直接拷貝到 Android 開(kāi)發(fā)工程里使用,作為打底數(shù)據(jù);
  • sign目錄:out 格式文件的 md5 碼,供模板管理平臺(tái)下發(fā)模板給客戶(hù)端校驗(yàn)使用;
  • txt目錄:XML 模板編譯成二進(jìn)制數(shù)據(jù)之后的十六進(jìn)制字符串形式,轉(zhuǎn)換成二進(jìn)制數(shù)據(jù)就是 java 目錄下的字節(jié)數(shù)組。

在我們的示例中,分別生成了VVTEST.java、VVTest.outVVTest.md5VVTest.txt。

(4)接口模式

除了直接使用命令行執(zhí)行工具,還可以基于此搭建完整成熟的模板工具,它可以是個(gè)客戶(hù)端,也可以是個(gè)后端服務(wù),或者是個(gè)插件,所以需要提供接口模式供宿主程序調(diào)用,代碼如下:

//初始化構(gòu)建對(duì)象
ViewCompilerApi viewCompiler = new ViewCompilerApi();
//設(shè)置配置文件加載器,需要實(shí)現(xiàn)一個(gè)自己的 ConfigLoader,這里的 LocalConfigLoader 是示例
viewCompiler.setConfigLoader(new LocalConfigLoader());
//讀取模板數(shù)據(jù)
FileInputStream fis = new FileInputStream(rootDir);
//調(diào)用接口,傳入必備參數(shù),參數(shù)二是模板名稱(chēng)類(lèi)型,參數(shù)三是模板版本號(hào),此時(shí)不區(qū)分平臺(tái),如果要區(qū)分平臺(tái),使用方單獨(dú)編譯即可
byte[] result = viewCompiler.compile(fis, "icon", 13);

3.2.3 模板數(shù)據(jù)下發(fā)到客戶(hù)端

即客戶(hù)端獲取編譯后的二進(jìn)制數(shù)據(jù),獲取有2種途徑:

  • 直接將編譯后的模板打包到客戶(hù)端里,開(kāi)發(fā)者通過(guò)代碼加載;
  • 框架先發(fā)布到模板管理后臺(tái),客戶(hù)端在線更新到模板數(shù)據(jù)(即實(shí)現(xiàn)了動(dòng)態(tài)更新)。

我們將VVTest.out文件放到assets目錄下,假設(shè)它就是后端下載的文件。再把VVTEST.java放到我們代碼里,之后我們也可以使用它。

3.3 客戶(hù)端加載界面

客戶(hù)端獲取到編譯后的界面模板后,進(jìn)行加載與解析,最終渲染出視圖界面。這個(gè)時(shí)候需要通過(guò)Java代碼來(lái)使用VirtualView,我們可以單獨(dú)使用VirtualView,也可以在Tangram中使用VirtualView。

3.3.1 單獨(dú)使用 VirtualView

首先構(gòu)建一個(gè) VafContext 對(duì)象,代碼如下:

VafContext vafContext = new VafContext(mContext.getApplicationContext());

如果使用內(nèi)置的基礎(chǔ)圖片組件 NImage 和 VImage,那么需要初始化一下圖片加載器,我們使用的Glide,代碼如下:

vafContext.setImageLoaderAdapter(new ImageLoader.IImageLoaderAdapter() {
    @Override
    public void bindImage(String uri, ImageBase imageBase, int reqWidth, int reqHeight) {
        if (Utils.isValidContextForGlide(VirtualViewActivity.this)) {
            RequestBuilder requestBuilder =
                    Glide.with(VirtualViewActivity.this).asBitmap().load(uri);
            if (reqWidth > 0 || reqHeight > 0) {
                requestBuilder.submit(reqWidth, reqHeight);
            }
            ImageTarget imageTarget = new ImageTarget(imageBase);
            requestBuilder.into(imageTarget);
        }
    }

    @Override
    public void getBitmap(String uri, int reqWidth, int reqHeight,
                          ImageLoader.Listener lis) {
        if (Utils.isValidContextForGlide(VirtualViewActivity.this)) {
            RequestBuilder requestBuilder =
                    Glide.with(VirtualViewActivity.this).asBitmap().load(uri);
            if (reqWidth > 0 || reqHeight > 0) {
                requestBuilder.submit(reqWidth, reqHeight);
            }
            ImageTarget imageTarget = new ImageTarget(lis);
            requestBuilder.into(imageTarget);
        }
    }
});

其中ImageTarget的代碼如下:

public class ImageTarget extends SimpleTarget<Bitmap> {
    ImageBase mImageBase;
    ImageLoader.Listener mListener;

    public ImageTarget(ImageBase imageBase) {
        mImageBase = imageBase;
    }

    public ImageTarget(ImageLoader.Listener listener) {
        mListener = listener;
    }

    @Override
    public void onResourceReady(@NonNull Bitmap resource,
                                @Nullable Transition<? super Bitmap> transition) {
        mImageBase.setBitmap(resource, true);
        if (mListener != null) {
            mListener.onImageLoadSuccess(resource);
        }
    }

    @Override
    public void onLoadFailed(@Nullable Drawable errorDrawable) {
        if (mListener != null) {
            mListener.onImageLoadFailed();
        }
    }
}

初始化 ViewManager 對(duì)象,代碼如下:

ViewManager viewManager = vafContext.getViewManager();
viewManager.init(mContext.getApplicationContext());

加載模板數(shù)據(jù),利用 VirtualView Tools 編譯出的二進(jìn)制文件,在初始化的時(shí)候加載,有兩種方式,一種是直接加載二進(jìn)制字節(jié)數(shù)組:

viewManager.loadBinBufferSync(VVTEST.BIN);

另一種是通過(guò)二進(jìn)制文件路徑加載(不推薦):

viewManager.loadBinFileSync("file:///android_asset/VVTest.out");

如果開(kāi)發(fā)了自定義的基礎(chǔ)組件,注冊(cè)自定義組件的構(gòu)造器:(開(kāi)發(fā)自定義組件的說(shuō)明參考這里)

viewManager.getViewFactory().registerBuilder(BizCommon.TM_PRICE_TEXTVIEW, new TMPriceView.Builder());
viewManager.getViewFactory().registerBuilder(BizCommon.TM_TOTAL_CONTAINER, new TotalContainer.Builder());

注冊(cè)事件處理器,比如常用的點(diǎn)擊、曝光處理:(更多事件處理信息的說(shuō)明參考這里

vafContext.getEventManager().register(EventManager.TYPE_Click, new IEventProcessor() {

    @Override
    public boolean process(EventData data) {
        Toast.makeText(VirtualViewActivity.this, data.mVB.getAction(), Toast.LENGTH_SHORT).show();
        return true;
    }
});
vafContext.getEventManager().register(EventManager.TYPE_Exposure, new IEventProcessor() {

    @Override
    public boolean process(EventData data) {
        Log.d(TAG, "Exposure process: " + data.mVB.getViewCache().getComponentData());
        return true;
    }
});

以上步驟也可以根據(jù)業(yè)務(wù)需求放到自定義的Application的onCreate()方法里。

然后通過(guò)組件名參數(shù) name 生成組件實(shí)例,注意組件名(也就是模板名)的大小寫(xiě),代碼如下:

View container = vafContext.getContainerService().getContainer("VVTest", true);
mLinearLayout.addView(container);

如果組件模板里寫(xiě)了數(shù)據(jù)綁定的表達(dá)式,那么需要給組件綁定真實(shí)的數(shù)據(jù)。假設(shè)我們之前寫(xiě)的vvtest.json是真實(shí)的數(shù)據(jù),我們把這個(gè)文件復(fù)制到assets目錄下,然后添加如下代碼:

IContainer iContainer = (IContainer) container;
JSONObject jsonObject = Utils.getJSONDataFromAsset(this, "vvtest.json");
if (jsonObject != null) {
    iContainer.getVirtualView().setVData(jsonObject);
}

運(yùn)行APP,效果如下:


image

此時(shí)點(diǎn)擊組件還可以看到彈出的Toast。

3.3.2 在 Tangram 中使用 VirtualView

在 Tangram 里使用 VirtualView 的時(shí)候,大致流程如上述所示,只不過(guò)很多步驟已經(jīng)內(nèi)置到 Tangram 的初始化里了,我們只需要注冊(cè)業(yè)務(wù)組件類(lèi)型、加載模板數(shù)據(jù)和提供事件處理器即可。

注冊(cè) VirtualView 版本的 Tangram 組件,只需要提供組件類(lèi)型名稱(chēng)即可,代碼如下:

builder.registerVirtualView("VVTest");

在 TangramEngine 構(gòu)建出來(lái)之后加載模板數(shù)據(jù),有兩種方式,代碼如下:

mEngine.setVirtualViewTemplate(VVTEST.BIN);
mEngine.setVirtualViewTemplate(Utils.getAssertsFile(this, "VVTest.out"));

同樣的有必要的話(huà)需要注冊(cè)自定義基礎(chǔ)組件的構(gòu)造器,代碼如下:

ViewManager viewManager = tangramEngine.getService(ViewManager.class);
viewManager.getViewFactory().registerBuilder(BizCommon.TM_PRICE_TEXTVIEW, new TMPriceView.Builder());

注冊(cè)事件處理器,代碼如下:

VafContext vafContext = mEngine.getService(VafContext.class);
vafContext.getEventManager().register(EventManager.TYPE_Click, new IEventProcessor() {
    @Override
    public boolean process(EventData data) {
        Toast.makeText(TangramActivity.this, data.mVB.getAction(), Toast.LENGTH_SHORT).show();
        return true;
    }
});
vafContext.getEventManager().register(EventManager.TYPE_Exposure, new IEventProcessor() {
    @Override
    public boolean process(EventData data) {
        Log.d(TAG, "Exposure process: " + data.mVB.getViewCache().getComponentData());
        return true;
    }
});

如果使用內(nèi)置的基礎(chǔ)圖片組件 NImage 和 VImage,那么需要初始化一下圖片加載器,我們使用的Glide,代碼如下:

vafContext.setImageLoaderAdapter(new ImageLoader.IImageLoaderAdapter() {
    @Override
    public void bindImage(String uri, ImageBase imageBase, int reqWidth, int reqHeight) {
        if (Utils.isValidContextForGlide(TangramActivity.this)) {
            RequestBuilder requestBuilder =
                    Glide.with(TangramActivity.this).asBitmap().load(uri);
            if (reqWidth > 0 || reqHeight > 0) {
                requestBuilder.submit(reqWidth, reqHeight);
            }
            ImageTarget imageTarget = new ImageTarget(imageBase);
            requestBuilder.into(imageTarget);
        }
    }

    @Override
    public void getBitmap(String uri, int reqWidth, int reqHeight,
                          ImageLoader.Listener lis) {
        if (Utils.isValidContextForGlide(TangramActivity.this)) {
            RequestBuilder requestBuilder =
                    Glide.with(TangramActivity.this).asBitmap().load(uri);
            if (reqWidth > 0 || reqHeight > 0) {
                requestBuilder.submit(reqWidth, reqHeight);
            }
            ImageTarget imageTarget = new ImageTarget(lis);
            requestBuilder.into(imageTarget);
        }
    }
});

為了演示效果,我們?cè)?a target="_blank">Android動(dòng)態(tài)界面開(kāi)發(fā)框架Tangram使用完整教程的demo里的data.json追加如下代碼:

{
  "type": "container-oneColumn",
  "items": [
    {
      "type": "VVTest",
      "bgColor": "#FF0000",
      "imageUrl": "https://gw.alicdn.com/tfs/TB1vqF.PpXXXXaRaXXXXXXXXXXX-110-72.png",
      "text": "VirtualView測(cè)試1",
      "action": "您點(diǎn)擊了VirtualView1"
    },
    {
      "type": "VVTest",
      "bgColor": "#00FF00",
      "imageUrl": "https://gw.alicdn.com/tfs/TB1vqF.PpXXXXaRaXXXXXXXXXXX-110-72.png",
      "text": "VirtualView測(cè)試2",
      "action": "您點(diǎn)擊了VirtualView2"
    },
    {
      "type": "VVTest",
      "bgColor": "#0000FF",
      "imageUrl": "https://gw.alicdn.com/tfs/TB1vqF.PpXXXXaRaXXXXXXXXXXX-110-72.png",
      "text": "VirtualView測(cè)試3",
      "action": "您點(diǎn)擊了VirtualView3"
    }
  ]
}

運(yùn)行APP,效果如下:


image

4 控件

控件分為虛擬view與實(shí)體view:

  • 虛擬view,即view的內(nèi)容直接通過(guò)宿主的canvas繪制出來(lái),它本身并不需要一個(gè)實(shí)體的view存在,在真實(shí)的view
    tree中,是看不到這個(gè)實(shí)例,只能看到其宿主的存在。它包括:原子虛擬view組件,比如文本、圖片、線條;布局虛擬view組件,比如線性布局、幀布局等。虛擬view也遵循Android繪制view的邏輯,需要響應(yīng)measure、layout、draw的過(guò)程才能顯示??蚣軆?nèi)置的虛擬view一般以V開(kāi)頭,例如VText。
  • 實(shí)體view,即原生的native view??蚣軆?nèi)置的實(shí)體view一般以N開(kāi)頭,例如NText。

每個(gè)控件有公共的屬性和自己的屬性,如下。

4.1 公共屬性

4.1.1 屬性

名稱(chēng) 類(lèi)型 默認(rèn)值 描述
id int 0 組件id
layoutWidth int/float/enum(match_parent/wrap_content) 0 組件的布局寬度,與Android里的概念類(lèi)似,寫(xiě)絕對(duì)值的時(shí)候表示絕對(duì)寬高,match_parent表示盡可能撐滿(mǎn)父容器提供的寬高,wrap_content表示根據(jù)自身內(nèi)容的寬高來(lái)布局
layoutHeight int/float/enum(match_parent/wrap_content) 0 組件的布局寬度,與Android里的概念類(lèi)似,寫(xiě)絕對(duì)值的時(shí)候表示絕對(duì)寬高,match_parent表示盡可能撐滿(mǎn)父容器提供的寬高,wrap_content表示根據(jù)自身內(nèi)容的寬高來(lái)布局
layoutGravity enum(left/right/top/bottom/v_center/h_center) left|top 描述組件在容器中的對(duì)齊方式,left:靠左,right:靠右,top:靠上,bottom:靠底,v_center:垂直方向居中,h_center:水平方向居中,可用或組合描述
autoDimX int/float 1 組件寬高比計(jì)算的橫向值
autoDimY int/float 1 組件寬高比計(jì)算的豎向值
autoDimDirection enum(X/Y/NONE) NONE 組件在布局中的基準(zhǔn)方向,用于計(jì)算組件的寬高比,與autoDimX、autoDimY配合使用,設(shè)置了這三個(gè)屬性時(shí),在計(jì)算組件尺寸時(shí)具有更高的優(yōu)先級(jí)。當(dāng)autoDimDirection=X時(shí),組件的寬度由layoutWidth和父容器決策決定,但高度 = width * (autoDimY / autoDimX),當(dāng)autoDimDirection=Y時(shí),組件的高度由layoutHeight和父容器決策決定,但寬度 = height * (autoDimX / autoDimY)
minWidth(iOS暫未支持) int/float 0 最小寬度
minHeight(iOS暫未支持) int/float 0 最小高度
padding int/float 0 同時(shí)設(shè)置 4 個(gè)內(nèi)邊距
paddingLeft int/float 0 左內(nèi)邊距,優(yōu)先級(jí)高于 padding
paddingRight int/float 0 右內(nèi)邊距,優(yōu)先級(jí)高于 padding
paddingTop int/float 0 上內(nèi)邊距,優(yōu)先級(jí)高于 padding
paddingBottom int/float 0 下內(nèi)邊距,優(yōu)先級(jí)高于 padding
layoutMargin int/float 0 同時(shí)設(shè)置 4 個(gè)外邊距
layoutMarginLeft int/float 0 左外邊距,優(yōu)先級(jí)高于 layoutMargin
layoutMarginRight int/float 0 右外邊距,優(yōu)先級(jí)高于 layoutMargin
layoutMarginTop int/float 0 上外邊距,優(yōu)先級(jí)高于 layoutMargin
layoutMarginBottom int/float 0 下外邊距,優(yōu)先級(jí)高于 layoutMargin
background int 0 背景色
borderWidth int/float 0 邊框?qū)挾?/td>
borderColor int 0 邊框顏色
borderRadius int/float 0 邊框四個(gè)角的圓角半徑,與 borderWidth 配合使用,支持NText、VText、VHLayout、VH2Layout、FrameLayout、GridLayout
borderTopLeftRadius int/float 0 單獨(dú)設(shè)置左上角圓角半徑,使用同上(iOS僅Layout支持單獨(dú)設(shè)置),優(yōu)先級(jí)高于 borderRadius
borderTopRightRadius int/float 0 單獨(dú)設(shè)置右上角圓角半徑,使用同上(iOS僅Layout支持單獨(dú)設(shè)置),優(yōu)先級(jí)高于 borderRadius
borderBottomLeftRadius int/float 0 單獨(dú)設(shè)置左下角圓角半徑,使用同上(iOS僅Layout支持單獨(dú)設(shè)置),優(yōu)先級(jí)高于 borderRadius
borderBottomRightRadius int/float 0 單獨(dú)設(shè)置右下角圓角半徑,使用同上(iOS僅Layout支持單獨(dú)設(shè)置),優(yōu)先級(jí)高于 borderRadius
visibility enum(visible/invisible/gone) visible 可見(jiàn)性,與Android里的概念類(lèi)似,visible:可見(jiàn),invisible:不可見(jiàn),但占位,gone:不可見(jiàn)也不占位
dataTag string 組件數(shù)據(jù)標(biāo)識(shí)
flag enum(flag_software/flag_exposure/flag_clickable/flag_longclickable/flag_touchable) 組件行為定義 flag_software:關(guān)閉view的硬件加速,flag_exposure:需要觸發(fā)曝光事件,flag_clickable:需要響應(yīng)點(diǎn)擊事件,flag_longclickable:需要響應(yīng)長(zhǎng)按事件,flag_touchable:需要響應(yīng)觸摸事件
action string null 表示點(diǎn)擊事件觸發(fā)之后跳轉(zhuǎn)到數(shù)據(jù)中action字段定義的頁(yè)面
class string null 跟組件綁定的邏輯處理對(duì)象名稱(chēng)

4.1.2 數(shù)值單位

在組件屬性里,跟尺寸相關(guān)的屬性,其值的單位默認(rèn)是dp,比如layoutWidth=10,表示寬度是10dp;實(shí)際值 = dp * density。

為了更精準(zhǔn)地適配視覺(jué),支持rp單位,表示適配屏幕大小的值,比如layoutWidth=10rp,實(shí)際值 = 10 * 屏幕寬度 / 750。

4.1.3 顏色值

在組件顏色相關(guān)屬性里,支持16進(jìn)制數(shù)表示,格式為<b>#RRGGBB</b>、#AARRGGBB,也支持以下幾個(gè)顏色文本:

  • black:黑色
  • blue:藍(lán)色
  • cyan:青色
  • dkgray:深灰
  • gray:灰色
  • green:綠色
  • ltgray:淺灰
  • magenta:品紅
  • red:紅色
  • transparent:透明色
  • yellow:黃色

4.2 原子組件

基礎(chǔ)元素葉子節(jié)點(diǎn)組件,不能嵌套其他組件。

組件名 說(shuō)明 詳細(xì)說(shuō)明鏈接
NText 原生實(shí)現(xiàn)的文本組件,通過(guò)模板里定義可綁定以下屬性:字體顏色、字號(hào)大小、字體粗細(xì)、支持文本對(duì)齊,行數(shù),最大行數(shù),行間距,行間距系數(shù),截?cái)喾绞健?/td> http://tangram.pingguohe.net/docs/virtualview/ntext
VText 虛擬化實(shí)現(xiàn)的文本組件,精簡(jiǎn)了大量原生文本的特性,只支持以下幾個(gè)特性:字體顏色、字號(hào)大小、字體粗細(xì)、支持文本對(duì)齊,只能單行顯示,不支持分多行。不支持響應(yīng)點(diǎn)擊事件。 http://tangram.pingguohe.net/docs/virtualview/vtext
NImage 原生圖片組件,支持加載本地圖片或者網(wǎng)絡(luò)圖片,支持所有的縮放模式。 http://tangram.pingguohe.net/docs/virtualview/nimage
VImage(僅安卓) 虛擬化實(shí)現(xiàn)的圖片組件,支持加載本地圖片或者網(wǎng)絡(luò)圖片,支持基本的縮放模式。 http://tangram.pingguohe.net/docs/virtualview/vimage
NLine 實(shí)體進(jìn)度條組件,支持實(shí)線、虛線,可以使用橫向顯示、豎向顯示。 http://tangram.pingguohe.net/docs/virtualview/nline
VLine 虛擬化線條,支持實(shí)線、虛線,可以使用橫向顯示、豎向顯示。 http://tangram.pingguohe.net/docs/virtualview/nline
VGraph(僅安卓) 虛擬化圖片組件,顯示圓形、矩形。 http://tangram.pingguohe.net/docs/virtualview/vgraph
Progress(僅安卓) 虛擬化進(jìn)度條組件,橫向顯示,整體進(jìn)度由背景色顯示,當(dāng)前進(jìn)度由前景色顯示,總進(jìn)度為組件的寬度。 http://tangram.pingguohe.net/docs/virtualview/progress

4.3 容器組件

負(fù)責(zé)布局的容器組件,可以嵌套其他組件。

組件名 說(shuō)明 詳細(xì)說(shuō)明鏈接
Scroller(僅安卓) 頁(yè)面級(jí)別的容器組件,在Android上采用Recycler+StaggeredGridLayoutManager實(shí)現(xiàn)。通過(guò)數(shù)據(jù)驅(qū)動(dòng)綁定其他組件。 http://tangram.pingguohe.net/docs/virtualview/scroller
Slider(僅安卓) 實(shí)體水平滾動(dòng)的組件容器,支持內(nèi)部組件的回收復(fù)用。 http://tangram.pingguohe.net/docs/virtualview/slider
Page 翻頁(yè)滾動(dòng)的組件,與Scroller和Slider的區(qū)別在于它是有頁(yè)面效果,一頁(yè)一頁(yè)滾動(dòng),而Scroller、Slider是可連續(xù)滾動(dòng)。 http://tangram.pingguohe.net/docs/virtualview/page
Container(僅安卓) 虛擬化的布局容器,無(wú)特殊的布局邏輯,主要是在其他虛擬組件上加一層坑,無(wú)特殊功能。支持通過(guò)數(shù)據(jù)動(dòng)態(tài)創(chuàng)建子組件。 http://tangram.pingguohe.net/docs/virtualview/container
FrameLayout 虛擬化的幀布局,主要提供了布局協(xié)議,不支持通過(guò)數(shù)據(jù)動(dòng)態(tài)創(chuàng)建子組件,需要在布局模板里直接寫(xiě)子組件。 http://tangram.pingguohe.net/docs/virtualview/framelayout
RatioLayout 虛擬化的線性布局,其子組件支持寫(xiě)layoutRatio屬性來(lái)聲明在父容器空間上占用的比例,聲明過(guò)layoutRatio的組件按比例分配寬或高,未聲明layoutRatio的組件占用剩余的空間。不支持通過(guò)數(shù)據(jù)動(dòng)態(tài)創(chuàng)建子組件,需要在布局模板里直接寫(xiě)子組件。 http://tangram.pingguohe.net/docs/virtualview/ratiolayout
Grid 實(shí)體網(wǎng)格布局容器,支持通過(guò)數(shù)據(jù)動(dòng)態(tài)創(chuàng)建子組件。 http://tangram.pingguohe.net/docs/virtualview/grid
GridLayout 虛擬化的網(wǎng)格布局,與Grid的區(qū)別是它是虛擬的,主要提供了布局協(xié)議,不支持通過(guò)數(shù)據(jù)動(dòng)態(tài)創(chuàng)建子組件,需要在布局模板里直接寫(xiě)子組件。 http://tangram.pingguohe.net/docs/virtualview/gridlayout
VH(僅安卓) 實(shí)體的線性布局,支持通過(guò)數(shù)據(jù)動(dòng)態(tài)創(chuàng)建子組件。 http://tangram.pingguohe.net/docs/virtualview/vh
VHLayout 虛擬化的線性布局,與VH的區(qū)別是它是虛擬的,主要提供了布局協(xié)議,不支持通過(guò)數(shù)據(jù)動(dòng)態(tài)創(chuàng)建子組件,需要在布局模板里直接寫(xiě)子組件。 http://tangram.pingguohe.net/docs/virtualview/vhlayout
VH2Layout 虛擬化的線性布局,與VHLayout的區(qū)別是它支持子組件分別從top、left、right、bottom四個(gè)方向進(jìn)行布局。不支持通過(guò)數(shù)據(jù)動(dòng)態(tài)創(chuàng)建子組件,需要在布局模板里直接寫(xiě)子組件。 http://tangram.pingguohe.net/docs/virtualview/vh2layout
FlexLayout(僅安卓) 虛擬化的Flex布局,F(xiàn)lex協(xié)議的虛擬化實(shí)現(xiàn)。但是只實(shí)現(xiàn)了部分功能,與標(biāo)準(zhǔn)的Flex布局協(xié)議還存在一些差距。不支持通過(guò)數(shù)據(jù)動(dòng)態(tài)創(chuàng)建子組件,需要在布局模板里直接寫(xiě)子組件。 http://tangram.pingguohe.net/docs/virtualview/flexlayout
NFrameLayout 實(shí)體的幀布局,主要提供了布局協(xié)議,不支持通過(guò)數(shù)據(jù)動(dòng)態(tài)創(chuàng)建子組件,需要在布局模板里直接寫(xiě)子組件。虛擬的子組件會(huì)繪制到它的 canvas 上而不是整個(gè)大容器的 canvas,實(shí)體的子組件也會(huì)添加到它內(nèi)部,因此在 borderRadius 屬性的作用下會(huì)裁剪內(nèi)部區(qū)域,除此之外與 FrameLayout 的功能完全一致。 http://tangram.pingguohe.net/docs/virtualview/nframelayout
NRatioLayout 實(shí)體的線性布局,其子組件支持寫(xiě) layoutRatio 屬性來(lái)聲明在父容器空間上占用的比例,聲明過(guò)layoutRatio的組件按比例分配寬或高,未聲明 layoutRatio 的組件占用剩余的空間。不支持通過(guò)數(shù)據(jù)動(dòng)態(tài)創(chuàng)建子組件,需要在布局模板里直接寫(xiě)子組件。虛擬的子組件會(huì)繪制到它的 canvas 上而不是整個(gè)大容器的 canvas,實(shí)體的子組件也會(huì)添加到它內(nèi)部,因此在 borderRadius 屬性的作用下會(huì)裁剪內(nèi)部區(qū)域,除此之外與 RatioLayout 的功能完全一致。 http://tangram.pingguohe.net/docs/virtualview/nratiolayout
NGridLayout 實(shí)體的網(wǎng)格布局,主要提供了布局協(xié)議,不支持通過(guò)數(shù)據(jù)動(dòng)態(tài)創(chuàng)建子組件,需要在布局模板里直接寫(xiě)子組件。虛擬的子組件會(huì)繪制到它的 canvas 上而不是整個(gè)大容器的 canvas,實(shí)體的子組件也會(huì)添加到它內(nèi)部,因此在 borderRadius 屬性的作用下會(huì)裁剪內(nèi)部區(qū)域,除此之外與 GridLayout 的功能完全一致。 http://tangram.pingguohe.net/docs/virtualview/ngridlayout
NVHLayout 實(shí)體的線性布局,主要提供了布局協(xié)議,不支持通過(guò)數(shù)據(jù)動(dòng)態(tài)創(chuàng)建子組件,需要在布局模板里直接寫(xiě)子組件。虛擬的子組件會(huì)繪制到它的 canvas 上而不是整個(gè)大容器的 canvas,實(shí)體的子組件也會(huì)添加到它內(nèi)部,因此在 borderRadius 屬性的作用下會(huì)裁剪內(nèi)部區(qū)域,除此之外與 VHLayout 的功能完全一致。 http://tangram.pingguohe.net/docs/virtualview/nvhlayout
NVH2Layout 實(shí)體的線性布局,不支持通過(guò)數(shù)據(jù)動(dòng)態(tài)創(chuàng)建子組件,需要在布局模板里直接寫(xiě)子組件。虛擬的子組件會(huì)繪制到它的 canvas 上而不是整個(gè)大容器的 canvas,實(shí)體的子組件也會(huì)添加到它內(nèi)部,因此在 borderRadius 屬性的作用下會(huì)裁剪內(nèi)部區(qū)域,除此之外與 VH2Layout 的功能完全一致。 http://tangram.pingguohe.net/docs/virtualview/nvh2layout

5 總結(jié)

以上介紹了VirtualView及其使用步驟和控件的屬性,完整的示例代碼請(qǐng)見(jiàn):https://github.com/jimmysuncpt/TangramDemo

這里我們?cè)倏偨Y(jié)一下:

阿里最早提出了vlayout,可以說(shuō)極大地豐富了RecyclerView的功能,可以混合使用各種布局。

但是vlayout只能通過(guò)Java使用,而且只能寫(xiě)到客戶(hù)端上,很不方便,于是提出了Tangram,該框架可以通過(guò)json來(lái)動(dòng)態(tài)配置布局。

但是Tangram的組件還是需要用Java去寫(xiě),能否再靈活一些呢?于是阿里又提出了VirtualView,該框架又可以通過(guò)XML來(lái)動(dòng)態(tài)配置組件。

框架的思想是很好的,但是性能如何,還有待于進(jìn)一步發(fā)掘。

參考鏈接

  1. https://github.com/alibaba/Virtualview-Android
  2. https://github.com/alibaba/virtualview_tools
  3. http://tangram.pingguohe.net/docs/virtualview/about-virtualview
  4. http://tangram.pingguohe.net/docs/android/use-virtualview
  5. http://www.itdecent.cn/p/5bd7a210b800
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容