Tangram是淘宝开发的一款动态化UI框架,目的是为了适应淘宝对动态化运营UI的要求,做到由数据来驱动UI的展示。
Tangram中有两个很重要的概念,组件、卡片。
组件是卡片内的独立区域模块:比如一个TextView文本展示组件。
卡片是页面内的独立区域模块,他可以作为一个独立的模块来做UI展示,完成UI上的一个模块的职责,我们称之为卡片。
可以看到组件是卡片的子集。
100、集成Tangram
101、依赖引入
//tangram相关:tangram使用3.0之前的最新版本,其他直接使用最新版本
implementation 'com.alibaba.android:tangram:2.2.5@aar'
//tangram底层支持:vlayout
implementation 'com.alibaba.android:vlayout:1.2.36@aar'
//tangram虚拟视图(更灵活的视图,后面单独开篇讲)
implementation('com.alibaba.android:virtualview:1.4.6@aar') {
transitive true
}
//tangram支持banner翻页用的
implementation 'com.alibaba.android:ultraviewpager:1.0.7.8@aar'
//tangram内部需要rxjava
implementation 'io.reactivex.rxjava2:rxjava:2.1.12'
implementation 'io.reactivex.rxjava2:rxandroid:2.0.2'
102、application onCreate中注册Tangram中图片加载实现
TangramBuilder.init(context, new IInnerImageSetter() {
@Override
public <IMAGE extends ImageView> void doLoadImageUrl(@NonNull IMAGE view, @Nullable String url) {
Glide.with(context).load(url).into(view);
}
}, ImageView.class);
103、实现Trangram流程绑定组件
//创建builder来配置参数
TangramBuilder.InnerBuilder builder = TangramBuilder.newInnerBuilder(this);
//注册自己的cell
builder.registerCell(ImageTextView.class.getSimpleName(), ImageTextView.class);
builder.registerCell(SingleImageView.class.getSimpleName(), SingleImageView.class);
builder.registerCell("CustomCell", CustomCell.class, CustomCellView.class);
builder.registerCell("InterfaceCell", MyCustomTextView.class);
builder.registerCell("NoBackground", NoBackgroundView.class);
//创建引擎
mEngine = builder.build();
//绑定RecyclerView
mEngine.bindView(mBinding.rcgTangram);
initTangramOffSet();
initTangramPreload();
initTangramCellListener();
mBinding.rcgTangram.addOnScrollListener(new RecyclerView.OnScrollListener() {
@Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
super.onScrolled(recyclerView, dx, dy);
//在 scroll 事件中触发 engine 的 onScroll,内部会触发需要异步加载的卡片去提前加载数据
mEngine.onScrolled();
}
});
组件需要实现ITangramViewLifeCycle接口:
public class ImageTextView extends LinearLayout implements ITangramViewLifeCycle {
private ImageView mImgIcon;
private TextView mTvTitle;
public ImageTextView(Context context) {
super(context);
initView();
}
public ImageTextView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
initView();
}
private void initView() {
setOrientation(VERTICAL);
setGravity(Gravity.CENTER);
inflate(getContext(), R.layout.cell_image_text, this);
mImgIcon = findViewById(R.id.img_icon);
mTvTitle = findViewById(R.id.tv_title);
}
@Override
public void cellInited(BaseCell cell) {
Log.e("tangram", "cellInited");
setOnClickListener(cell);
//组件内部的曝光需要设置
if (cell.serviceManager != null) {
ExposureSupport exposureSupport = cell.serviceManager.getService(ExposureSupport.class);
if (exposureSupport != null) {
exposureSupport.onTrace(this, cell, cell.type);
}
}
}
@Override
public void postBindView(BaseCell cell) {
String url = cell.optStringParam("imgUrl");
Log.e("tangram", "postBindView cell = " + cell + " | url = " + url);
Glide.with(getContext()).load(url).into(mImgIcon);
mTvTitle.setText(cell.optStringParam("title"));
}
@Override
public void postUnBindView(BaseCell cell) {
Log.e("tangram", "postUnBindView");
}
}
104、绑定组件事件
private void initTangramCellListener() {
// 设置点击事件统一support类,统一处理
mEngine.addSimpleClickSupport(new MyClickSupport());
// 设置曝光事件统一处理
mEngine.addExposureSupport(new MyExposureSupport());
CardLoadSupport.setInitialPage(1);
mEngine.addCardLoadSupport(new CardLoadSupport(new AsyncLoader() {
// 加载首页
@Override
public void loadData(final Card card, @NonNull final LoadedCallback callback) {
Log.e("TangramActivity", "loadData");
List<BaseCell> cells = mEngine.parseComponent(getLoadData("all-cell.json"));
callback.finish(cells);
}
}, new AsyncPageLoader() {
// 加载更多
@Override
public void loadData(int page, @NonNull Card card,
@NonNull LoadedCallback callback) {
Log.e("TangramActivity", "loadData more page");
List<BaseCell> cells = mEngine.parseComponent(getLoadData("all_data.json"));
boolean hasMore = false;
callback.finish(cells, hasMore);
}
}));
// mEngine.addCardLoadSupport(mCardLoadSupport);
}
其中MyClickSupoort处理点击事件:
public class MyClickSupport extends SimpleClickSupport {
public MyClickSupport() {
// 相当于是一个优化项,可以让Support完全代理组件cell的点击事件,
// 而不用在每个cell中再实现
setOptimizedMode(true);
}
/**
* eventType返回的看起来是当前组件在卡片模块中的的position
* @param targetView
* @param cell
* @param eventType
*/
@Override
public void defaultClick(View targetView, BaseCell cell, int eventType) {
super.defaultClick(targetView, cell, eventType);
String title = cell.optStringParam("title");
String type = cell.optStringParam("type");
Log.e("MyClickSupport", "pos ="+cell.pos + " | stringType = " + cell.stringType
+ " | defaultClick title = " + title + " | type = " + type
+ " | eventType = " + eventType);
ToastUtils.toast(targetView.getContext(), TextUtils.isEmpty(title) ? type:title);
}
}
MyExposureSupport处理页面和组件的曝光,比如用来做日志上报就很合适。
CardLoadSupport用来做数据异步加载和分页加载。
105、绑定数据,视图渲染
试图渲染注意只能传入JsonArray,所以要保证服务端返回的数据是JsonArray格式而不是JsonObject。
//设置数据,触发渲染
byte[] bytes = AssetsUtil.getAssetsFile(this, "all_data.json");
if (bytes != null) {
String json = new String(bytes);
try {
JSONArray data = new JSONArray(json);
mEngine.setData(data);
} catch (JSONException e) {
e.printStackTrace();
}
}
106、json数据格式
数据格式就是UI,比如这里JsonArrya中的一个jsonObject就代表一个卡片(页面的一个完整UI模块,包含多个组件)。items是数据(数据中的某一项就是一个组件,type代表组件类型(这里第一个卡片container-banner就代表banner卡片样式;container-oneColumn代表一行一列的卡片),比如我们的自定义组件,虚拟组件,组件是代表卡片内的最小展示UI单元),type代表卡片的类型,style代表UI的样式(相当于我们在xml中定义的各种UI样式)
[{ "type": "container-banner", "style": { "margin": "[0,0,10,0]", "pageWidth": 200, "pageHeight": 100, "indicatorMargin": "5", "infinite": "true", "indicatorImg2": "https://img.alicdn.com/tps/TB1XRNFNXXXXXXKXXXXXXXXXXXX-32-4.png", "indicatorImg1": "https://img.alicdn.com/tps/TB16i4qNXXXXXbBXFXXXXXXXXXX-32-4.png", "scrollMarginLeft": "10", "indicatorGap": "2", "indicatorHeight": "1.5", "itemRatio": "2.654", "scrollMarginRight": "10", "indicatorGravity": "center", "hGap": "20" }, "items": [ { "imgUrl": "https://wanandroid.com/blogimgs/942a5c62-ca87-4e7c-a93d-41ff59a15ba4.png", "type": "SingleImageView" }, { "imgUrl": "https://www.wanandroid.com/blogimgs/62c1bd68-b5f3-4a3c-a649-7ca8c7dfabe6.png", "type": "SingleImageView" }, { "imgUrl": "https://www.wanandroid.com/blogimgs/50c115c2-cf6c-4802-aa7b-a4334de444cd.png", "type": "SingleImageView" }, { "imgUrl": "https://www.wanandroid.com/blogimgs/90c6cc12-742e-4c9f-b318-b912f163b8d0.png", "type": "SingleImageView" } ]
},{
"type": "container-oneColumn",
"items": [
{
"imgUrl": "https://tva1.sinaimg.cn/large/007S8ZIlgy1geqp9zhftrj303r03r3yl.jpg",
"title": "标题1",
"type": "ImageTextView"
},
{
"imgUrl": "https://tva1.sinaimg.cn/large/007S8ZIlgy1geqp9zhftrj303r03r3yl.jpg",
"title": "标题2",
"type": "ImageTextView"
},
{
"imgUrl": "https://tva1.sinaimg.cn/large/007S8ZIlgy1geqp9zhftrj303r03r3yl.jpg",
"title": "标题3",
"type": "ImageTextView"
},
{
"imgUrl": "https://tva1.sinaimg.cn/large/007S8ZIlgy1geqp9zhftrj303r03r3yl.jpg",
"title": "标题4",
"type": "ImageTextView"
}
]
},{
"type": "container-onePlusN",
"style": {
"aspectRatio": "1.778",
"margin": "[10,0,0,0]"
},
"items": [
{
"imgUrl": "https://wanandroid.com/blogimgs/942a5c62-ca87-4e7c-a93d-41ff59a15ba4.png",
"type": "SingleImageView"
},
{
"imgUrl": "https://www.wanandroid.com/blogimgs/62c1bd68-b5f3-4a3c-a649-7ca8c7dfabe6.png",
"type": "SingleImageView"
},
{
"imgUrl": "https://www.wanandroid.com/blogimgs/50c115c2-cf6c-4802-aa7b-a4334de444cd.png",
"type": "SingleImageView"
},
{
"imgUrl": "https://www.wanandroid.com/blogimgs/90c6cc12-742e-4c9f-b318-b912f163b8d0.png",
"type": "SingleImageView"
}
]
},{
"type": "container-banner",
"style": {
"margin": "[20,0,10,0]",
"pageWidth": 200,
"pageHeight": 100,
"indicatorMargin": "5",
"infinite": "true",
"indicatorImg2": "https://img.alicdn.com/tps/TB1XRNFNXXXXXXKXXXXXXXXXXXX-32-4.png",
"indicatorImg1": "https://img.alicdn.com/tps/TB16i4qNXXXXXbBXFXXXXXXXXXX-32-4.png",
"scrollMarginLeft": "10",
"indicatorGap": "2",
"indicatorHeight": "1.5",
"itemRatio": "2.654",
"scrollMarginRight": "10",
"indicatorGravity": "center",
"hGap": "20",
"autoScroll": 2000
},
"items": [
{
"imgUrl": "https://wanandroid.com/blogimgs/942a5c62-ca87-4e7c-a93d-41ff59a15ba4.png",
"type": "SingleImageView"
},
{
"imgUrl": "https://www.wanandroid.com/blogimgs/62c1bd68-b5f3-4a3c-a649-7ca8c7dfabe6.png",
"type": "SingleImageView"
},
{
"imgUrl": "https://www.wanandroid.com/blogimgs/50c115c2-cf6c-4802-aa7b-a4334de444cd.png",
"type": "SingleImageView"
},
{
"imgUrl": "https://www.wanandroid.com/blogimgs/90c6cc12-742e-4c9f-b318-b912f163b8d0.png",
"type": "SingleImageView"
}
]
},{
"type": "container-oneColumn",
"style": {
"margin": "[20,0,10,0]"
},
"items": [
{
"imgUrl": "https://tva1.sinaimg.cn/large/007S8ZIlgy1geqp9zhftrj303r03r3yl.jpg",
"title": "标题5",
"type": "ImageTextView"
},
{
"imgUrl": "https://tva1.sinaimg.cn/large/007S8ZIlgy1geqp9zhftrj303r03r3yl.jpg",
"title": "标题6",
"type": "ImageTextView"
},
{
"imgUrl": "https://tva1.sinaimg.cn/large/007S8ZIlgy1geqp9zhftrj303r03r3yl.jpg",
"title": "标题7",
"type": "ImageTextView"
},
{
"imgUrl": "https://tva1.sinaimg.cn/large/007S8ZIlgy1geqp9zhftrj303r03r3yl.jpg",
"title": "标题8",
"type": "ImageTextView"
}
]
},{
"type": "container-onePlusN",
"style": {
"aspectRatio": "1.778",
"margin": "[20,0,0,0]"
},
"items": [
{
"imgUrl": "https://wanandroid.com/blogimgs/942a5c62-ca87-4e7c-a93d-41ff59a15ba4.png",
"type": "SingleImageView"
},
{
"imgUrl": "https://www.wanandroid.com/blogimgs/62c1bd68-b5f3-4a3c-a649-7ca8c7dfabe6.png",
"type": "SingleImageView"
},
{
"imgUrl": "https://www.wanandroid.com/blogimgs/50c115c2-cf6c-4802-aa7b-a4334de444cd.png",
"type": "SingleImageView"
},
{
"imgUrl": "https://www.wanandroid.com/blogimgs/90c6cc12-742e-4c9f-b318-b912f163b8d0.png",
"type": "SingleImageView"
}
]
},{
"type": "container-oneColumn",
"items": [
{
"type": "CustomCell",
"text": "自定义model",
"imageUrl" : "https://gw.alicdn.com/tfs/TB1vqF.PpXXXXaRaXXXXXXXXXXX-110-72.png"
}
]
}
]
200、Tangram解析
Tangram底层基于vlayout,而这里的vlayout可以理解为对android的RecyclerView做了一个优化,对itemView减少了嵌套,相当于平铺了复杂情况下的子View。这样就保证了性能。
210、Tangram的type属性
Tangram中的type属性可以实现卡片和组件的UI布局,比如常见的margin边距、文字大小等。
201、流式布局
201.1、container-fourColumn 一行四列
"type": "container-fourColumn",
"items": [
{
"imgUrl": "https://tva1.sinaimg.cn/large/007S8ZIlgy1geqp9zhftrj303r03r3yl.jpg",
"title": "标题1",
"type": "ImageTextView"
}]
201.2、container-onePlusN 一拖N
"type": "container-onePlusN",
"style": {
"aspectRatio": "1.778",
"margin": "[10,0,0,0]"
},
效果如下:
仅仅是改变了客户端请求server后返回的json数据,就做到了UI效果的改变。
还有包括banner、瀑布流、一拖二、一拖N的卡片样式。
300、注意事项
1、Tangram需要rxjava,所以引入tangram的时候需要增加rxjava的引入。
2、Tangram绑定data的时候需要JSONArray的格式,就是json数组,所以需要将数据封装成json数组的样式,如果是jsonObject就会报错。
比如,如果不加[]将数据变为jsonArray,则无法解析组件的type样式为一行四列container-fourColumn:
[{
"type": "container-fourColumn",
"items": [
{
"imgUrl": "https://tva1.sinaimg.cn/large/007S8ZIlgy1geqp9zhftrj303r03r3yl.jpg",
"title": "标题1",
"type": "ImageTextView"
},
{
"imgUrl": "https://tva1.sinaimg.cn/large/007S8ZIlgy1geqp9zhftrj303r03r3yl.jpg",
"title": "标题2",
"type": "ImageTextView"
},
{
"imgUrl": "https://tva1.sinaimg.cn/large/007S8ZIlgy1geqp9zhftrj303r03r3yl.jpg",
"title": "标题3",
"type": "ImageTextView"
},
{
"imgUrl": "https://tva1.sinaimg.cn/large/007S8ZIlgy1geqp9zhftrj303r03r3yl.jpg",
"title": "标题4",
"type": "ImageTextView"
}
]
}]
400、Tangram高级用法
401、虚拟组件
首先虚拟组件是一个组件,我们从上面看到组件是在代码中通过registerCell的方式注册的。那这里的虚拟组件加了虚拟一词,就代表它是没有实体类对应的,完全是根据虚拟组件中的配置信息,用原生的方法draw出来的。所以后续线上你更改了这个虚拟组件的配置,并按照规范打包输出文件动态下发到客户端,就能保证虚拟组件的UI是可以随时变化的。
Tangram解决了布局中卡片的动态下发,可以根据json配置动态改变。但是组件是被java代码硬编码注册到了页面中的,即registerCell。所以如果要想更新组件的内容,就需要设计一套可以不依赖register注册就可以更新组件UI的方式,就是这里的虚拟组件。
其实很简单,就是Tangram设计了一套基于自定义的xml语言编写UI的算法,比如vImage、VText,用它提供的工具会把这个xml加密生成一个文件.out文件,字符串即bin中的字符串。通过动态下发这个out文件或者bin,客户端拿到了这个加密信息后,通过解密得到组件xml布局的原始信息,再通过各自原生android、iOS的绘制方法,来实现绘制组件UI的目的,这样就解决了硬编码去写组件的UI后,无法动态更新、改变组件UI的问题。
虚拟组件的特色:其实从当初淘宝团队的设计来说,是非常优秀的。完全和Compose的想法如出一辙,Compose也是谷歌通过声明式的方式,通过自己的绘制框架来实现了UI的跨平台,用户不需要关心如何绘制,只需要写好自己的UI声明即可。
编写虚拟组件xml
vvtest.xml
<?xml version="1.0" encoding="utf-8" ?>
<VHLayout
action="${action}"
background="${bgColor}"
flag="flag_exposure|flag_clickable"
layoutHeight="wrap_content"
layoutWidth="match_parent"
orientation="V">
<VImage
layoutGravity="h_center"
layoutHeight="72rp"
layoutMarginTop="10rp"
layoutWidth="72rp"
src="${imageUrl}"/>
<NText
layoutGravity="h_center"
layoutHeight="wrap_content"
layoutMarginBottom="10rp"
layoutMarginTop="10rp"
layoutWidth="wrap_content"
text="${text}"/>
</VHLayout>
编译生成输出文件
打开命令行,执行 sh buildTemplate.sh,模板编译后的文件会输出到 build 路径下,其包含以下几个目录:
out目录:XML 模板编译成二进制数据的文件,其他内容都是以此为基础生成,上传到 cdn,通过模板管理后台下发的也是这里的文件; java目录:XML 模板编译成二进制数据之后的 Java 字节数组形式,可以直接拷贝到 Android 开发工程里使用,作为打底数据; sign目录:out 格式文件的 md5 码,供模板管理平台下发模板给客户端校验使用; txt目录:XML 模板编译成二进制数据之后的十六进制字符串形式,转换成二进制数据就是 java 目录下的字节数组。 在我们的示例中,分别生成了VVTEST.java、VVTest.out、VVTest.md5和VVTest.txt。
构建
首先构建一个 VafContext 对象,代码如下:
VafContext vafContext = new VafContext(mContext.getApplicationContext());
加载
加载模板数据,利用 VirtualView Tools 编译出的二进制文件,在初始化的时候加载,有两种方式,一种是直接加载二进制字节数组:
viewManager.loadBinBufferSync(VVTEST.BIN);
另一种是通过二进制文件路径加载(不推荐):
viewManager.loadBinFileSync("file:///android_asset/VVTest.out");
500、常见问题
501、绑定的组件运行报错NoSuchMethodException
java.lang.RuntimeException: java.lang.NoSuchMethodException: com.my.widget.ImageTextView.<init> [class android.content.Context]
at com.tmall.wireless.tangram.structure.ViewCreator.handleException(ViewCreator.java:74)
at com.tmall.wireless.tangram.structure.ViewCreator.create(ViewCreator.java:65)
at com.tmall.wireless.tangram.dataparser.concrete.BaseCellBinder.createView(BaseCellBinder.java:79)
at com.tmall.wireless.tangram.dataparser.concrete.PojoGroupBasicAdapter.createViewHolder(PojoGroupBasicAdapter.java:124)
at com.tmall.wireless.tangram.core.adapter.GroupBasicAdapter.onCreateViewHolder(GroupBasicAdapter.java:263)
at com.tmall.wireless.tangram.core.adapter.GroupBasicAdapter.onCreateViewHolder(GroupBasicAdapter.java:56)
at androidx.recyclerview.widget.RecyclerView$Adapter.createViewHolder(RecyclerView.java:7295)
at androidx.recyclerview.widget.RecyclerView$Recycler.tryGetViewHolderForPositionByDeadline(RecyclerView.java:6416)
at androidx.recyclerview.widget.RecyclerView$Recycler.getViewForPosition(RecyclerView.java:6300)
at androidx.recyclerview.widget.RecyclerView$Recycler.getViewForPosition(RecyclerView.java:6296)
at com.alibaba.android.vlayout.ExposeLinearLayoutManagerEx$LayoutState.next(ExposeLinearLayoutManagerEx.java:1628)
at com.alibaba.android.vlayout.VirtualLayoutManager$LayoutStateWrapper.next(VirtualLayoutManager.java:1143)
at com.alibaba.android.vlayout.layout.RangeGridLayoutHelper.layoutViews(RangeGridLayoutHelper.java:374)
at com.alibaba.android.vlayout.layout.BaseLayoutHelper.doLayout(BaseLayoutHelper.java:318)
at com.alibaba.android.vlayout.VirtualLayoutManager.layoutChunk(VirtualLayoutManager.java:741)
at com.alibaba.android.vlayout.ExposeLinearLayoutManagerEx.fill(ExposeLinearLayoutManagerEx.java:1161)
at com.alibaba.android.vlayout.ExposeLinearLayoutManagerEx.onLayoutChildren(ExposeLinearLayoutManagerEx.java:365)
at com.alibaba.android.vlayout.VirtualLayoutManager.onLayoutChildren(VirtualLayoutManager.java:536)
at androidx.recyclerview.widget.RecyclerView.dispatchLayoutStep2(RecyclerView.java:4309)
at androidx.recyclerview.widget.RecyclerView.dispatchLayout(RecyclerView.java:4012)
at androidx.recyclerview.widget.RecyclerView.onLayout(RecyclerView.java:4578)
at android.view.View.layout(View.java:23065)
at android.view.ViewGroup.layout(ViewGroup.java:6479)
at androidx.constraintlayout.widget.ConstraintLayout.onLayout(ConstraintLayout.java:1873)
at android.view.View.layout(View.java:23065)
at android.view.ViewGroup.layout(ViewGroup.java:6479)
at android.widget.FrameLayout.layoutChildren(FrameLayout.java:332)
at android.widget.FrameLayout.onLayout(FrameLayout.java:270)
at android.view.View.layout(View.java:23065)
at android.view.ViewGroup.layout(ViewGroup.java:6479)
at androidx.appcompat.widget.ActionBarOverlayLayout.onLayout(ActionBarOverlayLayout.java:530)
at android.view.View.layout(View.java:23065)
at android.view.ViewGroup.layout(ViewGroup.java:6479)
at android.widget.FrameLayout.layoutChildren(FrameLayout.java:332)
at android.widget.FrameLayout.onLayout(FrameLayout.java:270)
at android.view.View.layout(View.java:23065)
at android.view.ViewGroup.layout(ViewGroup.java:6479)
at android.widget.LinearLayout.setChildFrame(LinearLayout.java:1829)
at android.widget.LinearLayout.layoutVertical(LinearLayout.java:1673)
at android.widget.LinearLayout.onLayout(LinearLayout.java:1582)
at android.view.View.layout(View.java:23065)
at android.view.ViewGroup.layout(ViewGroup.java:6479)
at android.widget.FrameLayout.layoutChildren(FrameLayout.java:332)
at android.widget.FrameLayout.onLayout(FrameLayout.java:270)
at com.android.internal.policy.DecorView.onLayout(DecorView.java:825)
at android.view.View.layout(View.java:23065)
at android.view.ViewGroup.layout(ViewGroup.java:6479)
at android.view.ViewRootImpl.performLayout(ViewRootImpl.java:3626)
at android.view.ViewRootImpl.performTraversals(ViewRootImpl.java:3086)
at android.view.ViewRootImpl.doTraversal(ViewRootImpl.java:2074)
at android.view.ViewRootImpl$TraversalRunnable.run(ViewRootImpl.java:8542)
at android.view.Choreographer$CallbackRecord.run(Choreographer.java:1076)
at android.view.Choreographer.doCallbacks(Choreographer.java:897)
at android.view.Choreographer.doFrame(Choreographer.java:826)
at android.view.Choreographer$FrameDisplayEventReceiver.run(Choreographer.java:1061)
at android.os.Handler.handleCallback(Handler.java:938)
at android.os.Handler.dispatchMessage(Handler.java:99)
at android.os.Looper.loop(Looper.java:236)
at android.app.ActivityThread.main(ActivityThread.java:8134)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:656)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:967)
Tangram在RecyclerView绑定Adapter后创建ItemView的时候,会创建你绑定的cell组件
//创建builder来配置参数
TangramBuilder.InnerBuilder builder = TangramBuilder.newInnerBuilder(this);
//注册自己的cell
builder.registerCell(ImageTextView.class.getSimpleName(), ImageTextView.class);
是通过反射来创建cell的实例,并且默认是创建参数为Context的构造方法,所以如果你自定义的View中没有默认参数为Context的构造函数,就会报错找不到方法。
at com.tmall.wireless.tangram.dataparser.concrete.BaseCellBinder.createView
at com.tmall.wireless.tangram.structure.ViewCreator.create
解决方案: 为自定义View添加默认参数为Context的构造函数即可。