Android | Tangram動態頁面之路(五)Tangram原理

本系列文章主要介紹天貓團隊開源的Tangram框架的使用心得和原理,因爲Tangram底層基於vlayout,因此也會簡單講解,該系列將按如下大綱進行介紹:java

  1. 需求背景
  2. Tangram和vlayout介紹
  3. Tangram的使用
  4. vlayout原理
  5. Tangram原理
  6. Tangram二次封裝

本文將對Tangram進行初步講解。git

基於Tangram最新源碼分析github

筆者Demo代碼json

Tangram

Tangram和vlayout介紹這篇文章提到過,Tangram經過解析json模板獲得佈局方式Card和具體視圖Cell,而後將Card轉換成對應的vlayoutLayoutHelper來進行測量和佈局,以下,數組

官網的架構圖以下,緩存

Card轉成LayoutHelper

跟進TangramActivityengine.setData(data)架構

//BaseTangramEngine.java
void setData(T data) {
    //模板解析,json文件 -> JSONArray -> List<Card>
    List<C> cards = mDataParser.parseGroup(data, this);
    this.setData(cards);
}

void setData(List<C> data) {
    this.mGroupBasicAdapter.setData(data);
}

來到GroupBasicAdapter框架

//GroupBasicAdapter.java
void setData(List<L> cards, boolean silence) {
    //把cards轉成vlayout的layoutHelpers
    setLayoutHelpers(transformCards(cards, mData, mCards));
    if (!silence)
        notifyDataSetChanged();
}

//cards指json模板中的多個佈局方式card,
//data指每一個card裏邊的具體視圖cell
//rangeCards指一段管轄範圍內所對應的佈局方式card
//假設第1個card對應ColumnLayoutHelper,有3個元素,則管轄範圍是[0,2]
//第2個card對應OnePlusNLayoutHelper,有4個元素,則管轄範圍是[3,6],以此類推
List<LayoutHelper> transformCards(List<L> cards, List<C> data,
                                  List<Pair<Range<Integer>, L>> rangeCards) {
    //data.size()初始值爲0
    int lastPos = data.size();
    List<LayoutHelper> helpers = new ArrayList<>(cards.size());
    for (int i = 0, size = cards.size(); i < size; i++) {
        //遍歷每一個card
        L card = cards.get(i);
        //獲取card的類型,如列布局container-fourColumn
        final String ctype = getCardStringType(card);
        //獲取card內的cell數組
        List<C> items = getItems(card);
        //若是card裏邊沒有cell,即沒有視圖,直接跳過
        if (items == null) {
            continue;
        }
        //記錄每一個card裏邊的多個cell
        data.addAll(items);
        int offset = lastPos;
        lastPos += items.size();
        //記錄每一段管轄範圍,和其對應的card
        rangeCards.add(Pair.create(Range.create(offset, lastPos), card));
        //獲取card對應的LayoutHelper,暫不深究
        LayoutBinder<L> binder = mCardBinderResolver.create(ctype);
        LayoutHelper helper = binder.getHelper(ctype, card);
        if (helper != null) {
            //設置cell個數
            helper.setItemCount(items.size());
            helpers.add(helper);
        }
    }
    return helpers;
}

Card被轉換成LayoutHelper源碼分析

轉換完成後,調用了notifyDataSetChanged,是如何顯示到RecyclerView上的呢?佈局

RecyclerView展現

跟進TangramActivityengine.bindView(recyclerView)

//BaseTangramEngine.java
void bindView(@NonNull final RecyclerView view) {
    this.mContentView = view;
    //設置VirtualLayoutManager
    this.mContentView.setLayoutManager(mLayoutManager);
    //設置性能監控,mLayoutManager負責監控cell的耗時
    mLayoutManager.setPerformanceMonitor(mPerformanceMonitor);
    if (mGroupBasicAdapter == null) {
        this.mGroupBasicAdapter = mAdapterBuilder.newAdapter(mContext, mLayoutManager, this);
        //設置性能監控,mGroupBasicAdapter負責監控card和cell的耗時
        mGroupBasicAdapter.setPerformanceMonitor(mPerformanceMonitor);
        //錯誤報告
        mGroupBasicAdapter.setErrorSupport(getService(InternalErrorSupport.class));
    }
    if (mContentView.getRecycledViewPool() != null) {
        //設置RecyclerView緩存池,InnerRecycledViewPool裝飾了RecycledViewPool
        mContentView.setRecycledViewPool(new InnerRecycledViewPool(mContentView.getRecycledViewPool()));
    }
    //註冊服務,暫不深究
    register(GroupBasicAdapter.class, mGroupBasicAdapter);
    register(RecyclerView.RecycledViewPool.class, mContentView.getRecycledViewPool());
    //設置適配器
    this.mContentView.setAdapter(mGroupBasicAdapter);
}

可見RecyclerView設置的適配器是GroupBasicAdapter,看下咱們比較關心的幾個方法,

//GroupBasicAdapter.java

int getItemViewType(int position) {
    C data = mData.get(position);
    //內部緩存了Map<String, Integer> mStrKeys
    //String就是cell名字如SingleImageView,Integer就是一系列從0開始遞增的ViewType
    return getItemType(data);
}

官方Demo早期用了int來聲明Cell,這樣容易混亂,不利於在json模板裏表意,如今改爲了String來聲明(爲此還作了些兼容代碼),建議直接使用String來註冊,可參考Tangram的使用

{
    "id": "banner1",
    "type": "container-oneColumn",
    "style": {
        "aspectRatio": 3.223
    },
    "items": [
        {
            "bizId":"item1",
            "type": 110,  //不要再使用int聲明cell,建議使用惟一字符串如SingleImageView
            "msg": "info1"
        },
        {
            "bizId":"item2",
            "type": 110,  //不要再使用int聲明cell,建議使用惟一字符串如SingleImageView
            "msg": "info2"
        }
    ]
}

而後看下onCreateViewHolderonBindViewHolder

//GroupBasicAdapter.java

BinderViewHolder<C, ? extends View> onCreateViewHolder(ViewGroup parent, int viewType) {
    //根據viewType獲得cell名字
    String cellType = getCellTypeFromItemType(viewType);
    //大概是經過cellType幫咱們建立對應的view,暫不深究
    ControlBinder<C, ? extends View> binder = mCompBinderResolver.create(cellType);
    //一個普通的ViewHolder,提供了bind方法
    BinderViewHolder binderViewHolder = createViewHolder(binder, mContext, parent);
    return binderViewHolder;
}

void onBindViewHolder(BinderViewHolder<C, ? extends View> holder, int position) {
    //獲取cell
    C data = mData.get(position);
    //綁定cell
    holder.bind(data);
}

//省略調用鏈:
//BinderViewHolder.bind -> BaseCellBinder.mountView -> MVHelper.mountView
// -> MVHelper.postMountView -> ITangramViewLifeCycle.postBindView

//回調到業務層,如TestView.java
void postBindView(BaseCell cell) {
    //業務邏輯
    textView.setText("xxx");
}

至此,整個流程就跑通了。

參考文章

相關文章
相關標籤/搜索