Как я могу увеличить ImageView, не подскакивая немного в другом месте? (Android) - PullRequest
3 голосов
/ 02 ноября 2019

Я пытаюсь создать приложение, в котором пользователь может перетаскивать и масштабировать ImageView. Но у меня возникают проблемы со следующим кодом:

Когда scaleFactor не равен 1 и второй палец опускается, он переводится немного куда-то еще. Я не знаю, откуда взялся этот перевод ...

Вот полный класс:

package me.miutaltbati.ramaview;

import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Matrix;
import android.graphics.Paint;
import android.graphics.PointF;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.ScaleGestureDetector;
import android.widget.ImageView;

import static android.view.MotionEvent.INVALID_POINTER_ID;

public class RamaView extends ImageView {
    private Context context;
    private Matrix matrix = new Matrix();
    private Matrix translateMatrix = new Matrix();
    private Matrix scaleMatrix = new Matrix();

    // Properties coming from outside:
    private int drawableLayoutId;
    private int width;
    private int height;

    private static float MIN_ZOOM = 0.33333F;
    private static float MAX_ZOOM = 5F;

    private PointF mLastTouch = new PointF(0, 0);
    private PointF mLastFocus = new PointF(0, 0);
    private PointF mLastPivot = new PointF(0, 0);
    private float mPosX = 0F;
    private float mPosY = 0F;

    public float scaleFactor = 1F;
    private int mActivePointerId = INVALID_POINTER_ID;

    private Paint paint;
    private Bitmap bitmapLayout;

    private OnFactorChangedListener mListener;
    private ScaleGestureDetector mScaleDetector;

    public RamaView(Context context) {
        super(context);
        initializeInConstructor(context);
    }

    public RamaView(Context context, @android.support.annotation.Nullable AttributeSet attrs) {
        super(context, attrs);
        initializeInConstructor(context);
    }

    public RamaView(Context context, @android.support.annotation.Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        initializeInConstructor(context);
    }

    public void initializeInConstructor(Context context) {
        this.context = context;
        paint = new Paint();
        mScaleDetector = new ScaleGestureDetector(context, new ScaleListener());
        mScaleDetector.setQuickScaleEnabled(false);

        setScaleType(ScaleType.MATRIX);
    }

    public Bitmap decodeSampledBitmap() {
        // First decode with inJustDecodeBounds=true to check dimensions
        final BitmapFactory.Options options = new BitmapFactory.Options();
        options.inMutable = true;
        options.inJustDecodeBounds = true;
        BitmapFactory.decodeResource(getResources(), drawableLayoutId, options);

        // Calculate inSampleSize
        options.inSampleSize = Util.calculateInSampleSize(options, width, height); // e.g.: 4, 8

        // Decode bitmap with inSampleSize set
        options.inJustDecodeBounds = false;
        return BitmapFactory.decodeResource(getResources(), drawableLayoutId, options);
    }

    public void setDrawable(int drawableId) {
        drawableLayoutId = drawableId;
    }

    public void setSize(int width, int height) {
        this.width = width;
        this.height = height;

        bitmapLayout = decodeSampledBitmap();
    }

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

        int action = event.getActionMasked();

        switch (action) {
            case MotionEvent.ACTION_DOWN: {
                int pointerIndex = event.getActionIndex();
                float x = event.getX(pointerIndex);
                float y = event.getY(pointerIndex);

                // Remember where we started (for dragging)
                mLastTouch = new PointF(x, y);

                // Save the ID of this pointer (for dragging)
                mActivePointerId = event.getPointerId(0);
            }

            case MotionEvent.ACTION_POINTER_DOWN: {
                if (event.getPointerCount() == 2) {
                    mLastFocus = new PointF(mScaleDetector.getFocusX(), mScaleDetector.getFocusY());
                }
            }

            case MotionEvent.ACTION_MOVE: {
                // Find the index of the active pointer and fetch its position
                int pointerIndex = event.findPointerIndex(mActivePointerId);

                float x = event.getX(pointerIndex);
                float y = event.getY(pointerIndex);

                // Calculate the distance moved
                float dx = 0;
                float dy = 0;

                if (event.getPointerCount() == 1) {
                    // Calculate the distance moved
                    dx = x - mLastTouch.x;
                    dy = y - mLastTouch.y;

                    matrix.setScale(scaleFactor, scaleFactor, mLastPivot.x, mLastPivot.y);

                    // Remember this touch position for the next move event
                    mLastTouch = new PointF(x, y);
                } else if (event.getPointerCount() == 2) {
                    // Calculate the distance moved
                    dx = mScaleDetector.getFocusX() - mLastFocus.x;
                    dy = mScaleDetector.getFocusY() - mLastFocus.y;

                    matrix.setScale(scaleFactor, scaleFactor, -mPosX + mScaleDetector.getFocusX(), -mPosY + mScaleDetector.getFocusY());

                    mLastPivot = new PointF(-mPosX + mScaleDetector.getFocusX(), -mPosY + mScaleDetector.getFocusY());
                    mLastFocus = new PointF(mScaleDetector.getFocusX(), mScaleDetector.getFocusY());
                }

                mPosX += dx;
                mPosY += dy;

                matrix.postTranslate(mPosX, mPosY);

                break;
            }

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

            case MotionEvent.ACTION_POINTER_UP: {
                final int pointerIndex = event.getActionIndex();
                final int pointerId = event.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;
                    mLastTouch = new PointF(event.getX(newPointerIndex), event.getY(newPointerIndex));
                    mActivePointerId = event.getPointerId(newPointerIndex);
                } else {
                    final int tempPointerIndex = event.findPointerIndex(mActivePointerId);
                    mLastTouch = new PointF(event.getX(tempPointerIndex), event.getY(tempPointerIndex));
                }

                break;
            }
        }

        invalidate();
        return true;
    }

    @Override
    protected void onDraw(Canvas canvas) {
        canvas.save();

        canvas.setMatrix(matrix);
        canvas.drawColor(Color.BLACK);
        canvas.drawBitmap(bitmapLayout, 0, 0, paint);

        canvas.restore();
    }

    private class ScaleListener extends ScaleGestureDetector.SimpleOnScaleGestureListener {
        @Override
        public boolean onScale(ScaleGestureDetector detector) {
            scaleFactor *= detector.getScaleFactor();
            scaleFactor = Math.max(MIN_ZOOM, Math.min(scaleFactor, MAX_ZOOM));

            return true;
        }
    }
}

Я думаю, что проблема в этой строке:

matrix.setScale(scaleFactor, scaleFactor, -mPosX + mScaleDetector.getFocusX(), -mPosY + mScaleDetector.getFocusY());

Я много чего пробовал, но не смог заставить его работать должным образом.

ОБНОВЛЕНИЕ:

Вот как вы можете запустить экземпляр RamaView:

Основная деятельность по созданию:

rvRamaView = findViewById(R.id.rvRamaView);

final int[] rvSize = new int[2];
ViewTreeObserver vto = rvRamaView.getViewTreeObserver();
vto.addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() {
    @Override
    public boolean onPreDraw() {
        rvRamaView.getViewTreeObserver().removeOnPreDrawListener(this);
        rvSize[0] = rvRamaView.getMeasuredWidth();
        rvSize[1] = rvRamaView.getMeasuredHeight();

        rvRamaView.setSize(rvSize[0], rvSize[1]);
        return true;
    }
});

rvRamaView.setDrawable(R.drawable.original_jpg);

Ответы [ 2 ]

0 голосов
/ 10 ноября 2019

Было бы лучше использовать матрицу для накопления изменений, а не пытаться пересчитать преобразования самостоятельно. Вы можете сделать это с помощью матриц post ... и pre ... и избежать методов set ... , которые сбрасывают матрицу.

Здесь доработка класса RamaView , который в значительной степени был целью, за исключением специальной обработки матрицы, как отмечено выше. Моды для метода onTouchEvent () . Видео выводится из кода, работающего в примере приложения.

enter image description here

RamaView.java

public class RamaView extends ImageView {
    private final Matrix matrix = new Matrix();

    // Properties coming from outside:
    private int drawableLayoutId;
    private int width;
    private int height;

    private static final float MIN_ZOOM = 0.33333F;
    private static final float MAX_ZOOM = 5F;

    private PointF mLastTouch = new PointF(0, 0);
    private PointF mLastFocus = new PointF(0, 0);

    public float scaleFactor = 1F;
    private int mActivePointerId = INVALID_POINTER_ID;

    private Paint paint;
    private Bitmap bitmapLayout;

    //    private OnFactorChangedListener mListener;
    private ScaleGestureDetector mScaleDetector;

    public RamaView(Context context) {
        super(context);
        initializeInConstructor(context);
    }

    public RamaView(Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
        initializeInConstructor(context);
    }

    public RamaView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        initializeInConstructor(context);
    }

    public void initializeInConstructor(Context context) {
        paint = new Paint();
        mScaleDetector = new ScaleGestureDetector(context, new ScaleListener());
        mScaleDetector.setQuickScaleEnabled(false);

        setScaleType(ScaleType.MATRIX);
    }

    public Bitmap decodeSampledBitmap() {
        // First decode with inJustDecodeBounds=true to check dimensions
        final BitmapFactory.Options options = new BitmapFactory.Options();
        options.inMutable = true;
        options.inJustDecodeBounds = true;
        BitmapFactory.decodeResource(getResources(), drawableLayoutId, options);

        options.inSampleSize = Util.calculateInSampleSize(options, width, height); // e.g.: 4, 8

        // Decode bitmap with inSampleSize set
        options.inJustDecodeBounds = false;
        return BitmapFactory.decodeResource(getResources(), drawableLayoutId, options);
    }

    public void setDrawable(int drawableId) {
        drawableLayoutId = drawableId;
    }

    public void setSize(int width, int height) {
        this.width = width;
        this.height = height;

        bitmapLayout = decodeSampledBitmap();
    }

    private float mLastScaleFactor = 1.0f;

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

        int action = event.getActionMasked();

        switch (action) {
            case MotionEvent.ACTION_DOWN: {
                int pointerIndex = event.getActionIndex();
                float x = event.getX(pointerIndex);
                float y = event.getY(pointerIndex);

                // Remember where we started (for dragging)
                mLastTouch = new PointF(x, y);

                // Save the ID of this pointer (for dragging)
                mActivePointerId = event.getPointerId(0);
            }

            case MotionEvent.ACTION_POINTER_DOWN: {
                if (event.getPointerCount() == 2) {
                    mLastFocus = new PointF(mScaleDetector.getFocusX(), mScaleDetector.getFocusY());
                }
            }

            case MotionEvent.ACTION_MOVE: {
                // Find the index of the active pointer and fetch its position
                int pointerIndex = event.findPointerIndex(mActivePointerId);

                float x = event.getX(pointerIndex);
                float y = event.getY(pointerIndex);

                // Calculate the distance moved
                float dx = 0;
                float dy = 0;

                if (event.getPointerCount() == 1) {
                    // Calculate the distance moved
                    dx = x - mLastTouch.x;
                    dy = y - mLastTouch.y;

                    // Remember this touch position for the next move event
                    mLastTouch = new PointF(x, y);
                } else if (event.getPointerCount() == 2) {
                    // Calculate the distance moved
                    float focusX = mScaleDetector.getFocusX();
                    float focusY = mScaleDetector.getFocusY();
                    dx = focusX - mLastFocus.x;
                    dy = focusY - mLastFocus.y;

                    // Since we are accumating translation/scaling, we are just adding to
                    // the previous scale.
                    matrix.postScale(scaleFactor/mLastScaleFactor, scaleFactor/mLastScaleFactor, focusX, focusY);
                    mLastScaleFactor = scaleFactor;

                    mLastFocus = new PointF(focusX, focusY);
                }

                // Translation is cumulative.
                matrix.postTranslate(dx, dy);

                break;
            }

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

            case MotionEvent.ACTION_POINTER_UP: {
                final int pointerIndex = event.getActionIndex();
                final int pointerId = event.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;
                    mLastTouch = new PointF(event.getX(newPointerIndex), event.getY(newPointerIndex));
                    mActivePointerId = event.getPointerId(newPointerIndex);
                } else {
                    final int tempPointerIndex = event.findPointerIndex(mActivePointerId);
                    mLastTouch = new PointF(event.getX(tempPointerIndex), event.getY(tempPointerIndex));
                }

                break;
            }
        }

        invalidate();
        return true;
    }

    @Override
    protected void onDraw(Canvas canvas) {
        canvas.save();

        canvas.setMatrix(matrix);
        canvas.drawColor(Color.BLACK);
        canvas.drawBitmap(bitmapLayout, 0, 0, paint);

        canvas.restore();
    }

    private class ScaleListener extends ScaleGestureDetector.SimpleOnScaleGestureListener {
        @Override
        public boolean onScale(ScaleGestureDetector detector) {
            scaleFactor *= detector.getScaleFactor();
            scaleFactor = Math.max(MIN_ZOOM, Math.min(scaleFactor, MAX_ZOOM));

            return true;
        }
    }
}
0 голосов
/ 05 ноября 2019

Кажется, что код не полный (например, я не вижу, как используется матрица и где назначен scaleFactor), но я думаю, что причина, по которой перевод не согласован, заключается в том, что в случае двух указателей вы получаете [x, y] от mScaleDetector.getFocus. В документации по ScaleGestureDetector.getFocusX() говорится:

Получите координату X фокальной точки текущего жеста. Если жест выполняется, фокус находится между каждым из указателей, формирующих жест.

Вы должны использовать mScaleDetector только для получения текущего масштаба, но перевод всегда должен рассчитываться какразница между mLastTouch и event.getXY(pointerIndex), так что для перевода рассматривается только один указатель. Если пользователь добавляет второй палец и отпускает первый, обязательно переназначьте pointerIndex и не выполняйте перевод, чтобы избежать прыжка.

...