閱讀本文大概需要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ù)覽,需要在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”,即可看到效果,如圖:

此時(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
- 枚舉說(shuō)明:
-
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.out、VVTest.md5和VVTest.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,效果如下:

此時(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,效果如下:

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ā)掘。