Konyでジェスチャーを認識させてみた（タップ、スワイプ、ロングタップ、パン、ピンチイン・アウト、回転）

#目次
1. はじめに
2. 実装方法
3. まとめ
4. 参考

#はじめに
スマートフォンを利用する時にジェスチャーを使う方は多いと思います。
例えば、Safariで前の画面に戻る時にスワイプを使って戻ってみたり...
そこでこの記事ではKonyでジェスチャーを認識させる方法をご紹介します。

まず今回認識させるジェスチャーについて説明します。

| ジェスチャー | 説明 |
| ------- | ------ |
| タップ | 画面を指先で叩く |
| スワイプ | 画面に触れ、その指先をスライドさせる |
| ロングタップ | 長押し。画面を指先で長く押す |
| パン | 移動処理 |
| ピンチイン | 2本の指の間隔を狭める |
| ピンチアウト | 2本の指の間隔を広くする | 
| 回転 | 2本指でタッチして回転させる | 

#実装方法
####**①画面の作成**
**1. メイン画面**
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/592264/2c101318-f49e-cf95-092b-4cfc06d0e729.png)
6つのボタンがあり、タップするとそれぞれの画面に遷移します。

**2. タップ画面**
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/592264/574723ec-1484-f6ff-8ffe-6a00fe4a2b85.png)
青い部分を1回タップすると「1回タップしました！」というメッセージが
緑の部分を1回タップすると「2回タップしました！」というメッセージが表示されます。
下の「Reset」ボタンを押すとメッセージがなくなり、初期状態に戻ります。

このような感じでその他の画面も作成します。
画面については下にあるデモ動画を参照いただけますと幸いです。

####**②コーディング**
各画面のコーディングを行います。

**1. メイン画面**

```frmMain.js
define({ 
　
  // タップ画面へ遷移
  navigatefrmTap: function() {
    const ntf = new kony.mvc.Navigation("frmTap");
    ntf.navigate();
  },

  // スワイプ画面へ遷移
  navigatefrmSwipe: function() {
    const ntf = new kony.mvc.Navigation("frmSwipe");
    ntf.navigate();
  },

  // ロングタップ画面へ遷移
  navigatefrmLongPress: function() {
    const ntf = new kony.mvc.Navigation("frmLongPress");
    ntf.navigate();
  },

  // パン画面へ遷移
  navigatefrmPan: function() {
    const ntf = new kony.mvc.Navigation("frmPan");
    ntf.navigate();
  },

  // ピンチイン・アウト画面へ遷移
  navigatefrmPinch: function() {
    const ntf = new kony.mvc.Navigation("frmPinch");
    ntf.navigate();
  },

  // 回転画面へ遷移
  navigatefrmRotation: function() {
    const ntf = new kony.mvc.Navigation("frmRotation");
    ntf.navigate();
  },
});
```
それぞれの画面へ遷移するように設定しています。

**2. タップ画面**

```frmTap.js
define({ 

  onTapClosure: function(widgetRef,gestureInfo){
    var tapParams = gestureInfo.gesturesetUpParams.taps;
    if(tapParams==1){
      this.view.lblTap.text="1回タップしました！";
    }else if(tapParams==2){
      this.view.lblDbl.text="2回タップしました！";
    }

  },
  
  preShow: function(){
    this.addDoubleTapGesture();
    this.addSingleTapGesture();
  },
  
  addSingleTapGesture: function(){
    var tapConfig={fingers:1,taps:1};
    this.view.flxSingle.addGestureRecognizer(constants.GESTURE_TYPE_TAP,tapConfig,this.onTapClosure);

  },
  
  addDoubleTapGesture: function(){
    var tapConfig={fingers:1,taps:2};
    this.view.flxDouble.addGestureRecognizer(constants.GESTURE_TYPE_TAP,tapConfig,this.onTapClosure);
  },
  
  reset: function(){
    this.view.lblTap.text = "";
    this.view.lblDbl.text = "";
  }
});
```
上を説明します。
**addGestureRecognizer（gestureType、gestureConfigParams、onGestureClosure）**：ウィジェットの指定したジェスチャタイプを認識させることができます。
ジェスチャタイプは以下になります。

| ジェスチャー |onGestureClosure |gestureConfigParams | 
| ------ | ------ | ------ | 
| タップ | constants.GESTURE_TYPE_TAP | fingers [Number]-ジェスチャで認識する必要のある指の最大数を指定<br>taps [Number]-ジェスチャで尊重する必要のあるタップの最大数を指定 | 
| スワイプ | constants.GESTURE_TYPE_SWIPE | fingers [Number]-ジェスチャで認識する必要のある指の最大数を指定 | 
| ロングタップ | constants.GESTURE_TYPE_LONGPRESS | pressDuration [Number]-ジェスチャがロングタップとして認識されるまでの最小時間間隔（秒単位）を指定 | 
| パン | constants.GESTURE_TYPE_PAN | fingers [Number]-ジェスチャで認識する必要のある指の最大数を指定<br>ContinuousEvents [ブール値]は、ジェスチャが認識されてから終了するまでのすべての変更に対して、コールバックを継続的に呼び出す必要があるかどうかを示す |
| 回転 | constants.GESTURE_TYPE_ROTATION |ContinuousEvents [ブール値]は、ジェスチャが認識されてから終了するまでのすべての変更に対して、コールバックを継続的に呼び出す必要があるかどうかを示す |
| ピンチイン・アウト | constants.GESTURE_TYPE_PINCH |ContinuousEvents [ブール値]は、ジェスチャが認識されてから終了するまでのすべての変更に対して、コールバックを継続的に呼び出す必要があるかどうかを示す |
※GESTURE_TYPE_RIGHTTAPもありますが、RIGHTTAPはWindows10にのみ適用可能となります。

**3. スワイプ画面**

```frmSwipe.js
define({ 

  onSwipeClosure:function(widgetRef,gestureInfo){
    var direction = "";
    var swipeDirection = ""+gestureInfo.swipeDirection; //Read swipe direction
    if (swipeDirection == "1") 
    {
      direction = "左";
      this.view.imgGes.src = "left.png";
    }
    else if (swipeDirection == "2") 
    {
      direction = "右";
      this.view.imgGes.src = "right.png";
    }
    else if (swipeDirection == "3") 
    {
      direction = "上";
      this.view.imgGes.src = "top.png";
    }
    else 
    {
      direction = "下";
      this.view.imgGes.src = "bottom.png";
    }
    this.view.lblGesture.text= "あなたがスワイプした方向は" + direction+"方向です";

  },

  addSwipeGesture:function(){
    var swipeConfig={fingers:1};
    this.view.imgGes.addGestureRecognizer(constants.GESTURE_TYPE_SWIPE,swipeConfig, this.onSwipeClosure);
  },

  reset: function() {
    this.view.imgGes.src = "crosskey.png";
    this.view.lblGesture.text= "下の枠のところで、好きな方法にスワイプしてください。\nスワイプした方向の矢印が表示されます。";
  }
});
```
上を説明します。
**swipeDirection**：左方向の場合は1、右方向の場合は2、上方向の場合は3、下方向の場合は4

**4. ロングタップ画面**

```frmLongPress.js
define({ 

  onLongpressClosure:function(widgetRef,gestureInfo){
    this.view.rixGesture.text= "今押している時間は" + gestureInfo.gesturesetUpParams.pressDuration+"秒です。";
    this.view.lblGesture.text = gestureInfo.gesturesetUpParams.pressDuration;
  },

  addLongPressGesture:function(){
    var getTime = parseInt(this.view.lstTime.selectedkeyvalue[1]);
    if(getTime){
      var longConfig = {pressDuration: getTime};
      this.view.flxLongpress.addGestureRecognizer(constants.GESTURE_TYPE_LONGPRESS, longConfig, this.onLongpressClosure);
    }
  },

  reset: function() {
    var longConfig = {pressDuration: 1};
    this.view.flxLongpress.addGestureRecognizer(constants.GESTURE_TYPE_LONGPRESS, longConfig, this.onLongpressClosure);
    this.view.rixGesture.text = "下のリストから何秒押し続けるか、選択してください。そのあと、緑色の枠を指で長押ししてください。<br>例：3を選択した場合、緑色の枠のところを指で長押ししてから3秒間カウントします。";
    this.view.lblGesture.text = "";
    this.view.lstTime.selectedKey = "lb1";
  }

});
```
上を説明します。
**pressDuration**：ジェスチャがロングタップとして認識されるまでの最小時間間隔（秒単位）を指定。たとえば、pressDurationが2秒の場合、継続して押すと、2秒以上続く場合にのみロングタップとして認識されます。デフォルト値は1です。
※Windowsには適用されません。

**5. パン画面**

```frmPan.js
define({ 
 
	onPanclosure:function(widgetRef,gestureInfo){
      
      this.view.lbltx.text=gestureInfo.translationX;
      this.view.lblty.text=gestureInfo.translationY;
      this.view.lblvx.text=gestureInfo.velocityX;
      this.view.lblvy.text=gestureInfo.velocityY;
      this.view.lblgx.text=gestureInfo.gestureX;
      this.view.lblgy.text=gestureInfo.gestureY;    
          
  },
  addPanGesture:function(){
    var panConfig={fingers:1,continuousEvents :false};
     this.view.flxpan.addGestureRecognizer(constants.GESTURE_TYPE_PAN,panConfig,this.onPanclosure);
        
  }
 });
```
上を説明します。
**translationX** または **translationY**：数値としての累積距離
**velocityX** または **velocityY**：1秒あたりの速度の水平および垂直距離。
**gestureX**：ジェスチャーが発生したポイントのX座標
**gestureY**：ジェスチャーが発生したポイントのY座標

**6. ピンチイン・アウト画面**

```frmPinch.js
define({ 

  g_scaleStart: null,
  g_scaleEnd: null,
  g_zoomIn: null,
  g_zoomOut: null,
  onPinchClosure:function(widgetRef,gestureInfo){
    var c = gestureInfo.gestureState;
    var d =	gestureInfo.scale;
    if(kony.os.toNumber(c) == 1)
    {
      this.g_scaleStart = kony.os.toNumber(d);
    }

    if( kony.os.toNumber(c) == 2)
    {
      this.g_scaleEnd = kony.os.toNumber(d);

      kony.print(this.g_scaleEnd +"===="+this.g_scaleStart + (this.g_scaleEnd > this.g_scaleStart));
      if(this.g_scaleEnd > this.g_scaleStart)
      {
        this.g_zoomIn =this.g_zoomIn+5;
        this.view.imgPinch.width=this.g_zoomIn+"%";
        this.view.imgPinch.height=this.g_zoomIn+"%";

        this.view.imgPinch.animate(
          kony.ui.createAnimation({
            "100": {

              "width": this.g_zoomIn+"%",
              "height":this.g_zoomIn+"%",
            }
          }), {
            "delay": 0,
            "iterationCount": 1,
            "fillMode": kony.anim.FILL_MODE_FORWARDS,
            "duration": 0
          }, {
            "animationEnd": function(){}
          });
      }
      else
      {
        this.g_zoomOut = this.g_zoomIn;
        if(this.g_zoomOut >50)
        {

          this.g_zoomOut =this.g_zoomOut-5;
          this.g_zoomIn = this.g_zoomOut;
          this.view.imgPinch.width= this.g_zoomOut+"%";
          this.view.imgPinch.height= this.g_zoomOut+"%";

          this.view.imgPinch.animate(
            kony.ui.createAnimation({
              "100": {

                "width": this.g_zoomOut+"%",
                "height":this.g_zoomOut+"%",
               }
            }), {
              "delay": 0,
              "iterationCount": 1,
              "fillMode": kony.anim.FILL_MODE_FORWARDS,
              "duration": 0
            }, {
              "animationEnd": function(){}
            });
        }
      }


    }	
    this.view.lblGesture.text=JSON.stringify(gestureInfo);

  },
  addPinchGesture:function(){
    var pinchConfig={fingers :2,continuousEvents :true};
    this.view.flxPinch.addGestureRecognizer(constants.GESTURE_TYPE_PINCH,pinchConfig,this.onPinchClosure);
  }
});
```
上を説明します。
**gestureState**:：ジェスチャーの状態を示す。1 –ジェスチャー状態が始まる、2-ジェスチャー状態が変更された、3 –ジェスチャー状態が終了
**scale**：画面座標の2つのタッチのポイントに対するスケール係数

**7. 回転画面**

```frmRotation.js
define({ 

  onRotateClosure:function(widgetRef,gestureInfo){
    var gState = gestureInfo.gestureState;
    if(kony.os.toNumber(gState)==2){
      this.rotatebyAngle(360-gestureInfo.rotation);
    }
  },
  addRotateGesture:function(){
    var rotateConfig={fingers :2,continuousEvents :true};
    this.view.flxRotation.addGestureRecognizer(constants.GESTURE_TYPE_ROTATION,rotateConfig,this.onRotateClosure);

  },
  rotatebyAngle: function(angle){

    var trasnfobj=kony.ui.makeAffineTransform();
    trasnfobj.rotate(angle);

    var animobj=kony.ui.createAnimation({

      "100": {
        "transform":trasnfobj
      }
    });
    this.view.imgrotate.animate(animobj,  {
      "delay": 0,
      "iterationCount": 1,
      "fillMode": kony.anim.FILL_MODE_FORWARDS,
      "duration": 0
    }, {
      "animationEnd": function(){}
    });
  }
});
```
上を説明します。
**gestureState**:：ジェスチャーの状態を示す。1 –ジェスチャー状態が始まる、2-ジェスチャー状態が変更された、3 –ジェスチャー状態が終了


####**③Actionの紐付け**
関数の準備ができたらActionと紐づけていきます。

メイン画面以外の画面で共通しているActionの紐付け
`flxImage`(バックボタン)を選択 > `Action`タブをクリック > `onClick`のEditをクリック > 以下のコードを貼り付け

```
var ntf = new kony.mvc.Navigation("frmMain");
ntf.navigate();
```

**1. メイン画面**
① `btnTap`(タップ)を選択 > `Action `タブをクリック > `onClick`のEditをクリック > Action(`navigatefrmTap`)紐づけ
② `btnSwipe`(スワイプ)を選択 > `Action`タブをクリック > `onClick`のEditをクリック > Action(`navigatefrmSwipe`)紐づけ
③ `btnLongPress`(ロングタップ)を選択 > `Actionタブをクリック` > `onClickのEditをクリック` > Action(`navigatefrmLongPress`)紐づけ
④`btnPan`(パン)を選択` > `Actionタブをクリック` > `onClickのEditをクリック` > Action(`navigatefrmPan`)紐づけ
⑤`btnPinch`(ピンチイン・アウト)を選択` > `Action`タブをクリック > `onClick`のEditをクリック > Action(`navigatefrmPinch`)紐づけ
⑥`btnRotation`(回転)を選択 > `Action`タブをクリック > `onClick`のEditをクリック > Action(`navigatefrmRotation`)紐づけ

**2. タップ画面**
①`ButtonRound`(Resetボタン)を選択 > `Action`タブをクリック > `onClick`のEditをクリック > Action(`reset`)紐づけ
②`frmTap`(フォーム)を選択 > `Action`タブをクリック > `preShow`のEditをクリック > Action(`preShow`)紐づけ

その他の画面は上を参考にして紐付けてください。
実際にどうなるかみていきましょう！

####**④実装の確認**
<blockquote class="twitter-tweet"><p lang="und" dir="ltr"><a href="https://t.co/qkEKKNcsRE">pic.twitter.com/qkEKKNcsRE</a></p>&mdash; Kony (@Kony12763790) <a href="https://twitter.com/Kony12763790/status/1361240372710576128?ref_src=twsrc%5Etfw">February 15, 2021</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>

それぞれのジェスチャーが認識されていることが分かりますね！

#まとめ
今回は、ジェスチャーを認識させる方法について説明しました。
みなさんもぜひ活用してみてくださいね:wave:

#参考
ジェスチャー：
https://www.so-net.ne.jp/siteinfo/intro/manual6.html

FlexContainerのメソッド：
https://docs.kony.com/konylibrary/visualizer/viz_widget_prog_guide/content/FlexContainer_Methods.htm

Gesture API：
https://docs.kony.com/konylibrary/visualizer/viz_api_dev_guide/content/kony.application_functions_gestures.htm#onGestureClosure1