私はPanGestureHandler
とPinchGestureHandler
を使用して、画面上で大きな画像をパンおよびズームイン/ズームアウトできるようにしています。ただし、スケーリング変換を導入すると、パンの動作が異なります。
この実装には 3 つの目標があります。
- ビューが最初に読み込まれたときに、特定の高さに合わせて画像を縮小しようとしています。これは、ユーザーができるだけ多くの画像を見ることができるようにするためです。画像にスケール変換のみを適用する場合、 の平行移動値は
0
画像を左上隅に配置しません (中央のスケール原点の結果として)。 - 誰かがピンチ ジェスチャを使用してズームしたときに、ズームの原点がユーザーがジェスチャを開始した時点にあるかのように見えるように、変換値も調整されるようにしようとしています (React Native は現在、スケール変換の中心原点)。これを達成するには、ユーザーがズームしたときに移動値を調整する必要があると思います (スケールが 以外の場合
1
)。 - ズーム後にパンするとき、ズームからのスケールに関連する移動値を調整することにより、パンがユーザーの指を (速く/遅く移動するのではなく) 追跡するようにします。
これが私がこれまでに持っているものです:
import React, { useRef, useCallback } from 'react';
import { StyleSheet, Animated, View, LayoutChangeEvent } from 'react-native';
import {
PanGestureHandler,
PinchGestureHandler,
PinchGestureHandlerStateChangeEvent,
State,
PanGestureHandlerStateChangeEvent,
} from 'react-native-gesture-handler';
const IMAGE_DIMENSIONS = {
width: 2350,
height: 1767,
} as const;
export default function App() {
const scale = useRef(new Animated.Value(1)).current;
const translateX = useRef(new Animated.Value(0)).current;
const translateY = useRef(new Animated.Value(0)).current;
const setInitialPanZoom = useCallback((event: LayoutChangeEvent) => {
const { height: usableHeight } = event.nativeEvent.layout;
const scaleRatio = usableHeight / IMAGE_DIMENSIONS.height;
scale.setValue(scaleRatio);
// TODO: should these translation values be set based on the scale?
translateY.setValue(0);
translateX.setValue(0);
}, []);
// Zoom
const onZoomEvent = Animated.event(
[
{
nativeEvent: { scale },
},
],
{
useNativeDriver: true,
}
);
const onZoomStateChange = (event: PinchGestureHandlerStateChangeEvent) => {
if (event.nativeEvent.oldState === State.ACTIVE) {
// Do something
}
};
// Pan
const handlePanGesture = Animated.event([{ nativeEvent: { translationX: translateX, translationY: translateY } }], {
useNativeDriver: true,
});
const onPanStateChange = (_event: PanGestureHandlerStateChangeEvent) => {
// Extract offset so that panning resumes from previous location, rather than resetting
translateX.extractOffset();
translateY.extractOffset();
};
return (
<View style={styles.container}>
<PanGestureHandler
minPointers={1}
maxPointers={1}
onGestureEvent={handlePanGesture}
onHandlerStateChange={onPanStateChange}
>
<Animated.View style={styles.imageContainer} onLayout={setInitialPanZoom}>
<PinchGestureHandler onGestureEvent={onZoomEvent} onHandlerStateChange={onZoomStateChange}>
<Animated.View style={{ transform: [{ scale }, { translateY }, { translateX }] }}>
<Animated.Image
source={require('./assets/my-image.png')}
style={{
width: IMAGE_DIMENSIONS.width,
height: IMAGE_DIMENSIONS.height,
}}
resizeMode="contain"
/>
</Animated.View>
</PinchGestureHandler>
</Animated.View>
</PanGestureHandler>
</View>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#fff',
alignItems: 'center',
justifyContent: 'center',
},
imageContainer: {
flex: 1,
backgroundColor: 'orange',
width: '100%',
},
});
平行移動値から寸法の差を差し引く方法に沿って何かを試しました。
translateX.setValue(0 - (IMAGE_DIMENSIONS.width / 2) - (IMAGE_DIMENSIONS.width * scaleRatio / 2))
この実装では数値がうまく機能しないため、おそらくこれを正しく行っていません。また、これは私の最初の目標のみを説明するものであり、他の 2 つを達成するには、スケール値に基づいて変換値を補間するようなことを行う必要があると推測しています。