likes
comments
collection
share

Android升级TargetSdk33后 ReactNative文本被遮挡问题

作者站长头像
站长
· 阅读数 14

现象

当 Android targetSDK 升级到 33 后,一些 React Native 的 Text (重点是自定义字体 FontFamily 后)会出现高度计算不正确和被遮挡的问题。

Android升级TargetSdk33后 ReactNative文本被遮挡问题

分析

  • RN 的 ReactTextView 使用 Spannable 定制文本显示。ReactTextView 有自己的文本高度测量逻辑
  • 测量是由 ReactRootView 发起的,在 Native Module 线程中执行。Yoga 负责测量,然后发送 UpdateLayoutOperation 在 UI 线程中执行 mNativeViewHierarchyManager.updateLayout 方法。最终调用 ReactTextView 的 onMeasure() 方法。但此时已经计算好了宽高,与系统的 TextView 测量不同(系统是 WrapContent)。
  • ReactTextView 虽然使用 Yoga 测量,但最终实现是在原生端的 ReactTextShadowNode 的 YogaMeasureFunction。关键测量代码为 BoringLayout.isBoring(text,textPaint)。BoringLayout.isBoring 支持简单文本的测量,而被遮挡的文本也是简单文本。所以问题出现在 BoringLayout 中。
  • 简单来说,因为 RN 使用了 Yoga 框架进行布局测量,所以脱离了原生的测量流程。导致在某些特定场景下,RN 计算的文本高度会小于实际显示的高度,不过在绝大多数情况下,RN 的计算策略是正常的。
  • 接下来分析下Android 13有没有类似的调整。我们发现Android官方修复了 Implement fallback line spacing for BoringLayout 的问题(相关链接:cs.android.com/android/_/a…),此修复只在 Android targetSdk 33 及以上版本生效
  • 官方代码可以发现,TextView 的文本高度计算逻辑也随之调整。我们可以重点关注 TextView 的源码,如下图所示:官方针对 BoringLayout.isBoring 的方法追加了 isFallbackLineSpacingForBoringLayout() 的控制,该参数由 CompatChanges.isChangeEnabled(BORINGLAYOUT_FALLBACK_LINESPACING) 开关控制,参数的含义:useFallbackLineSpacing – True for adjusting the line spacing based on fallback fonts. False for keeping the first font's line height. If some glyphs requires larger vertical spaces, by passing true to this argument, the layout increase the line height to fit all glyphs.

Android升级TargetSdk33后 ReactNative文本被遮挡问题

解决方案

目前采用方案二

方案一:跟随 Android 官方 TextView 的方式调整

优势:与官方 TextView 方案一致。(方案修复后与线上存在部分差异,暂不采用。)

缺陷:官方测量方案比较复杂,需要针对差异部分做调整。官方使用 CompatChanges.isChangeEnabled 控制新的测量方案,而 RN 和 Android 官方的测量也存在差异。实现:

  1. 调整 RN 的 BoringLayout.isBoring 实现,使其与官方一致。(但是官方的 CompatChanges 开关类是隐藏 API,反射获取也被禁用。)(targetSdk >= 33 && Build.VERSION >= 33)

注意:useFallbackLineSpacing 参数会因不同 ROM 而有不同结果,所以需要反射获取 TextView 的 isFallbackLineSpacingForBoringLayout() 控制。该方法不对外公开,CompatChanges 也是系统隐藏 API。但是反射也无法拿到系统的值,andorid系统在运行时阻止应用层代码反射获取该值

方案二:在使用 BoringLayout 方式测量的情况下,禁用 useFallbackLineSpacing 特性

优势:旧版本的 BoringLayout 不支持内边距,新版本支持内边距。目前在新版本上禁用内边距属性可以保证与历史逻辑一致。 难点:RN 的 Yoga 测量与 View 绘制是分离的。如何在性能和内存可控的前提下,实现测量方式的传递?另外,某些 RN 的 JS 代码指定 Text 高宽后不再调用 RN Java 的测量,此时如何准确获取测量方式?

  1. ReactTextView 在测量时记录当前测量是否为 BoringLayout,并向后传递。
  2. ReactTextView 设置文本时,判断当前测量方式是否为 BoringLayout。如果是,则禁用 fallbackLineSpace(targetSdk >= 33 && Build.VERSION >= 33)。 
  3. ReactTextView 有些 RN 的 JS 代码指定 Text 高宽后不再调用 RN Java 的测量。此时可以在 onLayout 时,对于没有明确测量方式的文本,通过 getLayout 获取测量方式是否为 BoringLayout。如果是,则禁用 fallbackLineSpace(targetSdk >= 33 && Build.VERSION >= 33)。

由于涉及到要修改RN(ReactNative)的源码,代码位置也较多,暂不贴出来了,可以参照思路自行修复,具体问题可以评论

转载自:https://juejin.cn/post/7248489733226528824
评论
请登录