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

![dragbox.gif](https://qiita-image-store.s3.amazonaws.com/0/29793/b58613ff-f829-ec40-142d-54c4bb4e7c18.gif)

ReactNativeではジェスチャーを扱うためのメソッドがViewに備わっていて、そのあたりは[GestureResponderSystem](http://facebook.github.io/react-native/docs/gesture-responder-system.html#content)で解説されてます。

それとは別にPanResponderというものがあって、これは特にDragを扱いやすくするよう別途用意されているものです。（と理解してます）

PanResponderについてはググると結構コード上げてる人が多い(公式ので十分だし)ので、簡単に理解した内容をまとめます。

### 簡単な使い方

いきなりTinderみたいなことやってる完成系のコードを見ると結構ボリュームあって動揺するので、まず実装に必要なキモだけ書いてみると...

#### １. componentWillMountなどでPanResponderインスタンスを生成する。

```
componentWillMount: function() {
  this._panResponder = PanResponder.create({
    onStartShouldSetPanResponder: true,
    onMoveShouldSetPanResponder: true,
    onPanResponderGrant: this._handlePanResponderGrant,
    onPanResponderMove: this._handlePanResponderMove,
    onPanResponderRelease: this._handlePanResponderEnd,
    onPanResponderTerminate: this._handlePanResponderEnd,
  });
},
```

#### ２. 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については[別記事](http://qiita.com/imaimiami/items/1ad4170c06b1922604dd)で書いてるので良ければ参考にしてください。

移動量を入れとく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/