Android-Tangram动态页面之路五Tangram原理

13次阅读

共计 4324 个字符,预计需要花费 11 分钟才能阅读完成。

本系列文章主要介绍天猫团队开源的 Tangram 框架的使用心得和原理,由于 Tangram 底层基于 vlayout,所以也会简单讲解,该系列将按以下大纲进行介绍:

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

本文将对 Tangram 进行初步讲解。

基于 Tangram 最新源码分析

笔者 Demo 代码

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");
}

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

参考文章

  • Tangram 官网 - 基础架构

正文完
 0