本系列文章主要介绍天猫团队开源的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.javavoid 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.javavoid 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.javavoid 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.javaint 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.javaBinderViewHolder<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.javavoid postBindView(BaseCell cell) {    //业务逻辑    textView.setText("xxx");}

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

参考文章

  • Tangram官网-基础架构