深度定制 RecyclerView 的几个例子

最近手头上接连接了几个比较复杂的需求。一个,是 github 上 star 数较多的 RecyclerViewPager,我去年深度定制了它,将 RecyclerView.Adapter 作为 ViewModel,内嵌 RecyclerView 在 ViewModel 之中,增加生命周期管理和 Utils注入,使之能够成为一个通用组件。然而,UED 要求的滑动效果,比起正常的 RecyclerView 的滑动距离,要多出半个 Item 的 Width。

考虑到 RecyclerViewPager 自身为了让 RecyclerView 的能够实现类 ViewPager 的效果,它自身就需要做一个触发滑动 => 自发滑动 => 惯性停止的事情,因此就去看 RecyclerViewPager 的自身实现。最终发现,RecyclerView 是通过 LayoutManager 来控制滑动、显示、隐藏等一系列逻辑的,其中就有 LayoutManager 的内部类 SmoothScroller 的 startSmoothScroll 方法,用来处理平滑滚动。hook 此方法,即可实现多滚动一段距离的功能。代码如下所示:

可以看到逻辑非常简单,只是判断了一下滑动方向,对于 dx 做了加减半个 item width 的功能。此外,就是看了一下 Action 这个内部类的结构,发现,如果对于不会触发滑动的 Action,会标示 onChanged field,遗憾的是 private,外部拿不到。那再 debug,发现不触发的情况下,duration 也会为一个负值。那就用 getDuration() 替代 onChanged 作为判断条件。细想也很自然,如果动作无需触发,也没有必要为动作设置持续时间 duration 了。

然而,以上简单的修改花了我两天的工作量,因为难度的本质在于,找到 hook 的地方,知道这么改会发生什么情况。那么就需要去读 Android 的源代码,看 LayoutManager 中的 SmoothScroller 是干什么用的,有哪些方法,哪个方法是滑动的关键方法。

同理,第二个案例是,如果使用横向 RecyclerView 代替 ViewPager 会有一个问题。RecyclerView 一次滑动是可以滑好几个 Item 的,但 ViewPager 始终只能一次滑一个。那么如何让 RecyclerView 实现 ViewPager 的滑动效果呢?

分析得知,滑动分成两种,滑动和甩动,前者针对于手指在屏幕上实际的触摸滑动距离,后者则是触摸滑动的速度,由速度计算出惯性滑动需要的距离。速度的处理在 fling 函数之中,因此,实现效果的代码如下:

这样就阻断了 super.fling() 中对甩动速度的处理。

推荐一个搜索 Android 源码的方法,google 关键词 + site:android.googlesource.com,这里带了大量的源码,相比使用 IDE 更为快捷。

Android 开发中的猥琐与底层

做 sku 的过程中,使用了很多猥琐的技巧。起因是因为,我需要在这个需求中解决几个问题:

  1. 如何使用 RecyclerView 做出 ViewPager 的效果。
  2. 如何在 RecyclerView 中 hook 到几个可 Override 的生命周期。
  3. 如何解决通用容器和专用业务容器的界限划定。

所谓的猥琐,就是阅读一遍相关的 Android 系统源码,然后去 hook Android api 的方法,然后去各种调教他的入参出参,做出自己的私活。

比如每次滑动距离要增加一个固定距离,比如关掉惯性滑动,比如尝试失败的 Recycler 回收 View做出单/多切换功能,比如之前一直没有解决的头图容器二次渲染白色的问题,等等。

说猥琐,其实也还好。Android 中很多现在大家约定俗称的东西,Google 文档里并没有这么写,只是一开始摸索 Android 的人,都这么写了,在网上的文章里也大多这么分享了,大家也就很自然地去做了。比如为什么 remove View 再重新 add View 就不行? 而要重新 new 一个 View ?的确,Android 上这样会有闪烁的问题。但是在有些不需要 care 闪烁的场景,就这么用,有他的好处。节约内存,省掉 GC,无需状态同步,等等。猥琐的方法用多了,也就变成了堂堂正正的路。

真正的问题大概是,对于框架层的东西,没有必要保持敬畏之情,上去干就好了。阅读一遍 google 的源码,就能搞清楚他的一个惯性滑动是怎么处理的。即便是一个 private 的 field,也可以丧心病狂地使用反射把他的值给改了。我们所需要 care 的点始终只是:

  1. 产品上做这个点有没有价值
  2. 如果有,工程上多猥琐的技巧都能把他给做了。
  3. 万一有风险怎么办?try catch,Tlog,orange,abtest,一堆兜底方案,哪怕98%的场景 ok,2%的场景挂了,只要能兜住,新业务能跑起来,2%的场景兜住不 crash,事后慢慢分析就可以了。没准还没分析完,业务先死了,那就更没有分析的必要了。

开发的层面上,没有解决不了的问题。因为开发始终是用快速学习、工程经验、与产品和视觉的沟通,游走在各种我可能不熟悉的领域,变成比较熟悉,做出东西来验证价值。如果有价值,再交给更专业的人去深入深化。如果解决不了,说明这是算法、当前技术水平、等等一系列的问题,不应当到开发层面。否则,只要无节操,多猥琐都能干了。

另一个有所领悟的点是,认清楚什么是自己的底层。过去认为系统的 API 就是自己的底层,那么一旦找不到一个 API 解决自己的功能,就会手足无措。但是,一旦迈出第一步去阅读 Android 源码,hook 系统公用 API 来做私活了,就会觉得,自己的底层又往下了一步。等到用反射去修改 private field 了,就会觉得,节操更低了,但也更强大了。因此,时刻怀疑自己的底层,是否真的足够底层了,也许再低一点,又会有新的突破。