Tangram不仅仅是一个Native(iOS & Android)的界面开发框架,而是我们从日常工作中沉淀出的一套界面解决方案,涵盖了Native SDK,GUI操作台,后端逻辑容器,组件库机制的一整套方案。
页面
|
- 布局1
| |
| - 组件11
| |
| - 组件12
| |
| - 组件13
|
- 布局2
| |
| - 组件21
| |
| - 组件22
|
- 布局3
| |
| - 组件31
| |
| - 组件32
|
- 组件4
...
|
|
|
- 布局n
|
- 组件n1
在Android中是用RecyclerView来承载.
{
"type": "container-oneColumn", ---> 描述布局类型
"style": { ---> 描述样式
...
},
"header": { ---> 描述header
},
"items": [ ---> 描述组件列表
...
],
"footer": { ---> 描述footer
}
}
{
"type": "demo", ---> 描述组件类型
"style": { ---> 描述组件样式
"margin": [
10,
10,
10,
10
],
"height": 100,
"width": 100
}
"imgUrl": "[URL]", ---> 业务数据
"title": "Sample"
}
面向业务、多端一致性和高性能。
Tangram的动态能力很弱,动态能力局限在组件层面,无法动态卡片.
compile 'com.alibaba.android:tangram:2.0.5'
compile ('com.alibaba.android:virtualview:1.0.5@aar') {
transitive = true
exclude group: 'com.android.support'
}
compile ('com.alibaba.android:vlayout:1.2.8@aar') {
transitive = true
exclude group: 'com.android.support'
}
compile ('com.alibaba.android:ultraviewpager:1.0.7.7@aar') {
transitive = true
exclude group: 'com.android.support'
}
TangramBuilder.init(context, new IInnerImageSetter() {
@Override
public void doLoadImageUrl(@NonNull IMAGE view,
@Nullable String url) {
//假设你使用 Picasso 加载图片
Picasso.with(context).load(url).into(view);
}
}, ImageView.class);
TangramBuilder.InnerBuilder builder = TangramBuilder.newInnerBuilder(TangramActivity.this);
View
,比如builder.registerCell(1, TestView.class);
。意思是类型为1的组件渲染时会被绑定到TestView
的实例上,这种方式注册的组件使用通用的组件模型BaseCell
。View
,比如builder.registerCell(1, TestCell.class, TestView.class);
。意思是类型为1的组件使用自定义的组件模型TestCell
,它应当继承于BaseCell
,在渲染时会被绑定到TestView
的实例上。ViewHolder
,比如builder.registerCell(1, TestCell.class, new ViewHolderCreator<>(R.layout.item_holder, TestViewHolder.class, TestView.class));
。意思是类型为1的组件使用自定义的组件模型TestCell
,它应当继承于BaseCell
,在渲染时以R.layout.item_holder
为布局创建类型为TestView
的 view,并绑定到类型为TestViewHolder
的 viewHolder 上,组件数据被绑定到定到TestView
的实例上。TangramEngine
实例TangramEngine engine = builder.build();
engine.register(SimpleClickSupport.class, new XXClickSupport());
engine.register(CardLoadSupport.class, new XXCardLoadSupport());
engine.register(ExposureSupport.class, new XXExposureSuport());
setContentView(R.layout.main_activity);
RecyclerView recyclerView = (RecyclerView) findViewById(R.id.main_view);
...
engine.bindView(recyclerView);
recyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
@Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
super.onScrolled(recyclerView, dx, dy);
//在 scroll 事件中触发 engine 的 onScroll,内部会触发需要异步加载的卡片去提前加载数据
engine.onScrolled();
}
});
String json = new String(getAssertsFile(this, "data.json"));
JSONArray data = null;
try {
data = new JSONArray(json);
engine.setData(data);
} catch (JSONException e) {
e.printStackTrace();
}
engine.destroy();
组件开发模式一,避免了反射调用,性能上更优:
实现一个自定义View,比如XXTangramView
;
实现接口
ITangramViewLifeCycle
,包含三个方法:
public void cellInited(BaseCell cell)
,绑定数据前调用;public void postBindView(BaseCell cell)
,绑定数据时机;public void postUnBindView(BaseCell cell)
,滑出屏幕,解除绑定;主要在上述public void postBindView(BaseCell cell)
完成组件业务逻辑;
组件开发模式二,动态绑定数据:
实现一个自定义View,比如XXTangramView
;
必须添加三个方法,以
@CellRender
注解,功能同模式一,只是被反射调用:
public void cellInited(BaseCell cell)
;public void postBindView(BaseCell cell)
;public void postUnBindView(BaseCell cell)
;还可以为组件每个属性实现单独的设置方法,而不是在postBindView
方法里一次性绑定数据,这些方法必须以@CellRender
注解,框架会在public void postBindView(BaseCell cell)
调用之前调用这些数据绑定方法;
Tangram.Builder builder = Tangram.newBuilder(activity);
builder.registerVirtualView("tmallcomponent1");
builder.registerVirtualView("tmallcomponent2");
tangramEngine.setVirtualViewTemplate(TMALLCOMPONENT1.BIN);
tangramEngine.setVirtualViewTemplate(TMALLCOMPONENT2.BIN);
...
tangramEngine.registerVirtualViewTemplate("tmallcomponent1",TMALLCOMPONENT1.BIN)
以创建一个 PicassoImage 为例(虽然内置了 VImage 和 NImage 两个控件,但在实际业务场景中,还是使用一个自定义的图片控件比较合适,这样可以更好利用起结合图片库的内存管理、性能优化等 feature)。
在编译工具里配置文件里定义:
VIEW_ID_PicassoImage=1014
,其中 PicassoImage
就是 XML 里的标签名,id 值为 1014,这个是自定义的,建议从 1001开始,前 1000 保留给系统使用;degree=Float
,表示属性名是 degree ,属性值按 Float 类型编译解析;url=String
,表示属性名是 url,属性值按 String 类型编译,不过未在配置文件里声明的属性都是按 String 类型编译的,所以可以省略;取名 PicassoImageView
,继承 ImageView
,实现 IView
接口,因为 demo 比较简单,除此之外不做其他逻辑,主要实现 IView
的接口调用对应的系统 measure、layout 方法,因为这些方法是不能在外部调用的,只能通过 IView
的接口封装一下暴露出去。
事件处理就涉及业务逻辑了,需要外部注册处理器。也是通过 EventManager
来注册,一种类型的事件注册一个处理器就可以了,如果注册了多个,那么在事件触发的时候,会回调每一个处理器。
VafContext vafContext = tangramEngine.getService(VafContext.class);
vafContext.getEventManager().register(EventManager.TYPE_Click, new IEventProcessor() {
@Override
public boolean process(EventData data) {
//handle here
return true;
}
});
vafContext.getEventManager().register(EventManager.TYPE_Exposure, new IEventProcessor() {
@Override
public boolean process(EventData data) {
//handle here
return true;
}
});
在处理函数里,可以获取到的资源有:
data.mVB
(JSONObject)data.mVB.getViewCache().getComponentData();
如果消化了事件,就返回 true
,否则返回 false
。
针对不同的控件,点击处理要做不同的逻辑,一般可以用以下方式来分发处理不同的点击事件:
String action = data.mVB.getAction()
获取所点击的控件的 action 字段,它是在 XML 目标里描述的,可以是一个常量值,也可从绑定的数据到数据。建议 actio 只是一个动作描述协议,比如跳转是一个 URL 格式的地址,其他的可以是自定义的协议;然后针对不同的协议地址,注册不同的处理模块,在自定义的 IEventProcessor
里分发处理。
下载源码之后,可执行工具存放在目录 TemplateWorkSpace 里,包含以下几个文件/目录(或运行后产生):
文件 | 作用 |
---|---|
config.properties | 配置组件 ID、xml 属性对应的 value 类型 |
templatelist.properties | 编译的模板文件列表 |
build | 二进制文件的输出目录 |
template | xml 的存放路径 |
compiler.jar | java 代码编译后 jar 文件,执行 xml 的编译逻辑 |
buildTemplate.sh | 编译执行文件 |
sh buildTemplate.sh
VIEW_ID_XXXX
VIEW_ID_FrameLayout=1
,则 xml 节点中的
在编译后会用数值1代替property=ValueType
配置属性值的类型,配置对所有模板生效,不支持在 1.xml 和 2.xml 中对相同的属性用不同的 ValueType 解析
目前已经支持
常规类型:String
(默认,不需要配置)、Float
、Color
、Expr
、Number
、Int
、Bool
特殊类型 Flag
、Type
、Align
、LayoutWidthHeight
、TextStyle
、DataMode
、Visibility
枚举类型
Enum
flexDirection=Enum
row
直接转化成 int:0
,row-reverse
转成 int:1
DEFAULT_PROPERTY_XXXX
格式
xmlFileName=outFileName,Version[,platform]
xmlFileName
标识 template 目录下需要编译的 xml 文件名建议不带 .xml
后缀,目前做了兼容outFileName
输出到 build 目录下的 .out
文件名Version
表示 xml 编译后的版本号platform
同时兼容 iOS 和 android 时不写,可填的值为 android
和iphone
除了直接使用命令行执行工具,还可以基于此搭建完整成熟的模板工具,它可以是个客户端,也可以是个后端服务,或者是个插件,所以需要提供接口模式供宿主程序调用。
//初始化构建对象
ViewCompilerApi viewCompiler = new ViewCompilerApi();
//设置配置文件加载器,需要实现一个自己的 ConfigLoader,这里的 LocalConfigLoader 是示例
viewCompiler.setConfigLoader(new LocalConfigLoader());
//读取模板数据
FileInputStream fis = new FileInputStream(rootDir);
//调用接口,传入必备参数,参数二是模板名称类型,参数三是模板版本号,此时不区分平台,如果要区分平台,使用方单独编译即可
byte[] result = viewCompiler.compile(fis, "icon", 13);
通过自定义原子组件解决本地icon问题
一些复杂组件,使用原生书写完毕后,内置在app中