閱讀本文大概需要20分鐘
之前阿里出品的vlayout豐富了RecyclerView的功能后频,支持各種布局卖陵,但是一切都用Java代碼實(shí)現(xiàn),不是很靈活喇聊,于是提出了Tangram,使用json來(lái)配置布局蹦狂。下面我們來(lái)學(xué)習(xí)一下如何使用Tangram誓篱。
1 Tangram的概念
Tangram 是阿里出品的用于快速實(shí)現(xiàn)組合布局的框架模型,在手機(jī)天貓 Android 及 iOS版廣泛使用凯楔。
中文翻譯為七巧板窜骄,即該框架提供一系列基本單元布局,通過(guò)快速拼裝就能搭建出一個(gè)具備多種布局的頁(yè)面摆屯。
Tangram提供了流式布局邻遏、滾動(dòng)布局糠亩,瀑布流布局,固定布局等數(shù)種布局樣式准验,布局提供樣式參數(shù)供調(diào)整赎线,布局內(nèi)部也可填充任意的視圖(View),使Native開(kāi)發(fā)的頁(yè)面具備一定的動(dòng)態(tài)性糊饱,并提供極致的性能垂寥。
Tangram包含的特點(diǎn)如下:
- Android iOS 雙平臺(tái)支持,iOS 版本參考開(kāi)源庫(kù) Tangram-iOS另锋。
- 通過(guò) json 創(chuàng)建頁(yè)面視圖滞项,并提供了默認(rèn)的解析器。
- 可輕松實(shí)現(xiàn)頁(yè)面視圖的回收與復(fù)用夭坪。
- 框架提供多種默認(rèn)的布局方式文判。
- 通過(guò) json 數(shù)據(jù)或代碼支持自定義布局樣式。
- 高性能室梅,基于vlayout
- 支持?jǐn)U展功能模塊
下面來(lái)看看如何使用Tangram戏仓。
2 Tangram使用步驟
2.1 引入依賴(lài)
在APP的build.gradle中添加:
implementation 'com.alibaba.android:tangram:3.3.6@aar'
// we added rxjava in latest version, so need compile rxjava
implementation 'io.reactivex.rxjava2:rxjava:2.1.12'
implementation 'io.reactivex.rxjava2:rxandroid:2.0.2'
其中,Tangram的版本號(hào)可以改成最新的竞惋,最新版本號(hào)可以在這個(gè)鏈接找到:https://github.com/alibaba/Tangram-Android/releases
另外還要添加VirtualView和UltraViewPager這兩個(gè)庫(kù)柜去,否則運(yùn)行時(shí)會(huì)出現(xiàn)問(wèn)題:
implementation ('com.alibaba.android:virtualview:1.4.6@aar') {
transitive = true
}
implementation ('com.alibaba.android:ultraviewpager:1.0.7.7@aar') {
transitive = true
}
VirtualView的最新版本號(hào)可以在這里找到:https://github.com/alibaba/Virtualview-Android/releases
2.2 初始化 Tangram 環(huán)境
應(yīng)用全局只需要初始化一次灰嫉,需要提供一個(gè)圖片加載器拆宛,例如使用Glide庫(kù)或Picasso庫(kù),代碼如下:
TangramBuilder.init(context, new IInnerImageSetter() {
@Override
public <IMAGE extends ImageView> void doLoadImageUrl(@NonNull IMAGE view,
@Nullable String url) {
//假設(shè)你使用 Picasso 加載圖片
Picasso.with(context).load(url).into(view);
}
}, ImageView.class);
2.3 初始化 TangramBuilder
在 Activity 中初始化TangramBuilder讼撒,假如你的 Activity 是TangramActivity浑厚,則代碼如下:
TangramBuilder.InnerBuilder builder = TangramBuilder.newInnerBuilder(TangramActivity.this);
2.4 注冊(cè)自定義的卡片和組件
注冊(cè)組件的方式有如下3種:
(1)注冊(cè)綁定組件類(lèi)型和自定義View,示例代碼:
builder.registerCell("type", TestView.class);
(2)注冊(cè)綁定組件類(lèi)型根盒、自定義 model钳幅、自定義View,示例代碼:
builder.registerCell("type", TestCell.class, TestView.class);
(3)注冊(cè)綁定組件類(lèi)型炎滞、自定義model敢艰、自定義ViewHolder,示例代碼:
builder.registerCell("type", TestCell.class, new ViewHolderCreator<>(R.layout.item_holder, TestViewHolder.class, TestView.class));
這里先不做詳解册赛,關(guān)于卡片和組件的詳細(xì)使用請(qǐng)參見(jiàn)第3節(jié)钠导。
2.5 生成 TangramEngine 實(shí)例
在上述基礎(chǔ)上調(diào)用:
TangramEngine engine = builder.build();
2.6 綁定業(yè)務(wù) support 類(lèi)到 engine
Tangram 內(nèi)部提供了一些常用的 support 類(lèi)輔助業(yè)務(wù)開(kāi)發(fā),具體請(qǐng)見(jiàn)3.3節(jié)森瘪,使用方式有如下3種:
engine.register(SimpleClickSupport.class, new XXClickSupport());
engine.register(CardLoadSupport.class, new XXCardLoadSupport());
engine.register(ExposureSupport.class, new XXExposureSuport());
2.7 綁定 RecyclerView
engine.bindView(recyclerView);
2.8 監(jiān)聽(tīng) RecyclerView 的滾動(dòng)事件
recyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
@Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
super.onScrolled(recyclerView, dx, dy);
//在 scroll 事件中觸發(fā) engine 的 onScroll牡属,內(nèi)部會(huì)觸發(fā)需要異步加載的卡片去提前加載數(shù)據(jù)
engine.onScrolled();
}
});
2.9 設(shè)置懸浮類(lèi)型布局的偏移(可選)
如果你的 RecyclerView 上方還覆蓋有其他 view,比如底部的 tabbar 或者頂部的 actionbar扼睬,為了防止懸浮類(lèi) view 和這些外部 view 重疊逮栅,可以設(shè)置一個(gè)偏移量。此功能需要額外引入vlayout(https://github.com/alibaba/vlayout)。代碼如下:
engine.getLayoutManager().setFixOffset(0, 40, 0, 0);
2.10 設(shè)置卡片預(yù)加載的偏移量(可選)
在頁(yè)面滾動(dòng)過(guò)程中會(huì)觸發(fā)engine.onScrolled()方法措伐,會(huì)去尋找屏幕外需要異步加載數(shù)據(jù)的卡片特纤,默認(rèn)往下尋找5個(gè),讓數(shù)據(jù)預(yù)加載出來(lái)侥加,我們也可以修改這個(gè)偏移量叫潦,代碼如下:
engine.setPreLoadNumber(3);
2.11 加載數(shù)據(jù)并傳遞給 engine
數(shù)據(jù)一般是調(diào)用接口加載遠(yuǎn)程數(shù)據(jù),這里演示的是 mock 加載本地的數(shù)據(jù):
byte[] bytes = Utils.getAssertsFile(this, "data.json");
if (bytes != null) {
String json = new String(bytes);
try {
JSONArray data = new JSONArray(json);
engine.setData(data);
} catch (JSONException e) {
e.printStackTrace();
}
}
2.12 退出的時(shí)候銷(xiāo)毀 engine
engine.destroy();
通過(guò)主動(dòng)調(diào)用 destroy 方法官硝,可以釋放內(nèi)部的資源矗蕊,比如清理 adapter、清理事件總線(xiàn)緩存的未處理消息氢架、注銷(xiāo)廣播等傻咖。注意調(diào)用 destroy 方法之后就不需要調(diào)用 unbind 方法了。
3 組件與布局
3.1 頁(yè)面概念模型
我們將一個(gè)普通的列表頁(yè)面結(jié)構(gòu)化成樹(shù)狀結(jié)構(gòu):分別是頁(yè)面岖研、布局(卡片)和組件卿操。一個(gè)頁(yè)面下面可以?huà)燧d多個(gè)布局或者組件,一個(gè)布局下面可以?huà)燧d多個(gè)組件孙援,整體是一個(gè)樹(shù)狀結(jié)構(gòu)描述害淤。每一層次都有各自的職責(zé),如下圖:
3.1.1 頁(yè)面
如上圖所示拓售,一個(gè)頁(yè)面包含一個(gè)卡片列表窥摄,每個(gè)卡片持有一個(gè)組件列表,整個(gè)頁(yè)面是一個(gè)頁(yè)面 - 布局 - 組件的樹(shù)狀結(jié)構(gòu)。它要求整體可滾動(dòng),并且能按照組件的類(lèi)型去回收復(fù)用敞斋。
在實(shí)現(xiàn)上碍脏,在 Tangram-iOS 里,它是基于 LazyScrollView 的頁(yè)面容器,在 Tangram-Android 里,它是基于 vlayout 構(gòu)建的 RecyclerView。
3.1.2 布局(卡片)
布局的主要職責(zé)是負(fù)責(zé)對(duì)組件進(jìn)行布局决摧,它有四個(gè)組成:header、footer凑兰、body掌桩、style,如下圖所示:
最重要的是 body 部分票摇,它包含了內(nèi)嵌的組件拘鞋,如果布局沒(méi)有 body,即沒(méi)有組件矢门,也就不在視覺(jué)上做渲染盆色』彝埽卡片的布局也就是對(duì) body 里包含的組件來(lái)進(jìn)行布局。Tangram 內(nèi)置了一系列布局能力對(duì)組件進(jìn)行布局隔躲,包括流式布局摩梧、瀑布流布局、吸頂布局宣旱、懸浮布局仅父、輪播布局等等,基本上常見(jiàn)的布局方式都可以覆蓋到浑吟。header笙纤、footer 是卡片的標(biāo)題和尾部,目前只有輪播卡片组力、通用流式卡片支持 header省容、footer。style 是對(duì)布局樣式的描述燎字,所有布局會(huì)有一些通用的樣式屬性比如邊距腥椒、間距,也有一些特有的比如寬高比候衍,通過(guò)樣式的描述笼蛛,可以讓布局能力更加豐富。
布局描述就是一種布局類(lèi)型的聲明蛉鹿,因?yàn)榭蚣芤呀?jīng)內(nèi)置了布局能力滨砍,只需要聲明采用哪一種布局方式,因此不需要布局模板榨为。如果框架的內(nèi)置布局能力滿(mǎn)足不了需求惨好,還可以自定義擴(kuò)展新的布局類(lèi)型注冊(cè)到 Tangram 里。
以下是一個(gè)布局的 json 描述示例(type, style, header, footer, items都是關(guān)鍵字):
[
{
"type": "container-oneColumn", ---> 描述布局類(lèi)型
"style": { ---> 描述樣式
...
},
"header": { ---> 描述header
},
"items": [ ---> 描述組件列表
...
],
"footer": { ---> 描述footer
}
},
...
]
3.1.3 組件
組件的職責(zé)就是負(fù)責(zé)基本的 UI 展示和交互随闺,它是按照業(yè)務(wù)劃分的最小單元,不像通用的 UI 框架那樣會(huì)設(shè)計(jì)文本蔓腐、按鈕矩乐、線(xiàn)條那樣的基礎(chǔ)元素。
在 Tangram 里回论,組件長(zhǎng)什么樣散罕,框架是不知道的,框架內(nèi)也不內(nèi)置組件傀蓉,都是由我們接入的時(shí)候自行按需注冊(cè)欧漱。同布局一樣,組件的數(shù)據(jù)描述也需要提供與 UI 相關(guān)的模板葬燎,包含3部分:類(lèi)型误甚、數(shù)據(jù)缚甩、樣式。類(lèi)型是必須的窑邦,如果我們?cè)?Tangram 里注冊(cè)過(guò)這種類(lèi)型擅威,那么就能被框架解析處理;數(shù)據(jù)也是必須的冈钦,它包含了業(yè)務(wù)信息郊丛;樣式是可選的,組件可以按照自己的需求定義樣式瞧筛,在實(shí)現(xiàn)的時(shí)候解讀樣式數(shù)據(jù)厉熟。
以下是一個(gè)組件的 json 描述示例(type, style都是關(guān)鍵字):
{
"type": "demo", ---> 描述組件類(lèi)型
"style": { ---> 描述組件樣式
"margin": [
10,
10,
10,
10
],
"height": 100,
"width": 100
}
"imgUrl": "[URL]", ---> 業(yè)務(wù)數(shù)據(jù)
"title": "Sample"
}
這樣,就可以將多個(gè)組件的 json 數(shù)據(jù)放到布局的items里较幌,然后將多個(gè)布局的 json 數(shù)據(jù)組合成一個(gè) json 列表庆猫,就形成了一個(gè)頁(yè)面。
下面的步驟绅络,先使用Java進(jìn)行組件的開(kāi)發(fā)月培,并進(jìn)行注冊(cè),然后使用json來(lái)描述整體的布局恩急。
3.2 組件開(kāi)發(fā)
組件分為兩層:model 和 View杉畜。model是對(duì)json數(shù)據(jù)的解析,View就是我們自定義的View衷恭。Tangram 里提供了通用 model 類(lèi)型BaseCell此叠,其包含了對(duì)json數(shù)據(jù)的解析,還有位置等信息随珠。開(kāi)發(fā)組件有兩種方式:
- 采用通用 model灭袁,開(kāi)發(fā)自定義 View;
- 采用自定義 model 和自定義 View窗看。
下面分別進(jìn)行介紹茸歧。
3.2.1 通用 model 開(kāi)發(fā)組件
這種方式無(wú)需關(guān)心 model,主要是開(kāi)發(fā)自定義 View显沈。
自定義 View 有兩種實(shí)現(xiàn)規(guī)范:
(1)使用接口方式软瞎,避免了反射調(diào)用,性能上更優(yōu)拉讯,步驟如下:
- 實(shí)現(xiàn)一個(gè)自定義View涤浇,比如XXTangramView;
- 實(shí)現(xiàn)接口ITangramViewLifeCycle魔慷,包含三個(gè)方法:
public void cellInited(BaseCell cell); // 綁定數(shù)據(jù)前調(diào)用
public void postBindView(BaseCell cell); // 綁定數(shù)據(jù)時(shí)機(jī)
public void postUnBindView(BaseCell cell); // 滑出屏幕只锭,解除綁定
- 主要在上述<b>public void postBindView(BaseCell cell)</b>方法里完成組件業(yè)務(wù)邏輯。
下面實(shí)現(xiàn)一個(gè)示例院尔,其中包含一個(gè)ImageView和一個(gè)TextView蜻展,代碼如下:
public class CustomInterfaceView extends LinearLayout implements ITangramViewLifeCycle {
private ImageView mImageView;
private TextView mTextView;
public CustomInterfaceView(Context context) {
super(context);
init();
}
public CustomInterfaceView(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
init();
}
public CustomInterfaceView(Context context, @Nullable AttributeSet attrs,
int defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
}
private void init() {
setOrientation(VERTICAL);
setGravity(Gravity.CENTER);
int padding = Utils.dip2px(getContext(), 10);
setPadding(padding, padding, padding, padding);
mImageView = new ImageView(getContext());
addView(mImageView, LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
mTextView = new TextView(getContext());
mTextView.setPadding(0, padding, 0, 0);
addView(mTextView, LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
}
@Override
public void cellInited(BaseCell cell) {
}
@Override
public void postBindView(BaseCell cell) {
if (cell.pos % 2 == 0) {
setBackgroundColor(0xffff0000);
mImageView.setImageResource(R.mipmap.ic_launcher);
} else {
setBackgroundColor(0xff00ff00);
mImageView.setImageResource(R.mipmap.ic_launcher_round);
}
mTextView.setText(String.format(Locale.CHINA, "%s%d: %s", getClass().getSimpleName(),
cell.pos, cell.optParam("text")));
}
@Override
public void postUnBindView(BaseCell cell) {
}
}
(2)使用注解方式喉誊,動(dòng)態(tài)綁定數(shù)據(jù):
- 實(shí)現(xiàn)一個(gè)自定義View,比如XXTangramView铺呵;
- 必須添加下面三個(gè)方法裹驰,以@CellRender注解,功能同上片挂,只是被反射調(diào)用:
public void cellInited(BaseCell cell)幻林;
public void postBindView(BaseCell cell);
public void postUnBindView(BaseCell cell)音念;
- 還可以為組件的每個(gè)屬性實(shí)現(xiàn)單獨(dú)的設(shè)置方法沪饺,而不是在postBindView方法里一次性綁定數(shù)據(jù),這些方法必須以@CellRender注解闷愤,框架會(huì)在<b>public void postBindView(BaseCell cell)</b>方法調(diào)用之前調(diào)用這些數(shù)據(jù)綁定方法整葡,示例代碼:
@CellRender(key = "pos") //這里的key=pos表示讓框架取原始json數(shù)據(jù)里pos字段的值傳給該方法,原始數(shù)據(jù)里沒(méi)有該字段讥脐,參數(shù)值會(huì)是該類(lèi)型的默認(rèn)值
public void setPosition(int pos) {//這里pos的類(lèi)型要注意遭居,是框架會(huì)以該方法聲明的類(lèi)型來(lái)取獲取原始數(shù)據(jù)
textView.setText(cell.id + " pos: " + pos + " " + cell.parent + " " + cell.optParam("msg"));
if (pos > 57) {
textView.setBackgroundColor(0x66cccf00 + (pos - 50) * 128);
} else if (pos % 2 == 0) {
textView.setBackgroundColor(0xaaaaff55);
} else {
textView.setBackgroundColor(0xccfafafa);
}
}
下面采用注解的方式實(shí)現(xiàn)之前用接口方式實(shí)現(xiàn)的自定義View,代碼如下:
public class CustomAnnotationView extends LinearLayout {
private ImageView mImageView;
private TextView mTextView;
public CustomAnnotationView(Context context) {
super(context);
init();
}
public CustomAnnotationView(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
init();
}
public CustomAnnotationView(Context context, @Nullable AttributeSet attrs,
int defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
}
private void init() {
setOrientation(VERTICAL);
setGravity(Gravity.CENTER);
int padding = Utils.dip2px(getContext(), 10);
setPadding(padding, padding, padding, padding);
mImageView = new ImageView(getContext());
addView(mImageView, LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
mTextView = new TextView(getContext());
mTextView.setPadding(0, padding, 0, 0);
addView(mTextView, LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
}
@CellRender
public void cellInited(BaseCell cell) {
}
@CellRender
public void postBindView(BaseCell cell) {
if (cell.pos % 2 == 0) {
setBackgroundColor(0xff0000ff);
mImageView.setImageResource(R.mipmap.ic_launcher);
} else {
setBackgroundColor(0xff00ffff);
mImageView.setImageResource(R.mipmap.ic_launcher_round);
}
mTextView.setText(String.format(Locale.CHINA, "%s%d: %s", getClass().getSimpleName(),
cell.pos, cell.optParam("text")));
}
@CellRender
public void postUnBindView(BaseCell cell) {
}
}
以上兩種方式開(kāi)發(fā)的組件在頁(yè)面初始化的時(shí)候調(diào)用TangramBuilder.InnerBuilder的<b>registerCell(String type, Class<V> viewClz)</b>方法進(jìn)行注冊(cè)旬渠,也就是2.4節(jié)的第一種注冊(cè)方式俱萍,代碼如下:
builder.registerCell("InterfaceCell", CustomInterfaceView.class);
builder.registerCell("AnnotationCell", CustomAnnotationView.class);
意思是類(lèi)型為“InterfaceCell”的組件渲染時(shí)會(huì)被綁定到CustomInterfaceView的實(shí)例上,類(lèi)型為“AnnotationCell”的組件渲染時(shí)會(huì)被綁定到CustomAnnotationView的實(shí)例上告丢,這種方式注冊(cè)的組件使用通用的組件模型BaseCell枪蘑。
在自定義 View 里訪(fǎng)問(wèn) json 數(shù)據(jù):
組件的View對(duì)應(yīng)于一個(gè)統(tǒng)一的model,類(lèi)型是BaseCell岖免,要在View里訪(fǎng)問(wèn) json 數(shù)據(jù)岳颇,BaseCell提供了以下方法:
public boolean hasParam(String key)
public Object optParam(String key)
public long optLongParam(String key)
public int optIntParam(String key)
public Stirng optStringParam(String key)
public double optDoubleParam(String key)
public boolean optBoolParam(String key)
public JsonObject optJsonObjectParam(String key)
public JsonArray optJsonArrayParam(String key)
這些方法都會(huì)先訪(fǎng)問(wèn)BaseCell里持有的原始json數(shù)據(jù),同時(shí)支持訪(fǎng)問(wèn)style節(jié)點(diǎn)下的屬性颅湘。
例如上面示例代碼的第46~47行中使用的代碼cell.optParam("text")话侧,它的含義就是取json數(shù)據(jù)中key為“text”的字段。
3.2.2 自定義 model 開(kāi)發(fā)組件
采用通用的 model 開(kāi)發(fā)組件栅炒,只需要寫(xiě) View 就可以了掂摔,然而需要在每次綁定數(shù)據(jù)的時(shí)候都要取原始 json 數(shù)據(jù)解析一下字段。有時(shí)候我們會(huì)有一些通用的業(yè)務(wù)字段定義赢赊,每個(gè)組件里重復(fù)解析會(huì)讓代碼顯得冗余,因此也提供了注冊(cè)自定義 model 的兼容模式開(kāi)發(fā)組件级历。這個(gè)時(shí)候就需要寫(xiě)自定義 model 和自定義 View 兩部分了释移。
(1)自定義 model 開(kāi)發(fā)
- 實(shí)現(xiàn)一個(gè)自定義 model 類(lèi),繼承自 BaseCell寥殖。
- 實(shí)現(xiàn)以下幾個(gè)方法:
/** 解析數(shù)據(jù)業(yè)務(wù)數(shù)據(jù)玩讳,可以將解析值緩存到成員變量里 */
public void parseWith(JSONObject data)
/** 解析數(shù)據(jù)樣式數(shù)據(jù)涩蜘,可以將解析值緩存到成員變量里 */
public void parseStyle(@Nullable JSONObject data)
/** 綁定數(shù)據(jù)到自定義 View */
public void bindView(@NonNull V view)
/** 綁定數(shù)據(jù)到 View 之后,可選實(shí)現(xiàn) */
public void postBindView(@NonNull V view)
/** 校驗(yàn)原始數(shù)據(jù)熏纯,檢查組件的合法性 */
public boolean isValid()
示例代碼:
public class CustomCell extends BaseCell<CustomCellView> {
private String imageUrl;
private String text;
@Override
public void parseWith(@NonNull JSONObject data, @NonNull MVHelper resolver) {
try {
if (data.has("imageUrl")) {
imageUrl = data.getString("imageUrl");
}
if (data.has("text")) {
text = data.getString("text");
}
} catch (JSONException e) {
e.printStackTrace();
}
}
@Override
public void bindView(@NonNull CustomCellView view) {
if (pos % 2 == 0) {
view.setBackgroundColor(0xffff00ff);
} else {
view.setBackgroundColor(0xffffff00);
}
view.setImageUrl(imageUrl);
view.setText(view.getClass().getSimpleName() + pos + ": " + text);
}
}
(2)自定義 View 開(kāi)發(fā)
類(lèi)似于3.2.1節(jié)同诫,示例代碼:
public class CustomCellView extends LinearLayout {
private ImageView mImageView;
private TextView mTextView;
public CustomCellView(Context context) {
super(context);
init();
}
public CustomCellView(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
init();
}
public CustomCellView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
}
private void init() {
setOrientation(VERTICAL);
setGravity(Gravity.CENTER);
int padding = Utils.dip2px(getContext(), 10);
setPadding(padding, padding, padding, padding);
mImageView = new ImageView(getContext());
addView(mImageView, Utils.dip2px(getContext(), 110), Utils.dip2px(getContext(), 72));
mTextView = new TextView(getContext());
mTextView.setPadding(0, padding, 0, 0);
addView(mTextView, LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
}
public void setImageUrl(String url) {
Glide.with(this).load(url).into(mImageView);
}
public void setText(String text) {
mTextView.setText(text);
}
}
這種方式開(kāi)發(fā)的組件在頁(yè)面初始化的時(shí)候調(diào)用TangramBuilder.InnerBuilder的<b>registerCell(int type, @NonNull Class<? extends BaseCell> cellClz, @NonNull Class<V> viewClz)</b>方法進(jìn)行注冊(cè),也就是2.4節(jié)的第二種注冊(cè)方式樟澜,代碼如下:
builder.registerCell("CustomCell", CustomCell.class, CustomCellView.class);
意思是類(lèi)型為“CustomCell”的組件使用自定義的組件模型CustomCell误窖,在渲染時(shí)會(huì)被綁定到CustomCellView的實(shí)例上。
也可采用2.4節(jié)的第三種注冊(cè)方式秩贰,即注冊(cè)綁定組件類(lèi)型霹俺、自定義model、自定義ViewHolder毒费,示例代碼:
builder.registerCell("HolderCell", CustomHolderCell.class,
new ViewHolderCreator<>(R.layout.item_holder, CustomViewHolder.class, TextView.class));
意思是類(lèi)型為“HolderCell”的組件使用自定義的組件模型CustomHolderCell丙唧,在渲染時(shí)以R.layout.item_holder為布局創(chuàng)建類(lèi)型為TextView的 view,并綁定到類(lèi)型為CustomViewHolder的 viewHolder 上觅玻,組件數(shù)據(jù)被綁定到TextView的實(shí)例上想际。
CustomHolderCell.java的代碼如下:
public class CustomHolderCell extends BaseCell<TextView> {
@Override
public void bindView(@NonNull TextView view) {
if (pos % 2 == 0) {
view.setBackgroundColor(0xff000fff);
} else {
view.setBackgroundColor(0xfffff000);
}
view.setText(String.format(Locale.CHINA, "%s%d: %s", getClass().getSimpleName(), pos,
optParam("text")));
}
}
item_holder.xml的代碼如下:
<?xml version="1.0" encoding="utf-8"?>
<TextView android:id="@+id/title"
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="130dp"
android:gravity="center"
android:textColor="#999999"
android:textSize="12sp"
android:textStyle="bold"/>
CustomViewHolder.java的代碼如下:
public class CustomViewHolder extends ViewHolderCreator.ViewHolder {
public TextView textView;
public CustomViewHolder(Context context) {
super(context);
}
@Override
protected void onRootViewCreated(View view) {
textView = (TextView) view;
}
}
一般情況下,使用上一節(jié)和這一節(jié)的第一種注冊(cè)方式這兩種方式注冊(cè)組件即可溪厘。
3.2.3 json數(shù)據(jù)開(kāi)發(fā)
下面針對(duì)上面開(kāi)發(fā)的幾種組件胡本,根據(jù)3.1節(jié)的格式寫(xiě)一個(gè)簡(jiǎn)單的json示例。
在項(xiàng)目的assets文件夾下(沒(méi)有的話(huà)自己建一個(gè))新建data.json桩匪,代碼如下:
[
{
"type": "container-oneColumn",
"items": [
{
"type": "InterfaceCell",
"text": "接口方式自定義View"
},
{
"type": "AnnotationCell",
"text": "注解方式自定義View"
},
{
"type": "CustomCell",
"text": "自定義model",
"imageUrl" : "https://gw.alicdn.com/tfs/TB1vqF.PpXXXXaRaXXXXXXXXXXX-110-72.png"
},
{
"type": "HolderCell",
"text": "自定義model和ViewHolder"
}
]
}
]
運(yùn)行APP打瘪,顯示效果如下:
3.3 處理業(yè)務(wù)邏輯
3.3.1 處理點(diǎn)擊
此時(shí)所有組件可以顯示出來(lái)了,但是還沒(méi)有點(diǎn)擊事件傻昙。組件 View 的點(diǎn)擊處理闺骚,可以實(shí)現(xiàn)SimpleClickSupport,然后在組件自定義的 View 內(nèi)部調(diào)用setOnClickListener(cell);妆档,那么組件的點(diǎn)擊行為會(huì)被回調(diào)到SimpleClickSupport里統(tǒng)一處理僻爽。
當(dāng)然使用SimpleClickSupport只是一種推薦的使用方式,我們選擇自定義的點(diǎn)擊處理也是可以的贾惦。
選用SimpleClickSupport的時(shí)候有幾個(gè)注意點(diǎn):
- 建議開(kāi)啟優(yōu)化模式 —— setOptimizedMode(true)胸梆,這樣點(diǎn)擊會(huì)被統(tǒng)一回調(diào)到<b>public void
defaultClick(View v, BaseCell cell, int pos)</b>方法里,我們就可以根據(jù)組件類(lèi)型须板、View的類(lèi)型即組件類(lèi)型做點(diǎn)擊處理碰镜。 - 如果不開(kāi)啟優(yōu)化模式,SimpleClickSupport內(nèi)部會(huì)跟進(jìn)被點(diǎn)擊的組件類(lèi)型和 View 的類(lèi)型來(lái)做路由习瑰,它要求我們?cè)?strong>SimpleClickSupport的實(shí)現(xiàn)類(lèi)里為每個(gè)組件的點(diǎn)擊提供以 onClickXXX 或者 onXXXClick 為命名規(guī)范的點(diǎn)擊處理方法绪颖,并且參數(shù)列表是View targetView, BaseCell cell, int type或者View targetView, BaseCell cell, int type, Map<String, Object> params。這種方式效率會(huì)低一些甜奄,針對(duì)采用自定義 model 開(kāi)發(fā)組件的時(shí)候有用柠横。
那么我們新建一個(gè)CustomClickSupport類(lèi)窃款,代碼如下:
public class CustomClickSupport extends SimpleClickSupport {
public CustomClickSupport() {
setOptimizedMode(true);
}
@Override
public void defaultClick(View targetView, BaseCell cell, int eventType) {
Toast.makeText(targetView.getContext(),
"您點(diǎn)擊了組件,type=" + cell.stringType + ", pos=" + cell.pos, Toast.LENGTH_SHORT).show();
}
}
在CustomInterfaceView.java和CustomAnnotationView.java的<b>cellInited()</b>方法中添加如下代碼:
public void cellInited(BaseCell cell) {
setOnClickListener(cell);
}
在CustomCell.java的<b>bindView()</b>方法中添加如下代碼:
@Override
public void bindView(@NonNull CustomCellView view) {
...
view.setOnClickListener(this);
}
在CustomHolderCell.java的<b>bindView()</b>方法中添加如下代碼:
@Override
public void bindView(@NonNull TextView view) {
...
view.setOnClickListener(this);
}
最后如2.6節(jié)在初始化的時(shí)候注冊(cè)一下牍氛,代碼如下:
engine.addSimpleClickSupport(new CustomClickSupport());
運(yùn)行一下APP晨继,點(diǎn)擊每個(gè)組件,就會(huì)彈出toast提示了搬俊。
3.3.2 處理曝光
所謂曝光紊扬,就是被 RecyclerView 的 Adapter 綁定數(shù)據(jù)的那個(gè)時(shí)候,也就是某個(gè)Item即將滑動(dòng)到屏幕范圍內(nèi)的時(shí)候悠抹。在這個(gè)時(shí)候業(yè)務(wù)上如果需要有一些處理珠月,就需要實(shí)現(xiàn)ExposureSupport類(lèi)。它定義了3個(gè)層面的曝光接口楔敌,一是曝光布局啤挎,二是曝光組件整體區(qū)域,三是曝光組件局部區(qū)域卵凑。我們實(shí)現(xiàn)它的子類(lèi)庆聘,并針對(duì)這三個(gè)層面的曝光做分別的實(shí)現(xiàn)。分別說(shuō)明如下:
(1)布局的整體曝光
需要重寫(xiě)回調(diào)接口方法public abstract void onExposure(@NonNull Card card, int offset, int position);勺卢。新建CustomExposureSupport.java伙判,代碼如下:
public class CustomExposureSupport extends ExposureSupport {
private static final String TAG = "CustomExposureSupport";
public CustomExposureSupport() {
setOptimizedMode(true);
}
@Override
public void onExposure(@NonNull Card card, int offset, int position) {
Log.d(TAG, "onExposure: card=" + card.getClass().getSimpleName() + ", offset=" + offset + ", position=" + position);
}
}
然后如2.6節(jié)在初始化的時(shí)候注冊(cè)一下,代碼如下:
engine.addExposureSupport(new CustomExposureSupport());
為了方便說(shuō)明黑忱,我們將data.json改成如下代碼:
[
{
"type": "container-oneColumn",
"items": [
{
"type": "InterfaceCell",
"text": "接口方式自定義View"
},
{
"type": "InterfaceCell",
"text": "接口方式自定義View"
}
}
]
},
{
"type": "container-oneColumn",
"items": [
{
"type": "AnnotationCell",
"text": "注解方式自定義View"
},
{
"type": "AnnotationCell",
"text": "注解方式自定義View"
}
]
},
{
"type": "container-oneColumn",
"items": [
{
"type": "CustomCell",
"text": "自定義model",
"imageUrl": "https://gw.alicdn.com/tfs/TB1vqF.PpXXXXaRaXXXXXXXXXXX-110-72.png"
},
{
"type": "CustomCell",
"text": "自定義model",
"imageUrl": "https://gw.alicdn.com/tfs/TB1vqF.PpXXXXaRaXXXXXXXXXXX-110-72.png"
}
]
},
{
"type": "container-oneColumn",
"items": [
{
"type": "HolderCell",
"text": "自定義model和ViewHolder"
},
{
"type": "HolderCell",
"text": "自定義model和ViewHolder"
}
]
}
]
運(yùn)行APP宴抚,當(dāng)我們滾到每一個(gè)布局的時(shí)候,就會(huì)打印一條日志甫煞,全部日志如下:
D/CustomExposureSupport: onExposure: card=SingleColumnCard, offset=0, position=0
D/CustomExposureSupport: onExposure: card=SingleColumnCard, offset=0, position=2
D/CustomExposureSupport: onExposure: card=SingleColumnCard, offset=0, position=4
D/CustomExposureSupport: onExposure: card=SingleColumnCard, offset=0, position=6
(2)組件的整體曝光
- 建議開(kāi)啟優(yōu)化模式 —— setOptimizedMode(true)菇曲,這樣曝光接口被統(tǒng)一回調(diào)到<b>public void defaultExposureCell(@NonNull View targetView, @NonNull BaseCell cell, int type)</b>方法里,我們可以根據(jù)組件類(lèi)型抚吠、View的類(lèi)型即組件類(lèi)型做曝光處理常潮。
- 如果不開(kāi)啟優(yōu)化模式,ExposureSupport內(nèi)部會(huì)跟進(jìn)被點(diǎn)擊的組件類(lèi)型和 View 的類(lèi)型來(lái)做路由楷力,它要求我們?cè)?strong>ExposureSupport的實(shí)現(xiàn)類(lèi)里為每個(gè)組件的點(diǎn)擊提供以 onExposureXXX 或者 onXXXExposure 為命名規(guī)范的點(diǎn)擊處理方法喊式,并且參數(shù)列表是View targetView, BaseCell cell, int type。這種方式效率會(huì)低一些萧朝,針對(duì)采用自定義 model 開(kāi)發(fā)組件的時(shí)候有用岔留。
在CustomExposureSupport.java里添加如下代碼:
@Override
public void defaultExposureCell(@NonNull View targetView, @NonNull BaseCell cell, int type) {
Log.d(TAG, "defaultExposureCell: targetView=" + targetView.getClass().getSimpleName() + ", pos=" + cell.pos + ", type=" + type);
}
運(yùn)行APP,當(dāng)我們滾到每一個(gè)組件的時(shí)候检柬,就會(huì)打印一條日志贸诚,全部日志如下:
D/CustomExposureSupport: defaultExposureCell: targetView=CustomInterfaceView, pos=0, type=0
D/CustomExposureSupport: defaultExposureCell: targetView=CustomInterfaceView, pos=1, type=1
D/CustomExposureSupport: defaultExposureCell: targetView=CustomAnnotationView, pos=0, type=0
D/CustomExposureSupport: defaultExposureCell: targetView=CustomAnnotationView, pos=1, type=1
D/CustomExposureSupport: defaultExposureCell: targetView=CustomCellView, pos=0, type=0
D/CustomExposureSupport: defaultExposureCell: targetView=CustomCellView, pos=1, type=1
D/CustomExposureSupport: defaultExposureCell: targetView=AppCompatTextView, pos=0, type=0
D/CustomExposureSupport: defaultExposureCell: targetView=AppCompatTextView, pos=1, type=1
(3)組件的局部區(qū)域曝光
- 建議開(kāi)啟優(yōu)化模式 —— setOptimizedMode(true),這樣曝光接口被統(tǒng)一回調(diào)到<b>public void defaultTrace(@NonNull View targetView, @NonNull BaseCell cell, int type)</b>方法里厕吉,我們可以根據(jù)組件類(lèi)型酱固、View的類(lèi)型即組件類(lèi)型做曝光處理。
- 如果不開(kāi)啟優(yōu)化模式头朱,ExposureSupport內(nèi)部會(huì)跟進(jìn)被點(diǎn)擊的組件類(lèi)型和 View 的類(lèi)型來(lái)做路由运悲,它要求我們?cè)?strong>ExposureSupport的實(shí)現(xiàn)類(lèi)里為每個(gè)組件的點(diǎn)擊提供以 onTraceXXX 或者 onXXXTrace 為命名規(guī)范的點(diǎn)擊處理方法,并且參數(shù)列表是View targetView, BaseCell cell, int type项钮。這種方式效率會(huì)低一些班眯,針對(duì)采用自定義 model 開(kāi)發(fā)組件的時(shí)候有用。
前兩個(gè)層面的曝光調(diào)用都是框架層調(diào)用烁巫,而組件局部曝光署隘,則需要我們?cè)诮M件邏輯里自行調(diào)用。使用方式如下:
ExposureSupport exposureSupport = serviceManager.getService(ExposureSupport.class);
if (exposureSupport != null) {
exposureSupport.onTrace(view, cell, type);
}
那么亚隙,我們?cè)?strong>CustomInterfaceView.java和CustomAnnotationView.java的<b>cellInited()</b>方法中添加如下代碼:
public void cellInited(BaseCell cell) {
...
if (cell.serviceManager != null) {
ExposureSupport exposureSupport = cell.serviceManager.getService(ExposureSupport.class);
if (exposureSupport != null) {
exposureSupport.onTrace(this, cell, cell.type);
}
}
}
然后在CustomCell.java的<b>bindView()</b>方法中添加如下代碼:
@Override
public void bindView(@NonNull CustomCellView view) {
...
if (serviceManager != null) {
ExposureSupport exposureSupport = serviceManager.getService(ExposureSupport.class);
if (exposureSupport != null) {
exposureSupport.onTrace(view, this, type);
}
}
}
在CustomHolderCell.java的<b>bindView()</b>方法中添加如下代碼:
@Override
public void bindView(@NonNull TextView view) {
...
if (serviceManager != null) {
ExposureSupport exposureSupport = serviceManager.getService(ExposureSupport.class);
if (exposureSupport != null) {
exposureSupport.onTrace(view, this, type);
}
}
}
最后在CustomExposureSupport.java里添加如下代碼:
@Override
public void defaultTrace(@NonNull View targetView, @NonNull BaseCell cell, int type) {
Log.d(TAG, "defaultTrace: targetView=" + targetView.getClass().getSimpleName() + ", pos=" + cell.pos + ", type=" + type);
}
運(yùn)行APP磁餐,當(dāng)我們滾到每一個(gè)組件的時(shí)候,就會(huì)打印一條日志阿弃。與(2)不同的是诊霹,(2)是在第一次曝光的時(shí)候調(diào)用,而(3)是在每次曝光的時(shí)候都會(huì)調(diào)用渣淳。全部日志如下:
D/CustomExposureSupport: defaultTrace: targetView=CustomInterfaceView, pos=0, type=0
D/CustomExposureSupport: defaultTrace: targetView=CustomInterfaceView, pos=1, type=0
D/CustomExposureSupport: defaultTrace: targetView=CustomAnnotationView, pos=0, type=0
D/CustomExposureSupport: defaultTrace: targetView=CustomAnnotationView, pos=1, type=0
D/CustomExposureSupport: defaultTrace: targetView=CustomCellView, pos=0, type=0
D/CustomExposureSupport: defaultTrace: targetView=CustomCellView, pos=1, type=0
D/CustomExposureSupport: defaultTrace: targetView=AppCompatTextView, pos=0, type=0
D/CustomExposureSupport: defaultTrace: targetView=AppCompatTextView, pos=1, type=0
3.3.3 異步加載數(shù)據(jù)
有時(shí) Tangram 的頁(yè)面的數(shù)據(jù)無(wú)法一次性返回脾还,有些區(qū)塊布局內(nèi)的數(shù)據(jù)需要異步加載、甚至分頁(yè)加載入愧。Tangram 里內(nèi)置了封裝了異步加載的邏輯鄙漏,需要各個(gè)層面配合完成,這里加以說(shuō)明:
(1)布局model 的load棺蛛,loadParams怔蚌,loadType,hasMore
- load 是接口名稱(chēng)鞠值,表示這個(gè)布局需要執(zhí)行異步加載的接口媚创。
- loadParams 是異步加載接口的常規(guī)參數(shù)字典,需要在調(diào)用接口時(shí)透?jìng)鳌?/li>
- loadType 是異步加載的方式彤恶,-1表示需要異步加載钞钙,1表示需要異步加載且有分頁(yè)。
- hasMore 與 loadType 配合声离,當(dāng) loadType = 1 的時(shí)候表示分頁(yè)是否結(jié)束芒炼。
示例代碼:
{
"id": "Shop",
"load": "queryShop",
"loadType": "-1",
"type": "container-oneColumn"
}
(2)setPreLoadNumber(int preLoadNumber)
調(diào)用TangramEngine上的setPreLoadNumber(int preLoadNumber)方法,設(shè)置觸發(fā)卡片預(yù)加載的時(shí)機(jī)术徊,默認(rèn) preLoadNumber 是5本刽,表示在滑動(dòng)過(guò)程中,提前去觸發(fā)可見(jiàn)范圍之外5塊布局以?xún)?nèi)的異步加載邏輯∽釉ⅲ可以通過(guò)這個(gè)接口調(diào)整預(yù)加載的范圍暗挑。
(3)onScrolled()
在recyclerView 的 onScrollListener里調(diào)用TangramEngine上的onScrolled()方法,觸發(fā)預(yù)加載的邏輯斜友,代碼如2.8節(jié)炸裆。
(4)CardLoadSupport與AsyncLoader,AsyncPageLoader
在配置完上述異步加載的基礎(chǔ)設(shè)置之后鲜屏,提供一個(gè)自定義的CardLoadSupport服務(wù)烹看,該服務(wù)需要提供一個(gè)自定義的AsyncLoader和AsyncPageLoader。
AsyncLoader的loadData(final Card card, @NonNull final LoadedCallback loadedCallback)方法回調(diào)是卡片異步加載的入口洛史。加載完成之后通過(guò)LoadedCallback的回寫(xiě)接口告知布局加載是否完成惯殊。
AsyncPageLoader的loadData(int page, @NonNull Card card, @NonNull final LoadedCallback callback)方法是回調(diào)卡片分頁(yè)加載的入口。加載完成之后通過(guò)LoadedCallback的回寫(xiě)接口告知布局加載是否完成也殖,是否還有下一頁(yè)土思。
代碼如下:
CardLoadSupport mCardLoadSupport = new CardLoadSupport(new AsyncLoader() {
@Override
public void loadData(final Card card, @NonNull final LoadedCallback callback) {
//...
}
}, new AsyncPageLoader() {
@Override
public void loadData(int page, @NonNull Card card,
@NonNull LoadedCallback callback) {
//...
}
});
CardLoadSupport.setInitialPage(1);
engine.addCardLoadSupport(mCardLoadSupport);
AsyncLoader加載成功之后回調(diào)示例代碼:
List<BaseCell> cells = engine.parseComponent(jsonObject.optJSONArray("items"));
callback.finish(cells);
AsyncPageLoader加載成功之后回調(diào)示例代碼:
List<BaseCell> cells = engine.parseComponent(jsonObject.optJSONArray("items"));
callback.finish(cells, itemHashMore);
我們就不在demo實(shí)現(xiàn)了,有興趣的讀者可以去深入研究毕源。
更多組件高級(jí)用法浪漠,例如使用定時(shí)器等,可參看http://tangram.pingguohe.net/docs/android/use-timer
3.4 布局(卡片)
Tangram的強(qiáng)大之處就在于可以在json中混合各種類(lèi)型的布局霎褐,其主要職責(zé)是負(fù)責(zé)對(duì)組件進(jìn)行布局址愿。
Tangram 內(nèi)置了一系列布局能力對(duì)組件進(jìn)行布局,包括流式布局冻璃、瀑布流布局响谓、吸頂布局、懸浮布局省艳、輪播布局等等娘纷,基本上常見(jiàn)的布局方式都可以覆蓋到。
下面對(duì)各種布局分別進(jìn)行闡述跋炕。
3.4.1 流式(網(wǎng)格)布局
流式布局是最常用的布局赖晶。詳細(xì)說(shuō)明
type | 對(duì)應(yīng)類(lèi)型 |
---|---|
container-oneColumn | 單列(一排一) |
container-twoColumn | 雙列 |
container-threeColumn | 三列 |
container-fourColumn | 四列 |
container-fiveColumn | 五列 |
container-flow | N列 |
3.4.2 一拖N布局
左邊一個(gè)大的,右邊N個(gè)小的辐烂,可調(diào)整比例遏插。詳細(xì)說(shuō)明
有三種樣式:
- 左邊一個(gè),右邊上面一個(gè)下面一個(gè)
- 左邊一個(gè)纠修,右邊上面一個(gè)下面兩個(gè)
- 左邊一個(gè)胳嘲,右邊上面一個(gè)下面三個(gè)
會(huì)根據(jù)數(shù)據(jù)的數(shù)量自動(dòng)區(qū)分
type | 對(duì)應(yīng)類(lèi)型 |
---|---|
container-onePlusN | 一拖2/3/4 |
3.4.3 浮動(dòng)布局
可拖動(dòng),自動(dòng)吸邊扣草。詳細(xì)說(shuō)明
type | 對(duì)應(yīng)類(lèi)型 |
---|---|
container-float | 浮標(biāo) |
3.4.4 固定布局
固定在某個(gè)位置了牛,不可拖動(dòng)颜屠。詳細(xì)說(shuō)明
type | 對(duì)應(yīng)類(lèi)型 |
---|---|
container-fix | 固定頂部或者底部,根據(jù)屬性指定 |
container-scrollFix | 滾動(dòng)固定(滾動(dòng)到某個(gè)布局的時(shí)候鹰祸,出現(xiàn)并固定) |
3.4.5 吸頂布局
碰到Tangram的頂端或底端就吸住甫窟。詳細(xì)說(shuō)明
type | 對(duì)應(yīng)類(lèi)型 |
---|---|
container-sticky | 吸頂或吸底,根據(jù)屬性指定 |
3.4.6 輪播滾動(dòng)布局
適用于Banner的場(chǎng)景福荸,按頁(yè)可自動(dòng)滾動(dòng)蕴坪,循環(huán)滾動(dòng)。詳細(xì)說(shuō)明
type | 對(duì)應(yīng)類(lèi)型 |
---|---|
container-banner | 輪播 |
3.4.7 橫向滾動(dòng)布局
適用于做線(xiàn)性的滾動(dòng)敬锐,而不是Banner一頁(yè)一頁(yè)的滾動(dòng)。詳細(xì)說(shuō)明
type | 對(duì)應(yīng)類(lèi)型 |
---|---|
container-scroll | 線(xiàn)性滾動(dòng)呆瞻,不像輪播一樣具有一頁(yè)一頁(yè)的效果 |
3.4.8 瀑布流布局
type | 對(duì)應(yīng)類(lèi)型 |
---|---|
container-waterfall | 瀑布流 |
4 示例
基于上面已經(jīng)實(shí)現(xiàn)的組件台夺,我實(shí)現(xiàn)了一個(gè)示例,包含了上面列出的所有類(lèi)型的布局痴脾,效果如下:
修改的data.json請(qǐng)見(jiàn):https://github.com/jimmysuncpt/TangramDemo/blob/master/app/src/main/assets/data.json颤介,大家可以把最后的container-oneColumn去掉,這個(gè)是后面用來(lái)演示VirtualView的赞赖。
為了演示設(shè)置背景滚朵,我還新增了一個(gè)無(wú)背景的類(lèi)型,無(wú)背景的自定義View代碼請(qǐng)見(jiàn):https://github.com/jimmysuncpt/TangramDemo/blob/master/app/src/main/java/com/jimmysun/tangramdemo/tangram/NoBackgroundView.java
別忘了在初始化的時(shí)候注冊(cè)一下:
builder.registerCell("NoBackground", NoBackgroundView.class);
完整示例請(qǐng)見(jiàn):https://github.com/jimmysuncpt/TangramDemo
以上前域,我們學(xué)習(xí)了Tangram的概念辕近、使用步驟以及組件與布局的開(kāi)發(fā)。
在 Tangram 體系里匿垄,頁(yè)面結(jié)構(gòu)可以通過(guò)配置動(dòng)態(tài)更新移宅,然而業(yè)務(wù)組件是通過(guò) Java 代碼實(shí)現(xiàn)的,無(wú)法動(dòng)態(tài)更新椿疗。為了解決業(yè)務(wù)組件的動(dòng)態(tài)更新漏峰,阿里后來(lái)又提出了VirtualView,具體教程可參見(jiàn)Android動(dòng)態(tài)界面開(kāi)發(fā)框架VirtualView使用完整教程届榄。