はじめに
こんにちは.今回は,Androidでピンチ操作を検知しようと思います.ピンチイン・ピンチアウトを判別する方法について解説します.
前提
開発環境は以下の通りです.
*Android Studio 4.0.1
*targetSdkVersion 28
*Google Nexus 5x
ピンチイン・ピンチアウトの判別
ピンチ操作の検知には,ScaleGestureDetector
を使用します.第一引数にContext,第二引数には,OnScaleGestureListener
インタフェースを実装したクラスのオブジェクトを指定します.今回は,匿名クラスを使用して,インタフェースを実装します.
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
が呼ばれるようにすることで,ピンチ操作を検知することができます.
@Override
public boolean onTouchEvent(MotionEvent motionEvent){
this.scaleGestureDetector.onTouchEvent(motionEvent);
return true;
ピンチ操作を検知できたら,次にピンチイン・ピンチアウトを判別します.ピンチ操作では,画面に2本の指を触れています.指間の距離を測定するメソッドとしてgetCurrentSpan()
が用意されているので,それを使用します.指間の距離の変化を利用することで,ピンチイン・ピンチアウトを判別することができます.ピンチイン・ピンチアウトの誤判定を防ぐために,閾値として指間の距離xを指定し,x以上変化した場合にピンチイン・ピンチアウトの判定を行います.
@Override
public boolean onScale(ScaleGestureDetector detector) {
distance_current = detector.getCurrentSpan();
return true;
}
ピンチ操作中の指間の距離の正確性
ピンチ操作中の指間の距離を取得するにはgetCurrentSpan()
を使用します.取得した指間の距離は,スマートフォンの画面の対角線の長さより小さいはずです.誤った距離を取得してしまった場合に取り除くために,スマートフォンの対角線の長さと比較して,それより小さいもののみを指間の距離とします.ここでは,Navigation bar を除いた画面の領域から,対角線の長さを求めます.画面の対角線の長さを取得するには以下のようにします.
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)));
サンプルコード
?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>
<?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>
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;
}
}