LoginSignup
36
31

More than 5 years have passed since last update.

ReactNativeでDragを扱うためのPanResponderを理解する

Last updated at Posted at 2016-05-18

dragbox.gif

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するだけならonStartShouldSetPanResponderonMoveShouldSetPanResponderで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を使ってトラックしてます。詳細なコードは最後の参考リンクから見てみましょう。

それとflattenOffsetValueXYの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/

36
31
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
36
31