捏合缩放与旋转
结合 PinchGestureHandler 和 RotationGestureHandler,并启用同时手势识别,实现可缩放、可旋转的图像组件。
捏合缩放与旋转 是 CoddyKit 上的免费 React Native Academy 课时。 这是第 4 节课,共 4 节。 你可以在下方免费阅读本课时的完整内容 — 然后在浏览器中使用内置代码编辑器和全天候 AI 导师进行实践。 这是 React Native Academy 学习路径的一部分,你的进度在网页和 CoddyKit 应用中同步。 React Native Academy 课程共包含 4 节课。
捏合与旋转为何重要
捏合缩放和旋转是移动设备上的典型交互,常见于照片查看器、地图和文档应用。要实现良好的效果,需要在原生线程上进行多点触控手势识别,并通过 Reanimated 共享值平滑地实时更新变换。
React Native Gesture Handler 提供了 Gesture.Pinch() 和 Gesture.Rotation() 识别器。将它们与 Reanimated 的 useAnimatedStyle 和 useSharedValue 结合,可以实现极其流畅的交互,使用感受几乎与原生实现无异。
Gesture.Pinch:检测缩放比例
Gesture.Pinch() 可识别双指捏合或张开手势。事件对象中最重要的值是 event.scale:手势开始时为 1.0,随后会根据手指间距离按比例变化。值为 2.0 表示手指间距离是开始时的两倍。
您必须将手势产生的增量缩放值与保存的基础缩放值结合起来,才能在多次捏合手势之间累计缩放;否则,每次新的捏合都会重置为 1.0。
import { Gesture } from 'react-native-gesture-handler';
import { useSharedValue, withSpring } from 'react-native-reanimated';
const scale = useSharedValue(1);
const savedScale = useSharedValue(1);
const pinch = Gesture.Pinch()
.onUpdate((e) => {
scale.value = savedScale.value * e.scale;
})
.onEnd(() => {
savedScale.value = scale.value;
});限制缩放范围
如果不进行限制,用户可能会放大到极大的比例,或缩小到图片完全消失。请定义最小和最大缩放级别,并在 onUpdate 回调中使用实用工作单元限制缩放值。
限制函数会作为工作单元在 UI 线程上运行,从而保持手势响应迅速。请根据内容选择合适的限制值——照片查看器通常允许 1 到 5 倍缩放,而地图可能允许更宽的范围。
const MIN_SCALE = 1;
const MAX_SCALE = 5;
function clamp(value, min, max) {
'worklet';
return Math.min(Math.max(value, min), max);
}
const pinch = Gesture.Pinch()
.onUpdate((e) => {
scale.value = clamp(savedScale.value * e.scale, MIN_SCALE, MAX_SCALE);
})
.onEnd(() => {
savedScale.value = scale.value;
// Snap back if over-zoomed out:
if (scale.value < MIN_SCALE) {
scale.value = withSpring(MIN_SCALE);
savedScale.value = MIN_SCALE;
}
});Gesture.Rotation:跟踪角度
Gesture.Rotation() 可跟踪双指扭转手势的角度变化。event.rotation 的值以弧度表示,即手指从手势开始位置旋转的角度。完整的顺时针旋转约为 6.28 弧度(2π)。
与缩放一样,您必须在多次手势之间累计旋转:在手势结束时保存当前旋转值,并在后续手势中将新的手势旋转量加到保存的偏移值上。
const rotation = useSharedValue(0);
const savedRotation = useSharedValue(0);
const rotationGesture = Gesture.Rotation()
.onUpdate((e) => {
rotation.value = savedRotation.value + e.rotation;
})
.onEnd(() => {
savedRotation.value = rotation.value;
});在 useAnimatedStyle 中应用缩放与旋转
useAnimatedStyle 中的 transform 数组可以同时应用缩放和旋转。变换的顺序很重要:先应用缩放,再应用旋转(也可以反过来,具体取决于所需效果)。React Native 的变换数组可以直接接受共享值中的数值缩放值和弧度旋转值。
rotate 变换需要一个带有“rad”后缀的字符串,而 scale 可以直接接收数字。请使用字符串拼接(不要使用带反引号的模板字面量)在工作单元中构造旋转字符串。
const animatedStyle = useAnimatedStyle(() => ({
transform: [
{ scale: scale.value },
{ rotate: rotation.value + 'rad' }, // concatenate string, no backticks
],
}));
return (
<GestureDetector gesture={composedGesture}>
<Animated.Image
source={{ uri: imageUrl }}
style={[styles.image, animatedStyle]}
/>
</GestureDetector>
);同时组合捏合与旋转
照片查看器需要让捏合和旋转同时进行。请将两个手势包装在 Gesture.Simultaneous 中,并将组合后的手势传递给单个 GestureDetector。这样,用户就能通过双指手势在一次流畅的操作中同时缩放和旋转。
再同时添加平移功能,就能实现完整的照片查看器体验:用双指同时缩放、旋转并重新定位图片。
const composedGesture = Gesture.Simultaneous(
pinch,
rotationGesture,
pan
);
return (
<GestureDetector gesture={composedGesture}>
<Animated.Image
source={{ uri: imageUrl }}
style={[styles.image, animatedStyle]}
resizeMode='contain'
/>
</GestureDetector>
);双击重置缩放
照片查看器的标准功能之一是双击在原始大小和放大视图之间切换,再次双击则重置。使用 Gesture.Tap().numberOfTaps(2) 创建双击识别器,并在其处理程序中使用弹簧动画将缩放和旋转恢复到初始值。
将 Gesture.Exclusive 与双击手势结合,并让双击优先于单击,以防止单击在双击序列期间触发。
const doubleTap = Gesture.Tap()
.numberOfTaps(2)
.onEnd(() => {
if (scale.value > 1) {
// Reset to natural size:
scale.value = withSpring(1);
savedScale.value = 1;
rotation.value = withSpring(0);
savedRotation.value = 0;
offsetX.value = withSpring(0);
offsetY.value = withSpring(0);
} else {
// Zoom to 2x:
scale.value = withSpring(2);
savedScale.value = 2;
}
});捏合缩放的中心点
默认情况下,缩放变换会以图片中心为中心缩放图片。为了获得更自然的效果,图片应围绕焦点缩放——也就是两根手指之间的中点。手势事件会提供 event.focalX 和 event.focalY 来实现这一点。
实现围绕焦点缩放,需要在缩放的同时调整平移偏移量,使手指下方的点在缩放过程中保持固定。这涉及计算给定焦点处因缩放变化而产生的偏移变化。
const pinch = Gesture.Pinch()
.onUpdate((e) => {
const newScale = clamp(savedScale.value * e.scale, MIN_SCALE, MAX_SCALE);
const scaleDelta = newScale / scale.value;
// Adjust offset to keep focal point fixed:
offsetX.value = e.focalX - scaleDelta * (e.focalX - offsetX.value);
offsetY.value = e.focalY - scaleDelta * (e.focalY - offsetY.value);
scale.value = newScale;
});平移与缩放后的边界限制
放大后,用户可以平移来查看图片的不同部分。但您应限制平移范围,避免图片被拖动到边缘之外而露出空白画布。请根据当前缩放比例和图片尺寸计算最大平移偏移量,并在平移手势的 onUpdate 中限制偏移量。
这种细节处理能将专业的照片查看器与粗糙的原型区分开来。限制逻辑只需几行数学计算,并会作为工作单元在 UI 线程上运行。
const IMAGE_WIDTH = 300;
const CONTAINER_WIDTH = 375;
const pan = Gesture.Pan()
.onUpdate((e) => {
const maxOffsetX = Math.max(0, (scale.value * IMAGE_WIDTH - CONTAINER_WIDTH) / 2);
offsetX.value = clamp(
savedOffsetX.value + e.translationX,
-maxOffsetX,
maxOffsetX
);
});缩放过度时弹回
当用户在缩放过度(低于最小值或高于最大值)后松开手指时,应使用弹簧动画将缩放比例恢复到限制值,而不是让图片停留在异常大小。请在捏合手势的 onEnd 回调中执行此操作。
同样地,当用户缩小回 1 倍后,请将平移偏移量重置为零,使图片回到中心。在 onEnd 中结合这些边界检查,可以让组件使用起来更精致、更可预测。
const pinch = Gesture.Pinch()
.onEnd(() => {
if (scale.value < MIN_SCALE) {
scale.value = withSpring(MIN_SCALE);
savedScale.value = MIN_SCALE;
// Reset pan to center:
offsetX.value = withSpring(0);
offsetY.value = withSpring(0);
} else if (scale.value > MAX_SCALE) {
scale.value = withSpring(MAX_SCALE);
savedScale.value = MAX_SCALE;
} else {
savedScale.value = scale.value;
}
});整合所有功能
一个完整的可缩放、可旋转、可平移照片查看器包括:用于保存缩放、旋转和偏移的共享值;通过 useAnimatedStyle 应用所有变换;使用 Gesture.Simultaneous 组合捏合、旋转、平移和双击;以及在 onUpdate 中进行边界限制,并在 onEnd 中进行弹簧修正。
这是您可以在 React Native 中构建的最令人印象深刻的交互之一,并且整个过程都在 UI 线程上运行,手势期间无需使用 JavaScript 线程,因此其表现几乎与原生实现无异。
const composed = Gesture.Simultaneous(
Gesture.Exclusive(
doubleTap,
singleTap
),
pan,
pinch,
rotationGesture
);
const style = useAnimatedStyle(() => ({
transform: [
{ translateX: offsetX.value },
{ translateY: offsetY.value },
{ scale: scale.value },
{ rotate: rotation.value + 'rad' },
],
}));快速检查
测试您对本课 React Native 移动开发概念的理解。
课程回顾
在本课中,您学习了:Gesture.Pinch() 提供 event.scale,用于跟踪相对于手势起始位置的双指缩放;Gesture.Rotation() 提供以弧度表示的 event.rotation,用于跟踪双指扭转;以及Gesture.Simultaneous 将捏合、旋转和平移组合起来,使三者同时工作,从而实现完整的照片查看器体验。接下来,我们将配置自定义 URL 方案,以便深层链接到应用中的特定屏幕。
用 AI 导师学习 JavaScript — 免费
在浏览器中编写并运行真实代码,获得全天候 AI 导师的即时帮助,并在网页或应用中继续学习。
- 课程
- 30
- 课程
- 120
常见问题解答
「捏合缩放与旋转」课时是免费的吗?
是的 — 「捏合缩放与旋转」的完整文本可在网页上免费阅读。要进行交互式练习(内置代码编辑器和全天候 AI 导师)并解锁 React Native Academy 课程的其余内容,请升级到 CoddyKit PRO。 React Native Academy 课程共包含 4 节课。
「捏合缩放与旋转」这节课中我会学到什么?
结合 PinchGestureHandler 和 RotationGestureHandler,并启用同时手势识别,实现可缩放、可旋转的图像组件。 你通过在浏览器中直接运行的动手代码来练习 React Native Academy,全天候 AI 导师会在你学习这节课的过程中回答你的问题。
学习 React Native Academy 需要有经验吗?
无需任何先前经验。CoddyKit 上的 React Native Academy 课程适合初学者到高级学习者,你可以从这里开始或从头开始,按照自己的节奏学习。 这是第 4 节课,共 4 节。
「捏合缩放与旋转」课时需要多长时间?
大多数 CoddyKit 课程大约需要 5–10 分钟。每节课都很精短且互动,所以你能稳步进步,并在网页和应用中从离开的地方继续。
我能在这节 React Native Academy 课中编写并运行代码吗?
能。每节 React Native Academy 课都包含内置代码编辑器,你可以在浏览器中直接编写并运行真实代码,并获得即时 AI 反馈 — 无需本地设置。