＊Android＊ピンチイン・ピンチアウトを検知する

#はじめに
こんにちは．今回は，Androidでピンチ操作を検知しようと思います．ピンチイン・ピンチアウトを判別する方法について解説します．

#前提
開発環境は以下の通りです．
＊Android Studio 4.0.1
＊targetSdkVersion 28
＊Google Nexus 5x

#ピンチイン・ピンチアウトの判別
ピンチ操作の検知には，```ScaleGestureDetector```を使用します．第一引数にContext，第二引数には，```OnScaleGestureListener```インタフェースを実装したクラスのオブジェクトを指定します．今回は，匿名クラスを使用して，インタフェースを実装します．

```java
this.scaleGestureDetector = new ScaleGestureDetector(this, new ScaleGestureDetector.OnScaleGestureListener() {
    // ピンチ操作中に繰り返し呼ばれる
    @Override
    public boolean onScale(ScaleGestureDetector detector) {
        return true;
    }
    // ピンチ操作を開始したときに呼ばれる
    @Override
    public boolean onScaleBegin(ScaleGestureDetector detector) {
        return true;
    }
    //  ピンチ操作を終了したときに呼ばれる
    @Override
    public void onScaleEnd(ScaleGestureDetector detector) {
    }
});
```

次に，```onTouchEvent```メソッドを用意し，```onTouchEvent```メソッドが呼ばれた際に、```ScaleGestureDetector.onTouchEvent```が呼ばれるようにすることで，ピンチ操作を検知することができます．

```java
@Override
public boolean onTouchEvent(MotionEvent motionEvent){
    this.scaleGestureDetector.onTouchEvent(motionEvent);
    return true;
```

ピンチ操作を検知できたら，次にピンチイン・ピンチアウトを判別します．ピンチ操作では，画面に2本の指を触れています．指間の距離を測定するメソッドとして```getCurrentSpan()```が用意されているので，それを使用します．指間の距離の変化を利用することで，ピンチイン・ピンチアウトを判別することができます．ピンチイン・ピンチアウトの誤判定を防ぐために，閾値として指間の距離xを指定し，x以上変化した場合にピンチイン・ピンチアウトの判定を行います．

```java
@Override
public boolean onScale(ScaleGestureDetector detector) {          
    distance_current = detector.getCurrentSpan();
    return true;
}
```

#ピンチ操作中の指間の距離の正確性
ピンチ操作中の指間の距離を取得するには```getCurrentSpan()```を使用します．取得した指間の距離は，スマートフォンの画面の対角線の長さより小さいはずです．誤った距離を取得してしまった場合に取り除くために，スマートフォンの対角線の長さと比較して，それより小さいもののみを指間の距離とします．ここでは，Navigation bar を除いた画面の領域から，対角線の長さを求めます．画面の対角線の長さを取得するには以下のようにします．

```java
WindowManager wm = (WindowManager) getSystemService(WINDOW_SERVICE);
Point size = new Point();
Display disp = wm.getDefaultDisplay();
disp.getSize(size);
screen_width = this.size.x;
screen_height = this.size.y;
//対角線の長さを求める
int screen_diagonal = (int) Math.sqrt((int)(Math.pow(screen_width, 2)) + (int)(Math.pow(screen_height, 2)));
```

# サンプルコード

```java:AndroidManifest.xml
?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.myapplication">

    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">
        <activity android:name=".MainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>
</manifest>
```

```java:activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Hello World!"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

</androidx.constraintlayout.widget.ConstraintLayout>
```

```java:MainActivity.java
package com.example.myapplication3;

import androidx.appcompat.app.AppCompatActivity;
import android.graphics.Point;
import android.os.Bundle;
import android.view.Display;
import android.view.MotionEvent;
import android.view.ScaleGestureDetector;
import android.view.WindowManager;
import android.widget.Toast;

public class MainActivity extends AppCompatActivity {
    private ScaleGestureDetector scaleGestureDetector;
    private long time_elapsed;
    private long time_start;
    private long time_current;
    private float distance_current;
    private float distance_start;
    private Boolean flg_pinch_out;
    private Boolean flg_pinch_in;
    private WindowManager wm;
    private Display disp;
    private Point size;
    private int screen_width;
    private int screen_height;
    //画面の対角線の長さ
    private int screen_diagonal;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        this.wm = (WindowManager) getSystemService(WINDOW_SERVICE);
        this.size = new Point();
        this.disp = wm.getDefaultDisplay();
        this.disp.getSize(size);
        screen_width = this.size.x;
        screen_height = this.size.y;
        //対角線の長さを求める
        screen_diagonal = (int) Math.sqrt((int)(Math.pow(screen_width, 2)) + (int)(Math.pow(screen_height, 2)));
        this.scaleGestureDetector = new ScaleGestureDetector(this, new ScaleGestureDetector.OnScaleGestureListener() {
            @Override
            public boolean onScale(ScaleGestureDetector detector) {
                time_current = detector.getEventTime();
                time_elapsed = time_current - time_start;
                if (time_elapsed >= 0.5){
                    distance_current = detector.getCurrentSpan();
                    if (distance_start == 0){
                        distance_start = distance_current;
                    }
                    flg_pinch_out = (distance_current - distance_start) > 300;
                    flg_pinch_in = (distance_start - distance_current) > 300;
                    if (flg_pinch_out){
                        Toast.makeText(getApplicationContext(), "Pinch out", Toast.LENGTH_LONG).show();
                        time_start = time_current;
                        distance_start = distance_current;
                    }
                    else if (flg_pinch_in){
                        Toast.makeText(getApplicationContext(), "Pinch in", Toast.LENGTH_LONG).show();
                        time_start = time_current;
                        distance_start = distance_current;
                    }
                    else {
                        //pass
                    }
                }
                return true;
            }


            @Override
            public boolean onScaleBegin(ScaleGestureDetector detector) {
                distance_start = detector.getEventTime();
                if (distance_start > screen_diagonal){
                    distance_start = 0;
                }
                time_start = detector.getEventTime();
                return true;
            }

            @Override
            public void onScaleEnd(ScaleGestureDetector detector) {
            }
        });
    }

    @Override
    public boolean onTouchEvent(MotionEvent motionEvent){
        this.scaleGestureDetector.onTouchEvent(motionEvent);
        return true;
    }
}
```