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

閱讀本文大概需要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
另外還要添加VirtualViewUltraViewPager這兩個(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é),如下圖:


頁(yè)面概念模型

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è)組成:headerfooter凑兰、body掌桩、style,如下圖所示:

卡片組成

布局結(jié)構(gòu)

最重要的是 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ā)組件有兩種方式:

  1. 采用通用 model灭袁,開(kāi)發(fā)自定義 View;
  2. 采用自定義 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.javaCustomAnnotationView.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è)自定義的AsyncLoaderAsyncPageLoader

AsyncLoaderloadData(final Card card, @NonNull final LoadedCallback loadedCallback)方法回調(diào)是卡片異步加載的入口洛史。加載完成之后通過(guò)LoadedCallback的回寫(xiě)接口告知布局加載是否完成惯殊。

AsyncPageLoaderloadData(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 瀑布流布局

詳細(xì)說(shuō)明

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使用完整教程届榄。

參考鏈接

  1. https://github.com/alibaba/tangram-android
  2. http://tangram.pingguohe.net/docs/basic-concept/concept
  3. http://tangram.pingguohe.net/docs/android/access-tangram
  4. http://www.reibang.com/p/b339c2d2d500
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末浅乔,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子铝条,更是在濱河造成了極大的恐慌靖苇,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,602評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件攻晒,死亡現(xiàn)場(chǎng)離奇詭異顾复,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)鲁捏,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,442評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門(mén)芯砸,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)萧芙,“玉大人,你說(shuō)我怎么就攤上這事假丧∷荆” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 152,878評(píng)論 0 344
  • 文/不壞的土叔 我叫張陵包帚,是天一觀的道長(zhǎng)渔期。 經(jīng)常有香客問(wèn)我,道長(zhǎng)渴邦,這世上最難降的妖魔是什么疯趟? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 55,306評(píng)論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮谋梭,結(jié)果婚禮上信峻,老公的妹妹穿的比我還像新娘。我一直安慰自己瓮床,他們只是感情好盹舞,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,330評(píng)論 5 373
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著隘庄,像睡著了一般踢步。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上丑掺,一...
    開(kāi)封第一講書(shū)人閱讀 49,071評(píng)論 1 285
  • 那天获印,我揣著相機(jī)與錄音,去河邊找鬼吼鱼。 笑死蓬豁,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的菇肃。 我是一名探鬼主播地粪,決...
    沈念sama閱讀 38,382評(píng)論 3 400
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼琐谤!你這毒婦竟也來(lái)了蟆技?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 37,006評(píng)論 0 259
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤斗忌,失蹤者是張志新(化名)和其女友劉穎质礼,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體织阳,經(jīng)...
    沈念sama閱讀 43,512評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡眶蕉,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,965評(píng)論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了唧躲。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片造挽。...
    茶點(diǎn)故事閱讀 38,094評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡碱璃,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出饭入,到底是詐尸還是另有隱情嵌器,我是刑警寧澤,帶...
    沈念sama閱讀 33,732評(píng)論 4 323
  • 正文 年R本政府宣布谐丢,位于F島的核電站爽航,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏乾忱。R本人自食惡果不足惜讥珍,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,283評(píng)論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望饭耳。 院中可真熱鬧串述,春花似錦、人聲如沸寞肖。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,286評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)新蟆。三九已至,卻和暖如春右蕊,著一層夾襖步出監(jiān)牢的瞬間琼稻,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,512評(píng)論 1 262
  • 我被黑心中介騙來(lái)泰國(guó)打工饶囚, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留帕翻,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 45,536評(píng)論 2 354
  • 正文 我出身青樓萝风,卻偏偏與公主長(zhǎng)得像嘀掸,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子规惰,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,828評(píng)論 2 345