본문 바로가기

안드로이드

멀티터치 이미지 뷰어 만들기

반응형

애플에서 '핀치'라고 명칭을 정한 멀티터치는 두 손가락을 이용해 손가락 사이를 벌리면 이미지가 점차 확대되고, 좁히면 이미지가 작아지도록 만든 기능이다

 

한 손가락으로 터치했을때의 좌표뿐만 아니라 두 번째 손가락으로 터치했을 때의 좌표 값까지 알 수 있어야한다

 

getPointerCount 메소드는 몇 개의 손가락이 터치되었는지 알 수 있도록 해주는 것으로 만약 반환된 값이 1이라면 한 개의 손가락, 2라면 두 개의 손가락이 터치된 상태이다

이벤트 처리에 사용되는 getX와 getY 메소드는 손가락이 하나일때 X와 Y의 좌표 값을 가져오지만, getX(int pointerIndex)와 getY(int pointerIndex) 메소드는 여러 개의 손가락이 터치되었을 때 각각의 손가락이 가지는 인덱스의 값을 이용해 좌표 값을 확인할 수 있도록 한다

 

 

 

activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">

    <TextView
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="두 손가락을 이용해 터치해 보세요." />

    <LinearLayout
        android:id="@+id/container"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical"></LinearLayout>

</LinearLayout>

 

 

 

ImageDisplayView.java

package com.example.a70_multitouch;

import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Matrix;
import android.graphics.Paint;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import android.view.View;

public class ImageDisplayView extends View implements View.OnTouchListener {
    private static final String TAG = "ImageDisplayView";

    Context mContext;
    Canvas mCanvas;
    Bitmap mBitmap;
    Paint mPaint;

    int lastX;
    int lastY;

    Bitmap sourceBitmap;

    Matrix mMatrix;

    float sourceWidth = 0.0F;
    float sourceHeight = 0.0F;

    float bitmapCenterX;
    float bitmapCenterY;

    float scaleRatio;
    float totalScaleRatio;

    float displayWidth = 0.0F;
    float displayHeight = 0.0F;

    int displayCenterX = 0;
    int displayCenterY = 0;

    public float startX;
    public float startY;

    public static float MAX_SCALE_RATIO = 5.0F;
    public static float MIN_SCALE_RATIO = 0.1F;

    float oldDistance = 0.0F;

    int oldPointerCount = 0;
    boolean isScrolling = false;
    float distanceThreshold = 3.0F;

    public ImageDisplayView(Context context) {
        super(context);

        mContext = context;

        init();
    }

    public ImageDisplayView(Context context, AttributeSet attrs) {
        super(context, attrs);

        mContext = context;

        init();
    }

    private void init() {
        Log.d(TAG, "init() 호출");

        mPaint = new Paint();
        mMatrix = new Matrix();

        lastX = -1;
        lastY = -1;

        setOnTouchListener(this);
    }

    //     뷰가 초기화되고 나서 화면에 보이기 전에 크기가 정해지면 호출되는 메소드 안에서 메모리 상에 새로운 비트맵 객체 생성
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        Log.d(TAG, "onSizeChanged() 호출 : " + w + ", " + h + ", " + oldw + ", " + oldh);

        if (w > 0 && h > 0) {
            newImage(w, h);

            redraw();
        }
    }

    public void newImage(int width, int height) {
        Log.d(TAG, "newImage() 호출 : " + width + ", " + height);

        Bitmap img = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
        Canvas canvas = new Canvas();
        canvas.setBitmap(img);

        mBitmap = img;
        mCanvas = canvas;

        displayWidth = (float) width;
        displayHeight = (float) height;

        displayCenterX = width / 2;
        displayCenterY = height / 2;
    }

    public void drawBackground(Canvas canvas) {
        Log.d(TAG, "drawBackground() 호출 : " + canvas);

        if (canvas != null) {
            canvas.drawColor(Color.BLACK);
        }
    }

//     뷰가 화면에 그려지는 메소드 안에서 메모리 상의 비트맵 객체 그리기
    protected void onDraw(Canvas canvas) {
        Log.d(TAG, "onDraw() 호출 : " + canvas);

        if (mBitmap != null) {
            canvas.drawBitmap(mBitmap, 0, 0, null);
        }
    }

    public void setImageData(Bitmap image) {
        Log.d(TAG, "setImageData() 호출 : " + image);

        recycle();

        sourceBitmap = image;

        sourceWidth = sourceBitmap.getWidth();
        sourceHeight = sourceBitmap.getHeight();

        bitmapCenterX = sourceBitmap.getWidth() / 2;
        bitmapCenterY = sourceBitmap.getHeight() / 2;

        scaleRatio = 1.0F;
        totalScaleRatio = 1.0F;
    }

    public void recycle() {
        Log.d(TAG, "recycle() 호출");

        if (sourceBitmap != null) {
            sourceBitmap.recycle();
        }
    }

    public void redraw() {
        Log.d(TAG, "redraw() 호출");

        if (sourceBitmap == null) {
            Log.d(TAG, "sourceBitmap is null in redraw().");
            return;
        }

        drawBackground(mCanvas);

        float originX = (displayWidth - (float) sourceBitmap.getWidth()) / 2.0F;
        float originY = (displayHeight - (float) sourceBitmap.getHeight()) / 2.0F;

        mCanvas.translate(originX, originY);
        mCanvas.drawBitmap(sourceBitmap, mMatrix, mPaint);
        mCanvas.translate(-originX, -originY);

        invalidate();
    }


//     뷰를 터치할 때 호출되는 메소드 다시 정의
    public boolean onTouch(View v, MotionEvent ev) {
        Log.d(TAG, "onTouch() 호출 : " + v + ", " + ev);

        final int action = ev.getAction();


        int pointerCount = ev.getPointerCount();
        Log.d(TAG, "Pointer Count : " + pointerCount);

        switch (action) {
            case MotionEvent.ACTION_DOWN:

                if (pointerCount == 1) {
                    float curX = ev.getX();
                    float curY = ev.getY();

                    startX = curX;
                    startY = curY;

                } else if (pointerCount == 2) {
                    oldDistance = 0.0F;

                    isScrolling = true;
                }

                return true;

            case MotionEvent.ACTION_MOVE:

                if (pointerCount == 1) {

                    if (isScrolling) {
                        return true;
                    }

                    float curX = ev.getX();
                    float curY = ev.getY();

                    if (startX == 0.0F) {
                        startX = curX;
                        startY = curY;

                        return true;
                    }

                    float offsetX = startX - curX;
                    float offsetY = startY - curY;

                    if (oldPointerCount == 2) {

                    } else {
                        Log.d(TAG, "ACTION_MOVE : " + offsetX + ", " + offsetY);

                        if (totalScaleRatio > 1.0F) {
//                            한 손가락으로 움직일때 moveImage 메소드 호출
                            moveImage(-offsetX, -offsetY);
                        }

                        startX = curX;
                        startY = curY;
                    }

                } else if (pointerCount == 2) {

                    float x1 = ev.getX(0);
                    float y1 = ev.getY(0);
                    float x2 = ev.getX(1);
                    float y2 = ev.getY(1);

                    float dx = x1 - x2;
                    float dy = y1 - y2;
                    float distance = new Double(Math.sqrt(new Float(dx * dx + dy * dy).doubleValue())).floatValue();

                    float outScaleRatio = 0.0F;
                    if (oldDistance == 0.0F) {
                        oldDistance = distance;

                        break;
                    }

                    if (distance > oldDistance) {
                        if ((distance - oldDistance) < distanceThreshold) {
                            return true;
                        }

                        outScaleRatio = scaleRatio + (oldDistance / distance * 0.05F);
                    } else if (distance < oldDistance) {
                        if ((oldDistance - distance) < distanceThreshold) {
                            return true;
                        }

                        outScaleRatio = scaleRatio - (distance / oldDistance * 0.05F);
                    }

                    if (outScaleRatio < MIN_SCALE_RATIO || outScaleRatio > MAX_SCALE_RATIO) {
                        Log.d(TAG, "Invalid scaleRatio : " + outScaleRatio);
                    } else {
                        Log.d(TAG, "Distance : " + distance + ", ScaleRatio : " + outScaleRatio);
//                        두 손가락으로 움직이고 있을 때 scaleImage 메소드 호출
                        scaleImage(outScaleRatio);
                    }

                    oldDistance = distance;
                }

                oldPointerCount = pointerCount;

                break;

//                손가락을 떼었을 때
            case MotionEvent.ACTION_UP:

                if (pointerCount == 1) {

                    float curX = ev.getX();
                    float curY = ev.getY();

                    float offsetX = startX - curX;
                    float offsetY = startY - curY;

                    if (oldPointerCount == 2) {

                    } else {
                        moveImage(-offsetX, -offsetY);
                    }

                } else {
                    isScrolling = false;
                }

                return true;
        }

        return true;
    }

    private void scaleImage(float inScaleRatio) {
        Log.d(TAG, "scaleImage() called : " + inScaleRatio);

        mMatrix.postScale(inScaleRatio, inScaleRatio, bitmapCenterX, bitmapCenterY);
        mMatrix.postRotate(0);

        totalScaleRatio = totalScaleRatio * inScaleRatio;

        redraw();
    }

    private void moveImage(float offsetX, float offsetY) {
        Log.d(TAG, "moveImage() called : " + offsetX + ", " + offsetY);

        mMatrix.postTranslate(offsetX, offsetY);

        redraw();
    }

}

 

 

MainActivity

package com.example.a70_multitouch;

import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.os.Bundle;
import android.widget.LinearLayout;

import androidx.appcompat.app.AppCompatActivity;

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        LinearLayout container = findViewById(R.id.container);

        Resources res = getResources();
        Bitmap bitmap = BitmapFactory.decodeResource(res, R.drawable.beach);

        ImageDisplayView view = new ImageDisplayView(this);
        view.setImageData(bitmap);
        LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(
                LinearLayout.LayoutParams.MATCH_PARENT,
                LinearLayout.LayoutParams.MATCH_PARENT);

        container.addView(view, params);
    }

}

 

 

 

 

반응형

'안드로이드' 카테고리의 다른 글

화면에 카메라 미리보기 넣기  (0) 2021.11.05
카메라로 사진 찍어 저장  (0) 2021.11.04
페인트보드(그림판) 만들기  (0) 2021.11.02
BtimapFactory 클래스  (0) 2021.11.02
비트맵 이미지 사용  (0) 2021.11.02