阅读本文大概需要20分钟
在Android动态界面开发框架Tangram使用完整教程我们学习了Tangram的使用。
在 Tangram 体系里,页面结构可以通过配置动态更新,然而业务组件是通过 Java 代码实现的,无法动态更新。VirtualView 就是为了解决业务组件的动态更新而生的,它提供了一系列基础 UI 组件和布局组件能力,通过 XML 来搭建业务组件,并将 XML 模板编译成二进制数据,然后主体框架解析二进制数据并渲染出视图。当 XML 模板数据能动态下发的时候,客户端上的业务组件视图也就能动态更新了。
要学习使用 VirtualView,需要从四个方面入手:
- 了解 VirtualView 模板数据的格式;
- 了解 VirtualView 的基本原理,包括从模板编译、解析、绑定数据几个主要流程;
- 了解 VirtualView 的基本接入方式,初始化、添加自定义基础控件、添加与外部的逻辑交互等;
- 了解 VirtualView 内置基础控件的特性,避免重复开发。
下面我们来学习如何使用VirtualView。
目录
3.3.2 在 Tangram 中使用 VirtualView
1 VirtualView介绍
VirtualView 是 Tangram 升级过程中引入的新的组件开发技术,主要功能包括:
- 一份模板,两端支持。
- 提供基础的原子控件与容器控件,支持加入自定义组件。
- 支持一种虚拟化实现控件的协议,在模板里混合使用虚拟控件和实体控件。
- 支持在模板里编写数据绑定的表达式。
- 支持在模板里写事件触发的逻辑表达式。
- 提供配套的开发工具,辅助模板开发工具。
2 接入教程
其实我们在Android动态界面开发框架Tangram使用完整教程中已经接入了,这里再复述一遍,在APP的build.gradle中添加:
implementation ('com.alibaba.android:virtualview:1.4.6@aar') {
transitive = true
}
VirtualView的最新版本号可以在这里找到:https://github.com/alibaba/Virtualview-Android/releases
3 使用步骤
VirtualView的工作流程分为3大部分:创建UI组件、创建界面模板和客户端加载界面,具体如下:
下面按照上图的流程分步骤讲解。
3.1 创建UI组件
根据业务需求,创建所需要的UI组件,有2种创建方式:使用框架内置(封装好)的UI组件或自定义组件。
3.1.1 使用框架内置组件
VirtualView内置了各种常用的组件,包括NText、VText、NImage和FrameLayout等,类似于Android内置的TextView、ImageView和FrameLayout等。一般来说这些内置的组件就够用了。
3.1.2 自定义UI组件
若框架内置的UI组件无法满足需求,我们还可以自定义UI组件。自定义组件的方法详见如何编写一个自定义基础控件 - Tangram
3.2 创建界面模板 & 下发
3.2.1 创建XML界面模板
根据业务需求,使用XML编写模板,需要使用专门的工具virtualview_tools。
首先到GitHub - alibaba/virtualview_tools将virtualview_tools的源码下载下来,然后使用Android Studio打开这个项目,当然不用Android Studio也可以,我们也可以用SublimeText等文本编辑器进行开发。我们编写XML模板是不能在Android Studio实时预览的,但是在手机上可以,如下图:
为支持实时预览,需要在PC上安装如下依赖:
- java:用于编译 VirtualView;
- python 2:用于跑 WebServer;
- fswatch:用于监听文件修改,安装命令:brew install fswatch
- qrencode:用于生成二维码(可选),安装命令:brew install qrencode
打开命令行,进入compiler-tools/RealtimePreview/目录,目录结构如下:
.
├── compiler.jar (VirtualView 的编译工具)
├── config.properties (VirtualView 编译模版需要的描述文件)
├── run.sh (主运行脚本)
├── .dir (目录配置,json数组格式,新增模板目录需要在此配置,并重新运行脚本)
└── templates (按文件夹分割存放模版)
└── helloworld
├── helloworld.json (该模版所需参数)
├── helloworld.out (该模版编译后的二进制)
├── helloworld.xml (该模版源文件)
└── helloworld_QR.png (该模版 URL 供于扫码加载)
模版统一存放在 templates 目录中,且模版相关文件名必须和目录名一致。
执行 run.sh 即可开启服务,启动服务后正常情况会有如下输出:
$ ./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
之后在GitHub - alibaba/Virtualview-Android: A light way to build UI in custom XML.下载VirtualView源码,用Android Studio打开,找到HttpUtil.java文件,找到这一行:
public static String getHostIp() {
return "10.0.2.2";
}
将IP改成你PC的IP地址,注意你的手机和PC要在同一网络环境下。运行APP,在“模板实时预览”里即可看到所有 templates 目录下的模版。
如果嫌下载官方项目再编译麻烦,可以直接下载我修改后的APP:https://github.com/jimmysuncpt/TangramDemo/blob/master/VirtualView.apk。我在官方APP基础上集成了可手动修改服务器IP的功能,下载二维码如下:
下面实现一个示例,在 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>
其中类似于${action}、${bgColor}这种字段是用来绑定json数据的,其支持多种表达式,具体使用请见绑定数据 - Tangram
然后再新建测试数据vvtest.json,代码如下:
{
"bgColor": "#00FF00",
"imageUrl": "https://gw.alicdn.com/tfs/TB1yGIdkb_I8KJjy1XaXXbsxpXa-72-72.png",
"text": "VirtualView测试",
"action": "您点击了VirtualView"
}
执行 run.sh ,运行VirtualView APP,打开“模板实时预览”-“vvtest”,即可看到效果,如图:
此时如果修改XML文件或者json文件,改完稍等一下,然后点击APP上“REFRESH”按钮,就可以看到修改后的效果。
3.2.2 编译成二进制数据
使用专门的工具virtualview_tools将编写好的XML界面模板编译成二进制数据,编译后的文件的后缀名是.out。
在virtualview_tools项目里,打开compiler-tools/TemplateWorkSpace/目录,其包含以下几个文件/目录(或运行后产生):
文件 | 作用 |
config.properties | 配置组件 ID、xml 属性对应的 value 类型 |
templatelist.properties | 编译的模板文件列表 |
build | 二进制文件的输出目录 |
template | xml 的存放路径 |
compiler.jar | java 代码编译后 jar 文件,执行 xml 的编译逻辑 |
buildTemplate.sh | 编译执行文件 |
将我们编写好的模板文件vvtest.xml放到template目录下,然后按下面步骤操作:
(1)配置 templatelist.properties
格式:xmlFileName=outFileName,Version[,platform]
- xmlFileName:标识 template 目录下需要编译的 xml 文件名,建议不带 .xml 后缀,但目前官方做了兼容;
- outFileName:输出到 build 目录下的 .out 文件名,也是模板名;
- Version:表示 xml 编译后的版本号;
- platform:同时兼容 iOS 和 android 时不写,可填的值为 android 和iphone。
那么我们在templatelist.properties文件里添加下面一行,注意outFileName的大小写很重要,后面注册模板名的时候要用到:
vvtest=VVTest,1
其实这个文件里的其它内容可以删掉,然后template目录下对应的xml文件也可以删掉,因为这些都是示例文件,我们并不需要。
(2)配置 config.properties
这个文件只有在自定义View和自定义属性的时候使用,我们暂且先忽略。
- VIEW_ID_XXXX
- 配置 xml 节点 id
- 如配置 VIEW_ID_FrameLayout=1,则 xml 节点中的 <FrameLayout> 在编译后会用数值1代替
- 节点配置以 VIEW_ID_ 开头 - 自定义控件 id 从 1000 开始分配,前面 1000 保留给系统使用
- property=ValueType
- 配置属性值的类型,配置对所有模板生效,不支持在 1.xml 和 2.xml 中对相同的属性用不同的 ValueType 解析
- 目前已经支持
- 常规类型:String(默认,不需要配置)、Float、Color、Expr、Number、Int、Bool
- 特殊类型:Flag、Type、Align、LayoutWidthHeight、TextStyle、DataMode、Visibility
- 枚举类型:Enum<name:value,……>
- 枚举说明:
- 如配置 flexDirection=Enum<row:0,row-reverse:1,column:2,column-reverse:3>
- 在解析属性是配置 row 直接转化成 int:0,row-reverse转成 int:1
- 枚举说明:
- DEFAULT_PROPERTY_XXXX
- 为了兼容就模板的编译,写的强制在二进制中写入一些属性类型定义,可以忽略
(3)构建产物
打开命令行,执行 sh buildTemplate.sh,模板编译后的文件会输出到 build 路径下,其包含以下几个目录:
- out目录:XML 模板编译成二进制数据的文件,其他内容都是以此为基础生成,上传到 cdn,通过模板管理后台下发的也是这里的文件;
- java目录:XML 模板编译成二进制数据之后的 Java 字节数组形式,可以直接拷贝到 Android 开发工程里使用,作为打底数据;
- sign目录:out 格式文件的 md5 码,供模板管理平台下发模板给客户端校验使用;
- txt目录:XML 模板编译成二进制数据之后的十六进制字符串形式,转换成二进制数据就是 java 目录下的字节数组。
在我们的示例中,分别生成了VVTEST.java、VVTest.out、VVTest.md5和VVTest.txt。
(4)接口模式
除了直接使用命令行执行工具,还可以基于此搭建完整成熟的模板工具,它可以是个客户端,也可以是个后端服务,或者是个插件,所以需要提供接口模式供宿主程序调用,代码如下:
//初始化构建对象
ViewCompilerApi viewCompiler = new ViewCompilerApi();
//设置配置文件加载器,需要实现一个自己的 ConfigLoader,这里的 LocalConfigLoader 是示例
viewCompiler.setConfigLoader(new LocalConfigLoader());
//读取模板数据
FileInputStream fis = new FileInputStream(rootDir);
//调用接口,传入必备参数,参数二是模板名称类型,参数三是模板版本号,此时不区分平台,如果要区分平台,使用方单独编译即可
byte[] result = viewCompiler.compile(fis, "icon", 13);
3.2.3 模板数据下发到客户端
即客户端获取编译后的二进制数据,获取有2种途径:
- 直接将编译后的模板打包到客户端里,开发者通过代码加载;
- 框架先发布到模板管理后台,客户端在线更新到模板数据(即实现了动态更新)。
我们将VVTest.out文件放到assets目录下,假设它就是后端下载的文件。再把VVTEST.java放到我们代码里,之后我们也可以使用它。
3.3 客户端加载界面
客户端获取到编译后的界面模板后,进行加载与解析,最终渲染出视图界面。这个时候需要通过Java代码来使用VirtualView,我们可以单独使用VirtualView,也可以在Tangram中使用VirtualView。
3.3.1 单独使用 VirtualView
首先构建一个 VafContext 对象,代码如下:
VafContext vafContext = new VafContext(mContext.getApplicationContext());
如果使用内置的基础图片组件 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 对象,代码如下:
ViewManager viewManager = vafContext.getViewManager();
viewManager.init(mContext.getApplicationContext());
加载模板数据,利用 VirtualView Tools 编译出的二进制文件,在初始化的时候加载,有两种方式,一种是直接加载二进制字节数组:
viewManager.loadBinBufferSync(VVTEST.BIN);
另一种是通过二进制文件路径加载(不推荐):
viewManager.loadBinFileSync("file:///android_asset/VVTest.out");
如果开发了自定义的基础组件,注册自定义组件的构造器:(开发自定义组件的说明参考这里)
viewManager.getViewFactory().registerBuilder(BizCommon.TM_PRICE_TEXTVIEW, new TMPriceView.Builder());
viewManager.getViewFactory().registerBuilder(BizCommon.TM_TOTAL_CONTAINER, new TotalContainer.Builder());
注册事件处理器,比如常用的点击、曝光处理:(更多事件处理信息的说明参考这里)
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;
}
});
以上步骤也可以根据业务需求放到自定义的Application的onCreate()方法里。
然后通过组件名参数 name 生成组件实例,注意组件名(也就是模板名)的大小写,代码如下:
View container = vafContext.getContainerService().getContainer("VVTest", true);
mLinearLayout.addView(container);
如果组件模板里写了数据绑定的表达式,那么需要给组件绑定真实的数据。假设我们之前写的vvtest.json是真实的数据,我们把这个文件复制到assets目录下,然后添加如下代码:
IContainer iContainer = (IContainer) container;
JSONObject jsonObject = Utils.getJSONDataFromAsset(this, "vvtest.json");
if (jsonObject != null) {
iContainer.getVirtualView().setVData(jsonObject);
}
运行APP,效果如下:
此时点击组件还可以看到弹出的Toast。
3.3.2 在 Tangram 中使用 VirtualView
在 Tangram 里使用 VirtualView 的时候,大致流程如上述所示,只不过很多步骤已经内置到 Tangram 的初始化里了,我们只需要注册业务组件类型、加载模板数据和提供事件处理器即可。
注册 VirtualView 版本的 Tangram 组件,只需要提供组件类型名称即可,代码如下:
builder.registerVirtualView("VVTest");
在 TangramEngine 构建出来之后加载模板数据,有两种方式,代码如下:
mEngine.setVirtualViewTemplate(VVTEST.BIN);
mEngine.setVirtualViewTemplate(Utils.getAssertsFile(this, "VVTest.out"));
同样的有必要的话需要注册自定义基础组件的构造器,代码如下:
ViewManager viewManager = tangramEngine.getService(ViewManager.class);
viewManager.getViewFactory().registerBuilder(BizCommon.TM_PRICE_TEXTVIEW, new TMPriceView.Builder());
注册事件处理器,代码如下:
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;
}
});
如果使用内置的基础图片组件 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);
}
}
});
为了演示效果,我们在Android动态界面开发框架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测试1",
"action": "您点击了VirtualView1"
},
{
"type": "VVTest",
"bgColor": "#00FF00",
"imageUrl": "https://gw.alicdn.com/tfs/TB1vqF.PpXXXXaRaXXXXXXXXXXX-110-72.png",
"text": "VirtualView测试2",
"action": "您点击了VirtualView2"
},
{
"type": "VVTest",
"bgColor": "#0000FF",
"imageUrl": "https://gw.alicdn.com/tfs/TB1vqF.PpXXXXaRaXXXXXXXXXXX-110-72.png",
"text": "VirtualView测试3",
"action": "您点击了VirtualView3"
}
]
}
运行APP,效果如下:
4 控件
控件分为虚拟view与实体view:
- 虚拟view,即view的内容直接通过宿主的canvas绘制出来,它本身并不需要一个实体的view存在,在真实的view tree中,是看不到这个实例,只能看到其宿主的存在。它包括:原子虚拟view组件,比如文本、图片、线条;布局虚拟view组件,比如线性布局、帧布局等。虚拟view也遵循Android绘制view的逻辑,需要响应measure、layout、draw的过程才能显示。框架内置的虚拟view一般以V开头,例如VText。
- 实体view,即原生的native view。框架内置的实体view一般以N开头,例如NText。
每个控件有公共的属性和自己的属性,如下。
4.1 公共属性
4.1.1 属性
名称 | 类型 | 默认值 | 描述 |
id | int | 0 | 组件id |
layoutWidth | int/float/enum(match_parent/wrap_content) | 0 | 组件的布局宽度,与Android里的概念类似,写绝对值的时候表示绝对宽高,match_parent表示尽可能撑满父容器提供的宽高,wrap_content表示根据自身内容的宽高来布局 |
layoutHeight | int/float/enum(match_parent/wrap_content) | 0 | 组件的布局宽度,与Android里的概念类似,写绝对值的时候表示绝对宽高,match_parent表示尽可能撑满父容器提供的宽高,wrap_content表示根据自身内容的宽高来布局 |
layoutGravity | enum(left/right/top/bottom/v_center/h_center) | left|top | 描述组件在容器中的对齐方式,left:靠左,right:靠右,top:靠上,bottom:靠底,v_center:垂直方向居中,h_center:水平方向居中,可用或组合描述 |
autoDimX | int/float | 1 | 组件宽高比计算的横向值 |
autoDimY | int/float | 1 | 组件宽高比计算的竖向值 |
autoDimDirection | enum(X/Y/NONE) | NONE | 组件在布局中的基准方向,用于计算组件的宽高比,与autoDimX、autoDimY配合使用,设置了这三个属性时,在计算组件尺寸时具有更高的优先级。当autoDimDirection=X时,组件的宽度由layoutWidth和父容器决策决定,但高度 = width * (autoDimY / autoDimX),当autoDimDirection=Y时,组件的高度由layoutHeight和父容器决策决定,但宽度 = height * (autoDimX / autoDimY) |
minWidth(iOS暂未支持) | int/float | 0 | 最小宽度 |
minHeight(iOS暂未支持) | int/float | 0 | 最小高度 |
padding | int/float | 0 | 同时设置 4 个内边距 |
paddingLeft | int/float | 0 | 左内边距,优先级高于 padding |
paddingRight | int/float | 0 | 右内边距,优先级高于 padding |
paddingTop | int/float | 0 | 上内边距,优先级高于 padding |
paddingBottom | int/float | 0 | 下内边距,优先级高于 padding |
layoutMargin | int/float | 0 | 同时设置 4 个外边距 |
layoutMarginLeft | int/float | 0 | 左外边距,优先级高于 layoutMargin |
layoutMarginRight | int/float | 0 | 右外边距,优先级高于 layoutMargin |
layoutMarginTop | int/float | 0 | 上外边距,优先级高于 layoutMargin |
layoutMarginBottom | int/float | 0 | 下外边距,优先级高于 layoutMargin |
background | int | 0 | 背景色 |
borderWidth | int/float | 0 | 边框宽度 |
borderColor | int | 0 | 边框颜色 |
borderRadius | int/float | 0 | 边框四个角的圆角半径,与 borderWidth 配合使用,支持NText、VText、VHLayout、VH2Layout、FrameLayout、GridLayout |
borderTopLeftRadius | int/float | 0 | 单独设置左上角圆角半径,使用同上(iOS仅Layout支持单独设置),优先级高于 borderRadius |
borderTopRightRadius | int/float | 0 | 单独设置右上角圆角半径,使用同上(iOS仅Layout支持单独设置),优先级高于 borderRadius |
borderBottomLeftRadius | int/float | 0 | 单独设置左下角圆角半径,使用同上(iOS仅Layout支持单独设置),优先级高于 borderRadius |
borderBottomRightRadius | int/float | 0 | 单独设置右下角圆角半径,使用同上(iOS仅Layout支持单独设置),优先级高于 borderRadius |
visibility | enum(visible/invisible/gone) | visible | 可见性,与Android里的概念类似,visible:可见,invisible:不可见,但占位,gone:不可见也不占位 |
dataTag | string | 组件数据标识 | |
flag | enum(flag_software/flag_exposure/flag_clickable/flag_longclickable/flag_touchable) | 组件行为定义 | flag_software:关闭view的硬件加速,flag_exposure:需要触发曝光事件,flag_clickable:需要响应点击事件,flag_longclickable:需要响应长按事件,flag_touchable:需要响应触摸事件 |
action | string | null | (表示点击事件触发之后跳转到数据中action字段定义的页面) |
class | string | null | 跟组件绑定的逻辑处理对象名称 |
4.1.2 数值单位
在组件属性里,跟尺寸相关的属性,其值的单位默认是dp,比如layoutWidth=10,表示宽度是10dp;实际值 = dp * density。
为了更精准地适配视觉,支持rp单位,表示适配屏幕大小的值,比如layoutWidth=10rp,实际值 = 10 * 屏幕宽度 / 750。
4.1.3 颜色值
在组件颜色相关属性里,支持16进制数表示,格式为#RRGGBB、#AARRGGBB,也支持以下几个颜色文本:
- black:黑色
- blue:蓝色
- cyan:青色
- dkgray:深灰
- gray:灰色
- green:绿色
- ltgray:浅灰
- magenta:品红
- red:红色
- transparent:透明色
- yellow:黄色
4.2 原子组件
基础元素叶子节点组件,不能嵌套其他组件。
组件名 | 说明 | 详细说明链接 |
NText | 原生实现的文本组件,通过模板里定义可绑定以下属性:字体颜色、字号大小、字体粗细、支持文本对齐,行数,最大行数,行间距,行间距系数,截断方式。 | |
VText | 虚拟化实现的文本组件,精简了大量原生文本的特性,只支持以下几个特性:字体颜色、字号大小、字体粗细、支持文本对齐,只能单行显示,不支持分多行。不支持响应点击事件。 | |
NImage | 原生图片组件,支持加载本地图片或者网络图片,支持所有的缩放模式。 | |
VImage(仅安卓) | 虚拟化实现的图片组件,支持加载本地图片或者网络图片,支持基本的缩放模式。 | |
NLine | 实体进度条组件,支持实线、虚线,可以使用横向显示、竖向显示。 | |
VLine | 虚拟化线条,支持实线、虚线,可以使用横向显示、竖向显示。 | |
VGraph(仅安卓) | 虚拟化图片组件,显示圆形、矩形。 | |
Progress(仅安卓) | 虚拟化进度条组件,横向显示,整体进度由背景色显示,当前进度由前景色显示,总进度为组件的宽度。 |
4.3 容器组件
负责布局的容器组件,可以嵌套其他组件。其中,如何使用Scroller、Page这样的控件来通过数据驱动绑定其它组件,可以见我的另一篇文章:Android动态界面开发框架VirtualView之Page、Scroller数据绑定
组件名 | 说明 | 详细说明链接 |
Scroller(仅安卓) | 页面级别的容器组件,在Android上采用Recycler+StaggeredGridLayoutManager实现。通过数据驱动绑定其他组件。 | |
Slider(仅安卓) | 实体水平滚动的组件容器,支持内部组件的回收复用。 | |
Page | 翻页滚动的组件,与Scroller和Slider的区别在于它是有页面效果,一页一页滚动,而Scroller、Slider是可连续滚动。 | |
Container(仅安卓) | 虚拟化的布局容器,无特殊的布局逻辑,主要是在其他虚拟组件上加一层坑,无特殊功能。支持通过数据动态创建子组件。 | |
FrameLayout | 虚拟化的帧布局,主要提供了布局协议,不支持通过数据动态创建子组件,需要在布局模板里直接写子组件。 | |
RatioLayout | 虚拟化的线性布局,其子组件支持写layoutRatio属性来声明在父容器空间上占用的比例,声明过layoutRatio的组件按比例分配宽或高,未声明layoutRatio的组件占用剩余的空间。不支持通过数据动态创建子组件,需要在布局模板里直接写子组件。 | |
Grid | 实体网格布局容器,支持通过数据动态创建子组件。 | |
GridLayout | 虚拟化的网格布局,与Grid的区别是它是虚拟的,主要提供了布局协议,不支持通过数据动态创建子组件,需要在布局模板里直接写子组件。 | |
VH(仅安卓) | 实体的线性布局,支持通过数据动态创建子组件。 | |
VHLayout | 虚拟化的线性布局,与VH的区别是它是虚拟的,主要提供了布局协议,不支持通过数据动态创建子组件,需要在布局模板里直接写子组件。 | |
VH2Layout | 虚拟化的线性布局,与VHLayout的区别是它支持子组件分别从top、left、right、bottom四个方向进行布局。不支持通过数据动态创建子组件,需要在布局模板里直接写子组件。 | |
FlexLayout(仅安卓) | 虚拟化的Flex布局,Flex协议的虚拟化实现。但是只实现了部分功能,与标准的Flex布局协议还存在一些差距。不支持通过数据动态创建子组件,需要在布局模板里直接写子组件。 | |
NFrameLayout | 实体的帧布局,主要提供了布局协议,不支持通过数据动态创建子组件,需要在布局模板里直接写子组件。虚拟的子组件会绘制到它的 canvas 上而不是整个大容器的 canvas,实体的子组件也会添加到它内部,因此在 borderRadius 属性的作用下会裁剪内部区域,除此之外与 FrameLayout 的功能完全一致。 | |
NRatioLayout | 实体的线性布局,其子组件支持写 layoutRatio 属性来声明在父容器空间上占用的比例,声明过layoutRatio的组件按比例分配宽或高,未声明 layoutRatio 的组件占用剩余的空间。不支持通过数据动态创建子组件,需要在布局模板里直接写子组件。虚拟的子组件会绘制到它的 canvas 上而不是整个大容器的 canvas,实体的子组件也会添加到它内部,因此在 borderRadius 属性的作用下会裁剪内部区域,除此之外与 RatioLayout 的功能完全一致。 | |
NGridLayout | 实体的网格布局,主要提供了布局协议,不支持通过数据动态创建子组件,需要在布局模板里直接写子组件。虚拟的子组件会绘制到它的 canvas 上而不是整个大容器的 canvas,实体的子组件也会添加到它内部,因此在 borderRadius 属性的作用下会裁剪内部区域,除此之外与 GridLayout 的功能完全一致。 | |
NVHLayout | 实体的线性布局,主要提供了布局协议,不支持通过数据动态创建子组件,需要在布局模板里直接写子组件。虚拟的子组件会绘制到它的 canvas 上而不是整个大容器的 canvas,实体的子组件也会添加到它内部,因此在 borderRadius 属性的作用下会裁剪内部区域,除此之外与 VHLayout 的功能完全一致。 | |
NVH2Layout | 实体的线性布局,不支持通过数据动态创建子组件,需要在布局模板里直接写子组件。虚拟的子组件会绘制到它的 canvas 上而不是整个大容器的 canvas,实体的子组件也会添加到它内部,因此在 borderRadius 属性的作用下会裁剪内部区域,除此之外与 VH2Layout 的功能完全一致。 |
4.4 不足之处
虽然官方给出了很多常用控件及常用的属性,但是仍然存在一些不足之处:
- Text控件目前不支持字体设置。
- Image控件目前不支持本地drawable资源。
- 控件背景无法设置本地drawable资源。
- Page设置自动滑动时,手动滑动时有bug。
目前的解决方式是可以参照官方的源码,自定义控件来实现官方未实现的功能。
5 总结
以上介绍了VirtualView及其使用步骤和控件的属性,完整的示例代码请见:GitHub - jimmysuncpt/TangramDemo: Android动态界面开发框架Tangram及VirtualView使用示例
这里我们再总结一下:
阿里最早提出了vlayout,可以说极大地丰富了RecyclerView的功能,可以混合使用各种布局。
但是vlayout只能通过Java使用,而且只能写到客户端上,很不方便,于是提出了Tangram,该框架可以通过json来动态配置布局。
但是Tangram的组件还是需要用Java去写,能否再灵活一些呢?于是阿里又提出了VirtualView,该框架又可以通过XML来动态配置组件。
框架的思想是很好的,但是性能如何,还有待于进一步发掘。