ReactNativeではジェスチャーを扱うためのメソッドがViewに備わっていて、そのあたりはGestureResponderSystemで解説されてます。
それとは別にPanResponderというものがあって、これは特にDragを扱いやすくするよう別途用意されているものです。(と理解してます)
PanResponderについてはググると結構コード上げてる人が多い(公式ので十分だし)ので、簡単に理解した内容をまとめます。
簡単な使い方
いきなりTinderみたいなことやってる完成系のコードを見ると結構ボリュームあって動揺するので、まず実装に必要なキモだけ書いてみると...
1. componentWillMountなどでPanResponderインスタンスを生成する。
componentWillMount: function() {
this._panResponder = PanResponder.create({
onStartShouldSetPanResponder: true,
onMoveShouldSetPanResponder: true,
onPanResponderGrant: this._handlePanResponderGrant,
onPanResponderMove: this._handlePanResponderMove,
onPanResponderRelease: this._handlePanResponderEnd,
onPanResponderTerminate: this._handlePanResponderEnd,
});
},
2. Dragさせたい対象のViewにジェスチャーを補足するのに必要なhandlerを渡す。
panResponder.panHandlersでhandlerが帰ってくるのでそのまま渡すだけです。
<View
{...this._panResponder.panHandlers}
/>
大きな手順としてはこれだけです。
panResponderをcreateするときのオプションを工夫して、
Dragしたときに位置が変わるようにしたりAnimationするようにしたりします。
Createのオプション
まず、どのタイミングでイベント取るかを指定します。
普通にDragするだけならonStartShouldSetPanResponder
とonMoveShouldSetPanResponder
でtrueを返しとけばいいようで、〜Capture
の方はパブリングさせずに奪ってしまうという意味のようです。条件によってユーザー操作をスルーして重くならないようにする、などの場合に活用するのかと思います。
onStartShouldSetPanResponder: (evt, gestureState) => true,
onStartShouldSetPanResponderCapture: (evt, gestureState) => true,
onMoveShouldSetPanResponder: (evt, gestureState) => true,
onMoveShouldSetPanResponderCapture: (evt, gestureState) => true,
それ以外のオプションで普通に各イベント時の処理を書いていきます。
例えばDragに合わせてViewを移動させるなら、以下のような感じです。
- Animatedについては別記事で書いてるので良ければ参考にしてください。
移動量を入れとくstateを作って、
constructor(props: any) {
super(props);
this.state = {
pan: new Animated.ValueXY()
};
}
onPanResponderMove
時に移動量をstateに反映します。
Animated.event
はユーザー操作イベントを引数に取るhandlerに渡してあげればいい具合にAnimated valueへ反映してくれる便利なやつです。
// 捕まえた時
onPanResponderGrant: (e, gestureState) => {
this.state.pan.setOffset({x: this._animatedValueX, y: this._animatedValueY});
this.state.pan.setValue({x: 0, y: 0}); //Initial value
},
// 操作中
onPanResponderMove: Animated.event([
null, {dx: this.state.pan.x, dy: this.state.pan.y}
]),
// 離した時
onPanResponderRelease: () => {
this.state.pan.flattenOffset();
}
ValueXY
はx, yの位置情報の他にoffsetがもてるようで、onPanResponderGrant
で前回操作時の最後の位置を入れ込んでます。最後の位置は適当な変数で別途保持してますが、これはAnimated.addListenerを使ってトラックしてます。詳細なコードは最後の参考リンクから見てみましょう。
それとflattenOffset
はValueXY
のoffsetの値をxyにmergeしてさらにoffsetを0にしてくれるメソッドのようです。
Viewを離した時に呼ばれるonPanResponderRelease
で一応flattenOffset
を呼んでますが、このコードだと結局onPanResponderGrant
でoffsetをセットしなおしてるので、flattenOffset
がなくても同じ動作が期待できます。
適当に見てみたGithubリポジトリでもみんなこのようにしていて、あくまで移動量をもっとく変数なので、おまじない的にリセットしとこうという意図だと理解しました。
あとは、Animatedを使うのでViewもきちんとAnimated.Viewに変更が必要です。
<Animated.View
style={{transform: this.state.pan.getTranslateTransform()}}
{...this._panResponder.panHandlers}
/>
以上
以上駆け足でしたが伝えられたでしょうか。最低限必要なコードを理解するまで時間がかかったので今回まとめてみました。リンクにあるサイトに詳しくまとめられているのでもっと色々やりたい方は見てみましょう。
参考リンク
Doc
http://facebook.github.io/react-native/docs/panresponder.html#content
詳細に解説してあるサイト
http://browniefed.com/react-native-animation-book/events/PANRESPONDER.html
わかりやすいサンプルがあるサイト
http://browniefed.com/blog/react-native-animated-api-with-panresponder/