Android Weex 自定义 Component 指北

Android Weex 自定义 Component 指北

Weex 自定义 Component 开发这块,官方文档和网上示例都较少涉及。工作所需有所研究,总结此文以飨读者。

基础定义与注册

如下述代码所示,从 WXComponent 继承出来以后,复写四个构造器方法,就可以完成一个最简单可跑当然也显示不了任何东西的 WXComponet。

需要说明的是 WXComponent 可以指定泛型 T,T extends View,用于指定WXComponent hostView 根布局的类型。这个还是指定的比较好,在某些进阶用法中会需要这个类型。

使用这个 Component 之前,还需要把他注册进 WXSDKEngine。如下所示:
一次注册后,在Android程序销毁之前,可以一直使用这个 Component。无需 unregister,WXSDKEngine 也没有提供 unregister 方法,这是显而易见的,因为当前还未产生任何实例。

如果想要设定 props 怎么办,如

那就在 Componet 中增加如下:

weex 会根据外部传入的 props,根据注解调用对应 props 的 set 方法。

生命周期

显然,看了第一节,只能保证链路上 weex 自定义 Component 能跑起来,没有做其他任何事情。那么,为了能实现我们需要的渲染和其他逻辑,就需要了解 Weex Componet 的生命周期。这里的生命周期,实质就是了解可 Override 的几个 WXComponent 方法,和他们的被调用的时机。这一块官方没有任何文档,全靠去 github 源码中看和试。

必需 Override

initComponentHostView()

用于生成根 View 返回给 Weex 来渲染。注意,不要在这里进行任何响应外界设入的 props 的渲染,因为此时极大可能 props 还没有被传入。

可选 Override

bindData()

需要注意的是,直接把 void 改成返回值比如 boolean 然后试图 return 是没有用的,weex js 侧收不到。因此,必须要去使用回调来给返回值。如上所示。

DOM

Weex 新内核(WeexCore)将 Dom 层和 Layout 引擎下沉到 C++ 层实现,移除 Java 层的 DomObject,提升渲染性能和内核的可通用性。因此,github 最新版不再可以获取到 WXComponent 中的 DomObject。

Tricks:强转

如果发现自定义 Component 的逻辑需要用到 Activity,而 WXComponent 只给你提供了 Context 的时候,不要慌,Weex 传入的 Context 其实可以强转 Activity。当然,以防万一,记得用 instance of 保护一下。

同理,如果你想要弹出一个 Fragment,结果发现自己需要一个 FragmentActivity 来getSupportFragmentManager(),不要慌,weex 传入的这个 Activity 也可以强转为
FragmentActivity,同样记得加 instance of 保护,否则业务挂了不算我的,因为这毕竟是文档中的未定义行为。

非全屏 Weex 页面开发中的 Android 适配

weex代码中的高度和宽度的单位均为px,然而,在手机屏幕上显示的高宽却不一定与代码指定的相同。原因是weex框架在底层做了针对不同屏幕的适配工作,具体计算公式为 实际高宽 = 代码高宽 * (屏幕宽度 / 750)。

举个例子,假设代码中是这么写的:

那么,在一款屏幕分辨率为1920*1280的Android手机上,此时的计算过程为:
height: 100 * (1080 / 750) = 144;
width: 200 * (1080 / 750) = 288。

如果我们开发的weex页面是全屏幕的,那么这个高宽的转换过程对我们而言是透明的,无需做额外的工作。然而一旦有一个业务场景,weex容器并非是全屏幕的,而是需要从外部传入weex容器的高度,那么,就不得不考虑这个转换的过程。

举一个我在开发weex弹窗时的例子。该weex弹窗的样式如下:

weex-blog

可以看到,如果不考虑多屏幕适配,顶栏和底栏都是一个固定值,那么只需要用总容器高度 – 两个定高组件就可以了。那么需要解决的第一个问题,就是如何获取外部容器的高度。由于weex可以通过$getConfig().env.deviceHeight$getConfig().env.deviceWidth的形式来获取手机屏幕的高度,因而,很自然地就想到,是否能在安卓中以屏幕的3/5的比例,约定容器高度,然后在weex代码中,同样通过3/5来计算容器高度。这样就避免了去写 Native Module 和 Method。

然而,这样的思路是不可行的。因为Android Native的总高度,事实上是可供显示的全屏高度,而不一定是物理屏幕的高度,因为有状态栏,虚拟按键栏,Smartbar等等安卓碎片化引入的额外显示元素,实际全屏高度很有可能小于物理屏幕高度。所以,仍然需要开发和注册Native Module,以获取外部容器高度。

再来看上文的计算公式:总容器高度 – 两个定高 = scroller高度。因为多屏幕适配的原因,上面的公式是不可行的,需要改为:

外部传入的总容器高度 – 两个定高组件的高度字面量 * 转换比例 = scroller实际高度

也就是说:外部传入的总容器高度 / 转换比例 – 两个定高组件的高度字面量 = scroller实际高度 / 转换比例 = scroller的字面量高度。

所以,最终的业务代码如下所示:

这个坑非常的隐蔽,本质是因为:weex 默默做了A参考系转换到B参考系的过程,然而一旦我们自力更生,试图从B参考系获得一个测量得到的高度,用在A参考系,而没意识到这个隐蔽的转换过程的时候,就会陷入到一台机子上调好了,另一台又跪了的尴尬局面。而且,这种情况在Android上远较iOS要来的严重。因为iOS上,除了4S以外,5,5s,6,6p,6s,6sp,屏幕尺寸均为同一长宽比。因此,在一台上调整好后,可无缝等比例放大到其他机型上。然而在Android上,毋论碎片化的屏幕尺寸,光status bar,navigation bar,smartbar等等虚拟的占用实际显示区域的各类bar,就足够让weex的默认适配喝一壶的。因此,weex这种隐蔽适配的处理方式,在Android生态上是否真的合理方便,尚待商榷。