Реализация масштабирования и перетаскивания с помощью встроенного в Android прослушивателя жестов и прослушивателя масштабирования.

Я пытаюсь реализовать масштабирование и перетаскивание с помощью прослушивателя жестов Android и прослушивателя масштабирования. Проблема в том, что когда я выполняю масштабирование, изображение (которое я пытаюсь увеличить) отскакивает в определенное место. Также положение масштабирования не по центру. Следующий код демонстрирует, чего я пытаюсь достичь. Любая идея, почему изображение прыгает (и как это исправить)?

public class CustomView extends View {
    Bitmap image;
    int screenHeight;
    int screenWidth;
    Paint paint;
    GestureDetector gestures;
    ScaleGestureDetector scaleGesture;
    float scale = 1.0f;
    float horizontalOffset, verticalOffset;

    int NORMAL = 0;
    int ZOOM = 1;
    int DRAG = 2;
    boolean isScaling = false;
    float touchX, touchY;
        int mode = NORMAL;

    public CustomView(Context context) {
    super(context);
            //initializing variables
    image = BitmapFactory.decodeResource(getResources(),
            R.drawable.image_name);
            //This is a full screen view
    screenWidth = getResources().getDisplayMetrics().widthPixels;
    screenHeight = getResources().getDisplayMetrics().heightPixels;
    paint = new Paint();
    paint.setAntiAlias(true);
    paint.setFilterBitmap(true);
    paint.setDither(true);
    paint.setColor(Color.WHITE);

    scaleGesture = new ScaleGestureDetector(getContext(),
            new ScaleListener());
    gestures = new GestureDetector(getContext(), new GestureListener());
    mode = NORMAL;
    initialize();
}

//Best fit image display on canvas 
    private void initialize() {
    float imgPartRatio = image.getWidth() / (float) image.getHeight();
    float screenRatio = (float) screenWidth / (float) screenHeight;

    if (screenRatio > imgPartRatio) {
        scale = ((float) screenHeight) / (float) (image.getHeight()); // fit height
        horizontalOffset = ((float) screenWidth - scale
                * (float) (image.getWidth())) / 2.0f;
        verticalOffset = 0;
    } else {
        scale = ((float) screenWidth) / (float) (image.getWidth()); // fit width
        horizontalOffset = 0;
        verticalOffset = ((float) screenHeight - scale
                * (float) (image.getHeight())) / 2.0f;
    }
        invalidate();
}

@Override
protected void onDraw(Canvas canvas) {
    canvas.save();
    canvas.drawColor(0, Mode.CLEAR);
    canvas.drawColor(Color.WHITE);
    if(mode == DRAG || mode == NORMAL) {
        //This works perfectly as expected
        canvas.translate(horizontalOffset, verticalOffset);
        canvas.scale(scale, scale);
        canvas.drawBitmap(image, getMatrix(), paint);
    }
    else if (mode == ZOOM) {
        //PROBLEM AREA - when applying pinch zoom,
        //the image jumps to a position abruptly
        canvas.scale(scale, scale, touchX, touchY);
        canvas.drawBitmap(image, getMatrix(), paint);
    }
    canvas.restore();
}

public class ScaleListener implements OnScaleGestureListener {
    @Override
    public boolean onScale(ScaleGestureDetector detector) {
        float scaleFactorNew = detector.getScaleFactor();
        if (detector.isInProgress()) {
            touchX = detector.getFocusX();
            touchY = detector.getFocusY();
            scale *= scaleFactorNew;
            invalidate(0, 0, screenWidth, screenHeight);
        }
        return true;
    }

    @Override
    public boolean onScaleBegin(ScaleGestureDetector detector) {
        isScaling = true;
        mode=ZOOM;
        return true;
    }

    @Override
    public void onScaleEnd(ScaleGestureDetector detector) {
        mode = NORMAL;
        isScaling = false;
    }

}

public class GestureListener implements GestureDetector.OnGestureListener,
        GestureDetector.OnDoubleTapListener {

    @Override
    public boolean onDown(MotionEvent e) {
        isScaling = false;
        return true;
    }

    @Override
    public boolean onScroll(MotionEvent e1, MotionEvent e2,
            float distanceX, float distanceY) {
        if (!isScaling) {
            mode = DRAG;
            isScaling = false;
            horizontalOffset -= distanceX;
            verticalOffset -= distanceY;
            invalidate(0, 0, screenWidth, screenHeight);
        } else {
            mode = ZOOM;
            isScaling = true;
        }
        return true;
    }
}

@Override
public boolean onTouchEvent(MotionEvent event) {
    scaleGesture.onTouchEvent(event);
    gestures.onTouchEvent(event);
    return true;
}
}

Заранее спасибо.


person chochim    schedule 17.10.2013    source источник
comment
если вы не используете режим DRAG до того, как вы используете режим ZOOM - это означает, что ZOOM - это первое, что вы делаете перед применением каких-либо переводов, изображение все еще прыгает?   -  person Gil Moshayof    schedule 21.10.2013
comment
Да изображение все равно прыгает.   -  person chochim    schedule 21.10.2013
comment
разве горизонтальное/вертикальное смещение не должно применяться даже при масштабировании? Вы пытались применить их во время масштабирования?   -  person Gil Moshayof    schedule 21.10.2013
comment
@GilMoshayof - Горизонтальное и вертикальное масштабирование также должно применяться во время масштабирования. Но это не имеет значения - изображение все равно прыгает. Кажется, мне нужно возиться с масштабированием опорных точек. Аналогичный вопрос (хотя и с гораздо более чистой реализацией) также задается здесь: stackoverflow.com/questions/19471356/ Согласно ответу, представленному в этом вопросе, необходимо скорректировать опорные точки, вокруг которых мы масштабируем . Я все еще чешу голову напрасно, чтобы правильно вычислить координаты точки поворота.   -  person chochim    schedule 21.10.2013
comment
как насчет того, чтобы попытаться установить touchX / touchY в вашем onScaleBegin? Это что-то меняет?   -  person Gil Moshayof    schedule 21.10.2013
comment
@GilMoshayof - не помогло :(   -  person chochim    schedule 22.10.2013


Ответы (1)


Я реализовал это поведение и использовал матрицу для управления масштабированием и прокруткой (и вращением, в моем случае). Это делает код аккуратным и работает как часы.

Сохраните матрицу как член класса:

Matrix drawMatrix;

Редактировать: сохранить старую точку фокусировки, используемую для смещения фокуса во время масштабирования.

float lastFocusX;
float lastFocusY;

Изменить: установить переменные lastFocus в onScaleBegin.

@Override
public boolean onScaleBegin(ScaleGestureDetector detector) {
    lastFocusX = detector.getFocusX();
    lastFocusY = detector.getFocusY();
    return true;
}

Замените свой onScale:

@Override
public boolean onScale(ScaleGestureDetector detector) {
    Matrix transformationMatrix = new Matrix();
    float focusX = detector.getFocusX();
    float focusY = detector.getFocusY();

    //Zoom focus is where the fingers are centered, 
    transformationMatrix.postTranslate(-focusX, -focusY);

    transformationMatrix.postScale(detector.getScaleFactor(), detector.getScaleFactor());

/* Adding focus shift to allow for scrolling with two pointers down. Remove it to skip this functionality. This could be done in fewer lines, but for clarity I do it this way here */
    //Edited after comment by chochim
    float focusShiftX = focusX - lastFocusX;
    float focusShiftY = focusY - lastFocusY;
    transformationMatrix.postTranslate(focusX + focusShiftX, focusY + focusShiftY);
    drawMatrix.postConcat(transformationMatrix);
    lastFocusX = focusX;
    lastFocusY = focusY;
    invalidate();
    return true;
}

Аналогично в onScroll:

@Override
public boolean onScroll(MotionEvent downEvent, MotionEvent currentEvent,
            float distanceX, float distanceY) {
    drawMatrix.postTranslate(-distanceX, -distanceY);
    invalidate();
    return true;
}

в onDraw; Нарисуйте с помощью drawMatrix:

canvas.drawBitmap(image, drawMatrix, paint);

Удачного кодирования!

person Tore Rudberg    schedule 23.10.2013
comment
Яркое, элегантное решение. Что такое детектор.getFocusShiftX()? - person chochim; 23.10.2013
comment
Ах, извините. Когда я реализовал это, я сделал свой собственный детектор жестов, чтобы также получать информацию о вращении. getFocusShift — это служебный метод, который явно недоступен в ScaleGestureDetector. Я отредактирую свой ответ для полноты - person Tore Rudberg; 24.10.2013
comment
Вам не нужно использовать focusShift, если вы уже переводите матрицу в onScroll. Для правильной работы измените transformationMatrix.postTranslate(focusX + focusShiftX), focusY + focusShiftY); на transformationMatrix.postTranslate(focusX, focusY); - person David Novák; 22.02.2015
comment
@DavidNovák: Если жест распознается как масштаб, вы не будете получать обновления прокрутки для этого же жеста, мой код позволяет пользователю прокручивать во время события масштабирования. Если вы не хотите такого поведения, то, во что бы то ни стало, делайте это по-своему. - person Tore Rudberg; 23.02.2015