动态渲染库简介
移动端开发中,对于信息流展示页面,通常对动态性有较高的要求,本地预置模板+接口下发动态组合的方式虽然从某种程度上做到了动态化,但新增样式时依然不可避免需要发版.
本例中通过自定义页面描述语法,并将其下发至客户端,由客户端实时生成布局并渲染,来实现UI级别的动态化.
整体架构
将按照Flex规范定义的页面描述文件,转化成对应的原生视图,同时根据其配置信息,从外层数据中查找对应内容进行展示.


核心流程详解
DSL解析
接口下发的DSL数据示例

其中样式,事件,动画的解析,由一个入口XML解析器来分发:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| @Override public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException { if (mInnernalHandler == null) { switch (localName) { case "Action": mInnernalHandler = new ActionXmlHandler(); break; case "Animation":
break; default: mInnernalHandler = new DynamicViewXmlHandler(); break; } if (mInnernalHandler != null) { mInnernalHandler.startElement(uri, localName, qName, attributes); } } else { mInnernalHandler.startElement(uri, localName, qName, attributes); } }
|

入口解析器解析完每一段xml数据,都会返回DslMappingData数据.前端根据数据实际类型进行保存.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| DslMappingData mappingData = XMLParsingMethods.readMixedXmlBySAX(layoutBean.dslXml); if (mappingData != null) { if (mappingData instanceof XmlActionEntity) { mActionPool.addAction((XmlActionEntity) mappingData); } else if (mappingData instanceof XmlAnimationEntity) { mAnimationPool.addAnimation((XmlAnimationEntity) mappingData); } else if (mappingData instanceof DynamicViewStyle) { DynamicViewStyle viewStyle = (DynamicViewStyle) mappingData; viewStyle.layoutID = layoutBean.layoutID; viewStyle.ver = layoutBean.ver; if (TextUtils.isEmpty(viewStyle.ver)) { viewStyle.ver = String.valueOf(System.currentTimeMillis()); } DynamicViewStyle currentStyle = mLayoutBeanHashMap.get(layoutBean.layoutID); if (currentStyle == null || currentStyle.ver == null) { mLayoutBeanHashMap.put(layoutBean.layoutID, viewStyle); } else { if (!currentStyle.ver.equals(viewStyle.ver)) { mLayoutBeanHashMap.put(layoutBean.layoutID, viewStyle); } } } }
|
视图创建
根据DynamicViewStyle中定义的type,生成对应原生控件,并使用对应装饰器对其进行配置.
DefaultViewGenerator是动态渲染库内部默认实现,提供了对应View的实例化过程:
1. 根据对应type,生成原生控件和其对应装饰器.
生成原生控件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| private DynamicViewDecorator makeSingleView(Activity activity, DynamicViewStyle style) { DynamicViewDecorator viewHolder = null; if (style.type != null) { switch (style.type) { case "View": viewHolder = new YogaLayoutDecorator(new CustomYogaLayout(activity)); break; case "CardView": viewHolder = new CardViewDecorator(new MgCardView(activity)); break; case "ListView": viewHolder = new ListViewDecorator(new DynamicListView(activity)); break; } if (viewHolder == null) { View view = mExtendViewCreater.makeView(activity, style.type); if (view != null) { viewHolder = new ExtendViewDecorator(view); } } } return viewHolder; }
|
根据控件类型,使用不同的装饰器进行属性配置
此处装饰器!=装饰者模式,仅是用于对不同类型的控件,进行特有的动态属性设置.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
|
public interface DslDecorator {
void parseLayout(StyleLayout bean, @Nullable ViewGroup parent);
void parseAttr(Map<String, String> styleAttr, ActionEntity acitonObject, ActionPool actionPool);
void parseAnimation(AnimationEntity animationEntity, AnimationPool animationPool); }
|
2. 解析DynamicViewStyle中的layout配置,并应用(以通用的DynamicViewDecorator为例).
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
| public void parseLayout(StyleLayout bean, @Nullable ViewGroup parent) { YogaNode yogaNode = null; if (parent instanceof YogaLayout) { yogaNode = ((YogaLayout) parent).getYogaNodeForView(itemView); } if (yogaNode != null) { if (bean.alignSelf != null) { switch (bean.alignSelf) { case "flex-end": yogaNode.setAlignSelf(YogaAlign.FLEX_END); break; case "center": yogaNode.setAlignSelf(YogaAlign.CENTER); break; case "space-between": yogaNode.setAlignSelf(YogaAlign.SPACE_BETWEEN); break; case "space-around": yogaNode.setAlignSelf(YogaAlign.SPACE_AROUND); break; default: yogaNode.setAlignSelf(YogaAlign.FLEX_START); break; } } else { yogaNode.setAlignSelf(YogaAlign.AUTO); } } }
|
3. 解析DynamicViewStyle中的attr配置,并应用(以通用的DynamicViewDecorator为例).
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| public DynamicValueBinder<T> onParseAttr(@Nullable Map<String, String> styleAttr, @Nullable ActionEntity acitonObject, ActionPool actionPool) { ValueStore valueStore; DynamicValueBinder<T> binder = createDynamicValueBinder(); if (styleAttr != null) { String alphaStr = styleAttr.get("alpha"); if (alphaStr != null) { float alphaNum = DynamicUtil.stringToFloat(alphaStr); if (alphaNum != 0) { itemView.setAlpha(alphaNum); } } String visibleStr = styleAttr.get("visible"); if (!TextUtils.isEmpty(visibleStr)) { valueStore = new ValueStore(visibleStr); binder.addDataPath("visible", valueStore); } } return binder; }
|
4. 为需要动态设置渲染内容的View配置id,用于外部查找.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
|
if (viewHolder instanceof ExtendViewDecorator) { int id = countHelper.getIdAndInCrease(); itemView.setId(id); if (bindableViewIds != null) { bindableViewIds.add(id); } } else { viewHolder.parseAttr(style.attr, style.action, mActionPool); if (viewHolder.needDynamicBind) { int id = 0; if (itemView instanceof PlayerView) { id = R.id.dsl_tag_playerview; } else if (itemView instanceof BackgroundView) { id = R.id.dsl_tag_backgroundview; } else { id = countHelper.getIdAndInCrease(); } itemView.setId(id); if (bindableViewIds != null) { bindableViewIds.add(id); } } }
|
5. 为列表型View设置item样式.
1 2 3
| if (style.item != null && itemView instanceof DslListInterface) { ((DslListInterface) itemView).setDynamicStyle(style.item, this); }
|
6. 重复1~5,对其child进行实例化,并构建层次结构.
1 2 3 4 5 6
| if (itemView instanceof ViewGroup && style.children != null) { for (DynamicViewStyle childBean : style.children) { makeCurrentView(activity, (ViewGroup) itemView, childBean, bindableViewIds, countHelper, connectionHelper); } }
|
7. 完成View-View间的绑定关系(目前用于Banner-Indicator).
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
|
public void creatConections() { if (mConnectionAbleArrayList.size() > 1) { for (ConnectionAble connectionAble : mConnectionAbleArrayList) { for (ConnectionAble connectionAble2 : mConnectionAbleArrayList) { if (connectionAble != connectionAble2) { connectionAble.onConection(connectionAble2); } } } } mConnectionAbleArrayList.clear(); }
|
对于动态渲染库不支持的type,会在第一步中调用设置的mExtendViewCreater来进行控件生成过程.
1 2 3 4 5 6
| if (viewHolder == null) { View view = mExtendViewCreater.generateView(activity, style); if (view != null) { viewHolder = new ExtendViewDecorator(view); } }
|
数据绑定
理论上所有配置在attr中的属性,都支持通过相对路径从数据实体中取值,并设置.
例如attr中配置text:~title,则意味着该控件将外部数据实体看做JsonObject,取其title字段当做文本内容.
在样式描述体中定义控件的某个属性和其取值对应路径,待外层拿到数据实体后,进行实时解析并设置给控件的过程,即动态绑定.
动态绑定定义在ValueBinder接口中.
1 2 3 4 5 6 7 8 9
| public interface DataBinder<T extends View> {
void rendWithData(T view, JsonObject data, CommBridge commBridge,int listIndex); }
|
其主要有两类实现:
- DynamicValueBinder:动态渲染库内部实现,具备保存属性和相对路径映射的能力.
- 外部业务层实现类:对应原生控件,根据预置的属性和解析策略,自行实现渲染方法.
DynamicValueBinder结构图

逻辑计算
属性对应的值以^开头时,意味着不仅需要动态取值,还要进行逻辑运算.
例如visible:^feedBack and fdTags.count!=0代表当外层数据feedBack值不为空,且fdTags数据对应的列表长度不为0时,visible值为1.
动态渲染库中集成了简单的逻辑处理流程,目前支持and,or,==,!=,三目运算.

采用类似二叉树概念,将逻辑语句拆解,转化成多个可计算的最小单元,从末端开始计算,最终得到整体计算结果.

事件传递
当前事件主要有3种
- 曝光事件.RenderAction.在渲染时产生.对应rendWithData调用时.
- 点击事件.ClickAction.点击时产生.
- 列表滑动事件.ChildScrollAction.列表控件的item滑动时产生.

事件通过rendWithData(T view, JsonObject data, CommBridge commBridge,int listIndex)中的CommBridge对外传递.
总结
UI级别的动态化由于完全采用平台api实现.在放弃部分灵活性的同时,保证其性能和兼容性,是当前移动端不可或缺的技术之一,其设计思路对于设计真正的跨平台框架也有所帮助.