 详解:同步测量视图位置与尺寸)
react-native-reanimated measure() 详解同步测量视图位置与尺寸【免费下载链接】react-native-reanimatedReact Natives Animated library reimplemented项目地址: https://gitcode.com/GitHub_Trending/re/react-native-reanimated导读measure()是 react-native-reanimated 提供的原生方法之一用于在 UI 线程上同步获取某个视图在屏幕上的位置x、y、pageX、pageY与尺寸width、height。本篇文章基于仓库 版本 2.x 官方文档结合当前仓库的源码实现全面讲解measure()的用法、返回值、注意事项、常见坑位与实战示例。读完本文你将掌握如何在动画 worklet 中安全地同步读取视图测量值并理解其底层实现原理。1. measure() 是什么measure()是一个同步函数用于在当前屏幕视口viewport内确定给定视图的位置、宽度和高度并返回一个包含测量结果的对象如果该视图无法被测量则返回null。从源码结构看measure在仓库中位于 packages/react-native-reanimated/src/platformFunctions/ 目录下分为两个平台实现measure.native.ts原生Android / iOS / macOS实现运行在 UI 运行时worklet中measure.tsWeb 实现基于 DOM 的getBoundingClientRect等方法。两者通过 src/index.ts 统一对外导出因此无论是原生平台还是 Web 平台开发者都使用同一套 API 签名。小提示如果你只是需要尽可能早地拿到测量值且不需要pageX与pageY官方文档建议优先使用 React Native 的onLayout属性因为它更轻量、触发时机更早。2. 函数签名与参数type Measure TRef extends InstanceOrElement( animatedRef: AnimatedRefTRef ) MeasuredDimensions | null;签名来自 measure.native.ts。2.1 animatedRef 参数measure()的唯一参数是animatedRef它是useAnimatedRef的返回值。useAnimatedRef是 Reanimated 对标准 React ref 的扩展关键区别在于它能够在 UI 线程上交付视图标签view tag这正是measure()能在 worklet 内部直接同步读取测量值的前提。从 useAnimatedRef.ts 的实现可以看到它内部还会处理可滚动组件的特殊情况——如果组件带有getScrollableNode或getNativeScrollRef方法如ScrollView、FlatList会优先返回其可滚动节点确保测量的是实际滚动内容对应的原生视图。2.2 返回值 MeasuredDimensions返回对象类型为MeasuredDimensions在 commonTypes.ts 中定义包含六个字段字段含义说明xX 坐标相对于父组件的横坐标yY 坐标相对于父组件的纵坐标width宽度组件的宽度height高度组件的高度pageX屏幕级 X 坐标相对于屏幕的横坐标pageY屏幕级 Y 坐标相对于屏幕的纵坐标如果测量未能执行成功例如视图尚未渲染则返回null。六字段的语义注释同样可以在 commonTypes.ts 的 JSDoc 中看到x/y是相对父组件pageX/pageY是相对屏幕。二者是定位动画、弹窗、Tooltip 等场景最常用的两组数据。3. 基本示例官方文档给出了一个最典型的用法在useDerivedValue的 worklet 中持续测量视图const Comp () { const aref useAnimatedRef(); useDerivedValue(() { const measured measure(aref); if (measured ! null) { const { x, y, width, height, pageX, pageY } measured; console.log({ x, y, width, height, pageX, pageY }); } else { console.warn(measure: could not measure view); } }); return View ref{aref} /; };要点拆解先useAnimatedRef()创建 animated ref并绑定到目标View ref{aref} /在 worklet 内调用measure(aref)获得同步测量结果务必对返回值做null检查因为测量失败时返回的是null直接解构会抛错。4. 返回值可能为 null 的场景重要:::infomeasure()只能对已渲染的组件生效。例如对屏幕外的FlatList列表项调用measure()会返回null。因此在使用返回值之前执行null检查是一个良好的实践。 :::从源码实现看原生实现 measureNative 中返回null的路径不止一种React Native 运行时调用当globalThis.__RUNTIME_KIND RuntimeKind.ReactNative时即不是在 UI 运行时调用直接返回null视图标签无效animatedRef.value为空时返回null源码会打印警告The view with tag ... is not a valid argument for measure()...并提示这可能是视图未渲染导致的例如屏幕外的 FlatList 项LayoutMetrics 未就绪底层global._measure(viewTag)返回null时同样返回null并给出警告提示视图可能尚未渲染Android 视图被 flatten当测量结果的x为NaN时返回null并警告 The view gets view-flattened on Android此时需要给组件设置collapsable{false}来禁用视图扁平化。另外值得注意在 Jest 测试环境中IS_JEST为真measure会被替换为 measureJest直接返回null并提示 measure() cannot be used with Jest.因此单元测试中无法使用该方法。5. 在 useAnimatedStyle 中调用的告警与规避:::tip 如果在useAnimatedStyle内部调用measure你可能会看到如下警告[Reanimated] measure() was called from the main JS context. Measure is only available in the UI runtime. (...)这背后的原因是在 React Native 应用中useAnimatedStyle的 worklet 在首次渲染期间会先在 JS 上下文中求值一次而此刻原生端尚未完成渲染因此measure()在 JS 上下文中不可用。这个警告本身是安全的可以忽略但如果不想看到它可以用如下方式包裹调用if (_WORKLET || isWeb) { const measured measure(animatedRef); if (measured ! null) { // ... } }_WORKLET是 Reanimated 注入的全局标志在 UI 运行时worklet中为trueisWeb用于 Web 平台其实现不依赖 UI 运行时。用_WORKLET || isWeb判断后JS 首次求值阶段就不会触发measure()告警自然消失。 :::这一行为与源码中的运行时检查完全吻合原生实现 measureNative 首先判断globalThis.__RUNTIME_KIND当不在 UI 运行时React Native 主线程上下文时直接返回null。因此即使在useAnimatedStyle中不包裹判断行为也是安全的返回null只是会伴随告警日志。6. 与调试工具的兼容性:::info 当 Chrome Developer Tools远程 JS 调试器连接时measure不可用。不过React Native 官方推荐的调试工具FlipperChrome DevTools支持measure更多细节可参见调试指南。 :::原因在于远程 JS 调试会把 worklet 的求值移出 UI 运行时导致依赖原生 view tag 的同步测量无法进行。建议在真机调试或使用支持 UI 运行时调试的工具如 Flipper时使用measure()。7. 底层实现原理原生端如何测量7.1 原生Android / iOS路径在 measure.native.ts 中measureNative是一个标记了worklet的函数执行流程如下检查运行环境若globalThis.__RUNTIME_KIND ! RuntimeKind.ReactNative即处于 UI 运行时继续执行从animatedRef.value取出视图标签view tag / shadow node wrapper调用 UI 运行时提供的全局函数global._measure!(viewTag)依次处理三种失败情形视图标签为空、LayoutMetrics未就绪、Android 上x为NaN均返回null并打印日志成功时返回MeasuredDimensions对象。global._measure的类型声明位于 privateGlobals.d.ts其签名为(shadowNodeWrapper: ShadowNodeWrapper | null) MeasuredDimensions是由原生模块注入 UI 运行时的内部能力。这意味着在 RN 的新架构Fabric下测量直接基于 ShadowNode 完成能够拿到屏幕坐标系中的真实布局数据。7.2 Web 路径在 measure.ts 中Web 实现不依赖 UI 运行时而是直接操作 DOM通过animatedRef()获取对应的HTMLElementWeb 端的 animated ref 是一个函数式 ref元素不存在时返回null并打印与原生端一致的告警使用element.getBoundingClientRect()取得视口偏移pageX、pageY用offsetWidth、offsetHeight、offsetLeft、offsetTop填充其余四个字段。注意一个平台差异原生端的x/y是相对父组件Web 端实现使用的是offsetLeft/offsetTop同样是相对定位父级offsetParent的偏移语义上保持一致。8. 实战用 measure() 实现跟随式 Tooltip下面是一个把measure()用于实际业务弹窗/气泡定位的完整示例——在点击时同步测量目标视图的位置然后把 Tooltip 绝对定位到其下方import { View, Text, TouchableOpacity } from react-native; import Animated, { useAnimatedRef, useSharedValue, withTiming, } from react-native-reanimated; const TooltipDemo () { const targetRef useAnimatedRef(); const tipX useSharedValue(0); const tipY useSharedValue(0); const opacity useSharedValue(0); const showTooltip () { // 注意此处在 JS 线程也能调用 measure但更推荐在 // worklet 中调用以获得同步、无跨线程延迟的测量结果 worklet; const m measure(targetRef); if (m null) { return; } tipX.value m.pageX; tipY.value m.pageY m.height 4; // 出现在目标下方 opacity.value withTiming(1, { duration: 150 }); }; return ( View style{{ flex: 1, paddingTop: 120 }} TouchableOpacity ref{targetRef} onPress{showTooltip} Text点击我显示提示/Text /TouchableOpacity Animated.View style{{ position: absolute, left: tipX, top: tipY, opacity, }} Text这是跟随式 Tooltip/Text /Animated.View /View ); };关键点使用measure(targetRef)拿到目标视图相对屏幕的坐标pageX/pageY与高度height将结果写入 shared value再由Animated.View的left/top驱动实现与原生布局完全同步的定位依然保留了null检查避免在目标未渲染时崩溃。9. 最佳实践小结始终做 null 检查measure()返回null的场景很多未渲染、屏幕外 FlatList 项、Android 视图 flatten 等这是官方文档强调的第一要点优先在 worklet 中调用measure的设计目标是 UI 运行时内的同步测量在useAnimatedStyle等首次 JS 求值阶段调用会触发告警可用_WORKLET || isWeb判断规避Android 上遇到 NaN 检查collapsable如果测量结果异常可尝试为目标组件设置collapsable{false}禁用视图扁平化能不用就不用若只需要尺寸数据、不需要屏幕坐标onLayout是更轻量的替代方案调试注意远程 JS 调试Chrome DevTools下不可用推荐使用 Flipper 调试测试注意Jest 环境下measure()直接返回null编写单元测试时应做相应 mock 或规避。相关资源measure 官方文档版本 2.xscrollTo 官方文档版本 2.x原生实现源码Web 实现源码MeasuredDimensions 类型定义useAnimatedRef 源码measure 导出位置【免费下载链接】react-native-reanimatedReact Natives Animated library reimplemented项目地址: https://gitcode.com/GitHub_Trending/re/react-native-reanimated创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考