Масштабирование и панорамирование ImageView Android

Я создал класс масштабирования и панорамирования для ImageView.

функции, которые я пытаюсь создать. - Он кастрюли на один палец прикосновения и движения - Он масштабируется и кастрюли на два пальца касания и движения

по большей части это работает очень хорошо. У этого есть небольшая ошибка, когда я делаю следующее: - Я панорамирую одним пальцем (статус: Нет проблем) - Я положил второй палец, масштабирование и панорамирование (статус: Нет проблем) - Я отпускаю второй палец (статус: Изображение немного подскакивает)

надеялся, что кто-то поможет мне решить эту проблему.

Я думаю, что это должно быть связано с сбросом mLastTouchX и mLastTouchY в "случае MotionEvent.Касания"

любая помощь была бы весьма признательна!

import android.content.Context;
import android.graphics.Canvas;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import android.view.ScaleGestureDetector;
import android.widget.ImageView;

public class MyImageView extends ImageView {

    private static final int INVALID_POINTER_ID = -1;

    private float mPosX;
    private float mPosY;

    private float mLastTouchX;
    private float mLastTouchY;
    private float mLastGestureX;
    private float mLastGestureY;
    private int mActivePointerId = INVALID_POINTER_ID;

    private ScaleGestureDetector mScaleDetector;
    private float mScaleFactor = 1.f;

    public MyImageView(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
        mScaleDetector = new ScaleGestureDetector(getContext(), new ScaleListener());
    }

    public MyImageView(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
        mScaleDetector = new ScaleGestureDetector(context, new ScaleListener());
    }

    @Override
    public boolean onTouchEvent(MotionEvent ev) {
        // Let the ScaleGestureDetector inspect all events.
        mScaleDetector.onTouchEvent(ev);

        final int action = ev.getAction();
        switch (action & MotionEvent.ACTION_MASK) {
            case MotionEvent.ACTION_DOWN: {
                if (!mScaleDetector.isInProgress()) {
                    final float x = ev.getX();
                    final float y = ev.getY();

                    mLastTouchX = x;
                    mLastTouchY = y;
                    mActivePointerId = ev.getPointerId(0);
                }
                break;
            }
            case MotionEvent.ACTION_POINTER_1_DOWN: {
                if (mScaleDetector.isInProgress()) {
                    final float gx = mScaleDetector.getFocusX();
                    final float gy = mScaleDetector.getFocusY();
                    mLastGestureX = gx;
                    mLastGestureY = gy;
                }
                break;
            }
            case MotionEvent.ACTION_MOVE: {

                // Only move if the ScaleGestureDetector isn't processing a gesture.
                if (!mScaleDetector.isInProgress()) {
                    final int pointerIndex = ev.findPointerIndex(mActivePointerId);
                    final float x = ev.getX(pointerIndex);
                    final float y = ev.getY(pointerIndex);

                    final float dx = x - mLastTouchX;
                    final float dy = y - mLastTouchY;

                    mPosX += dx;
                    mPosY += dy;

                    invalidate();

                    mLastTouchX = x;
                    mLastTouchY = y;
                }
                else{
                    final float gx = mScaleDetector.getFocusX();
                    final float gy = mScaleDetector.getFocusY();

                    final float gdx = gx - mLastGestureX;
                    final float gdy = gy - mLastGestureY;

                    mPosX += gdx;
                    mPosY += gdy;

                    invalidate();

                    mLastGestureX = gx;
                    mLastGestureY = gy;
                }

                break;
            }
            case MotionEvent.ACTION_UP: {
                mActivePointerId = INVALID_POINTER_ID;
                break;
            }
            case MotionEvent.ACTION_CANCEL: {
                mActivePointerId = INVALID_POINTER_ID;
                break;
            }
            case MotionEvent.ACTION_POINTER_UP: {
                final int pointerIndex = (ev.getAction() & MotionEvent.ACTION_POINTER_INDEX_MASK) 
                        >> MotionEvent.ACTION_POINTER_INDEX_SHIFT;
                final int pointerId = ev.getPointerId(pointerIndex);
                if (pointerId == mActivePointerId) {
                    Log.d("DEBUG", "mActivePointerId");
                    // This was our active pointer going up. Choose a new
                    // active pointer and adjust accordingly.
                    final int newPointerIndex = pointerIndex == 0 ? 1 : 0;
                    mLastTouchX = ev.getX(newPointerIndex);
                    mLastTouchY = ev.getY(newPointerIndex);
                    mActivePointerId = ev.getPointerId(newPointerIndex);
                }

                break;
            }
        }

        return true;
    }

    @Override
    public void onDraw(Canvas canvas) {

        canvas.save();

        canvas.translate(mPosX, mPosY);

        if (mScaleDetector.isInProgress()) {
            canvas.scale(mScaleFactor, mScaleFactor, mScaleDetector.getFocusX(), mScaleDetector.getFocusY());
        }
        else{
            canvas.scale(mScaleFactor, mScaleFactor);
        }
        super.onDraw(canvas);
        canvas.restore();
    }

    private class ScaleListener extends ScaleGestureDetector.SimpleOnScaleGestureListener {
        @Override
        public boolean onScale(ScaleGestureDetector detector) {
            mScaleFactor *= detector.getScaleFactor();

            // Don't let the object get too small or too large.
            mScaleFactor = Math.max(0.1f, Math.min(mScaleFactor, 10.0f));

            invalidate();
            return true;
        }
    }
}

4 ответов


похоже на холст.масштаб () в заявлении "else" метода "onDraw" требовал, чтобы mLastGestureX и mLastGestureY прекратили прыжки. Я также обновляю mLastTouchX и mLastTouchY, возвращаясь к панорамированию одним пальцем в случае MotionEvent.Касания'

вот финал, может не устраивать всех, потому что он не ограничивает панорамирование прошлых границ изображения, но это должно быть легко выполнить, есть много обсуждений на этом тема.

import android.content.Context;
import android.graphics.Canvas;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.ScaleGestureDetector;
import android.widget.ImageView;

public class MyImageView extends ImageView {

    private static final int INVALID_POINTER_ID = -1;

    private float mPosX;
    private float mPosY;

    private float mLastTouchX;
    private float mLastTouchY;
    private float mLastGestureX;
    private float mLastGestureY;
    private int mActivePointerId = INVALID_POINTER_ID;

    private ScaleGestureDetector mScaleDetector;
    private float mScaleFactor = 1.f;

    public MyImageView(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
        mScaleDetector = new ScaleGestureDetector(getContext(), new ScaleListener());
    }

    public MyImageView(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
        mScaleDetector = new ScaleGestureDetector(context, new ScaleListener());
    }

    @Override
    public boolean onTouchEvent(MotionEvent ev) {
        // Let the ScaleGestureDetector inspect all events.
        mScaleDetector.onTouchEvent(ev);

        final int action = ev.getAction();
        switch (action & MotionEvent.ACTION_MASK) {
            case MotionEvent.ACTION_DOWN: {
                if (!mScaleDetector.isInProgress()) {
                    final float x = ev.getX();
                    final float y = ev.getY();

                    mLastTouchX = x;
                    mLastTouchY = y;
                    mActivePointerId = ev.getPointerId(0);
                }
                break;
            }
            case MotionEvent.ACTION_POINTER_1_DOWN: {
                if (mScaleDetector.isInProgress()) {
                    final float gx = mScaleDetector.getFocusX();
                    final float gy = mScaleDetector.getFocusY();
                    mLastGestureX = gx;
                    mLastGestureY = gy;
                }
                break;
            }
            case MotionEvent.ACTION_MOVE: {

                // Only move if the ScaleGestureDetector isn't processing a gesture.
                if (!mScaleDetector.isInProgress()) {
                    final int pointerIndex = ev.findPointerIndex(mActivePointerId);
                    final float x = ev.getX(pointerIndex);
                    final float y = ev.getY(pointerIndex);

                    final float dx = x - mLastTouchX;
                    final float dy = y - mLastTouchY;

                    mPosX += dx;
                    mPosY += dy;

                    invalidate();

                    mLastTouchX = x;
                    mLastTouchY = y;
                }
                else{
                    final float gx = mScaleDetector.getFocusX();
                    final float gy = mScaleDetector.getFocusY();

                    final float gdx = gx - mLastGestureX;
                    final float gdy = gy - mLastGestureY;

                    mPosX += gdx;
                    mPosY += gdy;

                    invalidate();

                    mLastGestureX = gx;
                    mLastGestureY = gy;
                }

                break;
            }
            case MotionEvent.ACTION_UP: {
                mActivePointerId = INVALID_POINTER_ID;
                break;
            }
            case MotionEvent.ACTION_CANCEL: {
                mActivePointerId = INVALID_POINTER_ID;
                break;
            }
            case MotionEvent.ACTION_POINTER_UP: {

                final int pointerIndex = (ev.getAction() & MotionEvent.ACTION_POINTER_INDEX_MASK) 
                        >> MotionEvent.ACTION_POINTER_INDEX_SHIFT;
                final int pointerId = ev.getPointerId(pointerIndex);
                if (pointerId == mActivePointerId) {
                    // This was our active pointer going up. Choose a new
                    // active pointer and adjust accordingly.
                    final int newPointerIndex = pointerIndex == 0 ? 1 : 0;
                    mLastTouchX = ev.getX(newPointerIndex);
                    mLastTouchY = ev.getY(newPointerIndex);
                    mActivePointerId = ev.getPointerId(newPointerIndex);
                }
                else{
                    final int tempPointerIndex = ev.findPointerIndex(mActivePointerId);
                    mLastTouchX = ev.getX(tempPointerIndex);
                    mLastTouchY = ev.getY(tempPointerIndex);
                }

                break;
            }
        }

        return true;
    }

    @Override
    public void onDraw(Canvas canvas) {

        canvas.save();

        canvas.translate(mPosX, mPosY);

        if (mScaleDetector.isInProgress()) {
            canvas.scale(mScaleFactor, mScaleFactor, mScaleDetector.getFocusX(), mScaleDetector.getFocusY());
        }
        else{
            canvas.scale(mScaleFactor, mScaleFactor, mLastGestureX, mLastGestureY);
        }
        super.onDraw(canvas);
        canvas.restore();
    }

    private class ScaleListener extends ScaleGestureDetector.SimpleOnScaleGestureListener {
        @Override
        public boolean onScale(ScaleGestureDetector detector) {
            mScaleFactor *= detector.getScaleFactor();

            // Don't let the object get too small or too large.
            mScaleFactor = Math.max(0.1f, Math.min(mScaleFactor, 10.0f));

            invalidate();
            return true;
        }
    }
}

я играл с решением этой проблемы в течение недели, и это дает мне массу проблем. Однако я значительно сузил круг вопросов. Ваше решение выше не сработало для меня, но мое решение ниже близко. Проблема в том, что он прыгает всякий раз, когда второй палец нажимается или поднимается. Я обнаружил, что это происходит потому, что mPosX и mPosY не всегда действительно являются переменными должно представлять. Вот что я имею в виду:

когда Вызывается ACTION_MOVE и код вводит оператор" else " (для обработки событий масштабирования), mPosX и mPosY меняются только в соответствии с смена фокуса, не на изменение масштабирования. Это означает, что панорамирование двумя пальцами работает, а масштабирование двумя пальцами работает, но mPosX и mPosY не меняются должным образом в отношении изменения масштаба.

Я пытался выяснить, как это исправить, используя дифференциальные изменения in zoom (mScaleDetector.getScaleFactor ()) и дифференциальные изменения в фокусе, но я не могу работать с логикой достаточно хорошо, чтобы найти что-то, что работает.

другое решение-переместить все операции масштабирования в OnTouchListener и полностью избавиться от ScaleListener. Это означает гораздо больше математики, но это определенно было бы решением.

вот onDraw:

    @Override
    public void onDraw(Canvas c) {
        c.save();

        if (mScaleDetector.isInProgress()) {
            c.scale(mScaleFactor, mScaleFactor, mLastGestureX - mPosX,
                    mLastGestureY - mPosY);
        } else {
            c.scale(mScaleFactor, mScaleFactor, mLastGestureX, mLastGestureY);
        }

        c.translate(mPosX / mScaleFactor, mPosY / mScaleFactor);

        // drawing instruction here

        c.restore();
    }

вот как код реагирует на палец прессы:

    @Override
    public boolean onTouchEvent(MotionEvent ev) {
        mScaleDetector.onTouchEvent(ev);

        final int action = ev.getAction();
        switch (action & MotionEvent.ACTION_MASK) {
        case MotionEvent.ACTION_DOWN: {
            if (!mScaleDetector.isInProgress()) {
                final float x = ev.getX();
                final float y = ev.getY();

                mLastTouchX = x;
                mLastTouchY = y;

                mActivePointerId = ev.getPointerId(0);
            }
            break;
        }

        case MotionEvent.ACTION_POINTER_DOWN: {
            if (!mScaleDetector.isInProgress()) {
                final float gx = mScaleDetector.getFocusX();
                final float gy = mScaleDetector.getFocusY();

                mLastGestureX = gx;
                mLastGestureY = gy; 
            }
            break;
        }

        case MotionEvent.ACTION_MOVE: {
            if (!mScaleDetector.isInProgress()) {
                Log.i("hi", "SD not in progress");
                final int pointerIndex = ev.findPointerIndex(mActivePointerId);
                final float x = ev.getX(pointerIndex);
                final float y = ev.getY(pointerIndex);

                final float dx = x - mLastTouchX;
                final float dy = y - mLastTouchY;

                mPosX += dx;
                mPosY += dy;

                invalidate();

                mLastTouchX = x;
                mLastTouchY = y;
            } else {
                Log.i("hi", "SD in progress");
                final float gx = mScaleDetector.getFocusX();
                final float gy = mScaleDetector.getFocusY();

                final float gdx = gx - mLastGestureX;
                final float gdy = gy - mLastGestureY;

                mPosX += gdx;
                mPosY += gdy;

                // SOMETHING NEEDS TO HAPPEN RIGHT HERE.

                invalidate();

                mLastGestureX = gx;
                mLastGestureY = gy;
            }

            break;
        }

        case MotionEvent.ACTION_POINTER_UP: {

            final int pointerIndex = (ev.getAction() & MotionEvent.ACTION_POINTER_INDEX_MASK) >> MotionEvent.ACTION_POINTER_INDEX_SHIFT;
            final int pointerId = ev.getPointerId(pointerIndex);
            if (pointerId == mActivePointerId) {
                // This was our active pointer going up. Choose a new
                // active pointer and adjust accordingly.
                final int newPointerIndex = pointerIndex == 0 ? 1 : 0;

                mLastTouchX = ev.getX(newPointerIndex);
                mLastTouchY = ev.getY(newPointerIndex);

                mActivePointerId = ev.getPointerId(newPointerIndex);
            } else {
                final int tempPointerIndex = ev.findPointerIndex(mActivePointerId);

                mLastTouchX = ev.getX(tempPointerIndex);
                mLastTouchY = ev.getY(tempPointerIndex);
            }

            break;
        }
        }

        return true;
    }

и хотя это в основном не связано, вот ScaleListener:

    private class ScaleListener extends ScaleGestureDetector.SimpleOnScaleGestureListener {
        @Override
        public boolean onScale(ScaleGestureDetector detector) {
            mScaleFactor *= detector.getScaleFactor();

            invalidate();
            return true;
        }
    }

опять же, этот код работает не идеально, но он очень близок. Я объяснил точную проблему выше, и у меня все еще есть проблемы с ее работой. Я не знаю, появится ли это в ваших уведомлениях, Хэнк, но надеюсь, кто-то увидит это и поможет мне.


решение Хэнка работает для меня. Я добавил функцию сброса, чтобы последующие изображения могли отображаться нормально.

public void ResetView() {
        mScaleFactor = 1.f;
        mPosX = 0.f;
        mPosY = 0.f;
    }

без глядя на код, я бы предположил, что вы делаете расчет позиции на основе 2 пальцев, когда есть 2 пальца. В этом случае ты всегда сможешь прыгнуть.