[Java] Android Detects pinch-in and pinch-out

3 minute read

Introduction

Hello. This time, I will detect the pinch operation with Android. I will explain how to distinguish between pinch-in and pinch-out.

Premise

The development environment is as follows. *Android Studio 4.0.1 *TargetSdkVersion 28 *Google Nexus 5x

Pinch-in/Pinch-out discrimination

ScaleGestureDetector is used to detect the pinch operation. Specify Context as the first argument and the object of the class that implements the ```OnScaleGestureListener

interface as the second argument. This time, implement the interface using anonymous class.



```java
this.scaleGestureDetector = new ScaleGestureDetector(this, new ScaleGestureDetector.OnScaleGestureListener() {
    // Repeatedly called during pinch operation
    @Override
    public boolean onScale(ScaleGestureDetector detector) {
        return true;
    }
    // Called when the pinch operation is started
    @Override
    public boolean onScaleBegin(ScaleGestureDetector detector) {
        return true;
    }
    // Called when the pinch operation is finished
    @Override
    public void onScaleEnd(ScaleGestureDetector detector) {
    }
});

Next, prepare the onTouchEvent method, and make the ScaleGestureDetector.onTouchEvent called when the ```onTouchEvent

method is called, thereby performing the pinch operation. It can be detected.



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

If a pinch operation is detected, then pinch in/pinch out is determined. In the pinch operation, two fingers are touching the screen. ```getCurrentSpan()

is provided as a method for measuring the distance between fingers, so use that. Pinch-in/pinch-out can be identified by using the change in the distance between fingers. In order to prevent erroneous pinch-in/pinch-out judgments, the distance x between fingers is specified as the threshold value, and pinch-in/pinch-out judgment is performed when the distance exceeds x.



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

Accuracy of distance between fingers during pinch operation

Use ```getCurrentSpan()

to get the distance between fingers during pinch operation. The distance between the obtained fingers should be smaller than the length of the diagonal line of the smartphone screen. To get rid of the wrong distance, compare it with the length of the diagonal line of your smartphone and use only the smaller distance as the distance between your fingers. Here, the length of the diagonal line is obtained from the screen area excluding the navigation bar. To get the diagonal length of the screen, do the following:



```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;
// find the length of the diagonal
int screen_diagonal = (int) Math.sqrt((int)(Math.pow(screen_width, 2)) + (int)(Math.pow(screen_height, 2)));

Sample code

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>

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>

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;
    // screen diagonal length
    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;
        // find the length of the diagonalscreen_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;
    }
}