Как я могу наложить ограничения на матрицу перевода моего холста? - PullRequest
8 голосов
/ 21 июля 2011

Итак, я расширяю пользовательский SurfaceView и пытаюсь сделать так, чтобы он имел возможность масштабирования и прокрутки.

Как прокручивать:

    @Override
    public boolean onScroll(MotionEvent e1, MotionEvent e2,
            float distanceX, float distanceY) {

        // subtract scrolled amount
        matrix.postTranslate(-distanceX, -distanceY);

        rebound();

        invalidate();

        // handled
        return true;
    }

Как увеличить:

    @Override
    public boolean onScale(ScaleGestureDetector detector) {
        if (detector.isInProgress()) {
            // get scale 
            float factor = detector.getScaleFactor();

            // Don't let the object get too small or too large.
            if (factor * scale > 1f) {
                factor = 1f / scale;
            } else if (factor * scale < minScale) {
                factor = minScale / scale;
            }

            // store local scale
            scale *= factor;

            // do the scale
            matrix.preScale(factor, factor, detector.getFocusX(), detector.getFocusY());

            rebound();

            invalidate();
        }

        return true;
    }

(для справки я использую этот код для onDraw:)

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

    canvas.save();
    canvas.setMatrix(matrix);
    // [...] stuff drawn with matrix settings
    canvas.restore();
    // [...] stuff drawn without matrix settings, as an overlay
}

В настоящее время оба эти метода работают хорошо.Масштабирование останавливается при минимальном (между 0f и 1f) и максимальном (в настоящее время всегда 1f) значении масштаба.

Используемые глобальные поля:

  • drawW, drawH= float, размер в пикселях данных холста в масштабе = 1.
  • parentW, parentH = float, размер в пикселях видимого вида.
  • matrix = android.graphics.Matrix.

Проблема в том, что метод * rebound() (возможно, нуждается в более подходящем имени, хе), который я пытаюсь реализовать, будет автоматически вызыватьсодержимое, чтобы оставаться в поле зрения.

Я пробовал различные методы, чтобы попытаться вычислить, где должны быть границы (в пределах прямоугольника (0, 0, parentW, parentH)) и перевести матрицу «назад», когда оназаходит слишком далеко.

Вот то, что у меня есть в настоящее время, но оно определенно не работает, вместо этого я много толкаю его вправо.Я чувствую, что проблема в моей математике, а не в моей идее. Может ли кто-нибудь придумать более простой или понятный код, который переводит матрицу на край, если она слишком далеко, и / или исправляет проблемы с моей попыткой этой реализации? Проверки if, использованные для ее появления, появляются вверхний левый

public void rebound() {
    // bounds
    RectF currentBounds = new RectF(0, 0, drawW, drawH);
    matrix.mapRect(currentBounds);
    RectF parentBounds = new RectF(0, 0, parentW, parentH/2);

    PointF diff = new PointF(0, 0);

    if (currentBounds.left > parentBounds.left) {
        diff.x += (parentBounds.left - currentBounds.left);
    }
    if (currentBounds.top > parentBounds.top) {
        diff.y += (parentBounds.top - currentBounds.top);
    }
    if (currentBounds.width() > parentBounds.width()) {
        if (currentBounds.right < parentBounds.right) {
            diff.x += (parentBounds.right - currentBounds.right);
        }
        if (currentBounds.bottom < parentBounds.bottom) {
            diff.y += (parentBounds.bottom - currentBounds.bottom);
        }
    }

    matrix.postTranslate(diff.x, diff.y);
}

Предыдущая версия, которую я написал до матрицы, была полем (я просто использовал canvas.scale(), затем canvas.translate() в onDraw), который работал:

public void rebound() {
    // bounds
    int boundTop = 0;
    int boundLeft = 0;
    int boundRight = (int)(-scale * drawW + parentW);
    int boundBottom = (int)(-scale * drawH + parentH);

    if (boundLeft >= boundRight) {
        mScrollX = Math.min(boundLeft, Math.max(boundRight, mScrollX));
    } else {
        mScrollX = 0;
    }
    if (boundTop >= boundBottom) {
        mScrollY = Math.min(boundTop, Math.max(boundBottom, mScrollY));
    } else {
        mScrollY = 0;
    }
}

Я использую новый способ, чтобы можно было правильно масштабировать по центру detector.getFocusX(), detector.getFocusY().

ОБНОВЛЕНИЕ : я изменил метод на тот, который есть сейчас.Он работает только в некоторой степени, все еще ограничивая направление y в направлении от центра и неправильно после изменения уровней масштабирования.Я также сделал это "preScale" и "postTranslate", чтобы (насколько я понимаю) он всегда применял масштаб, а затем переводил, а не смешивал их.

FINAL UPDATE : Этоработает сейчас.Вот рабочий метод rebound с комментариями:

public void rebound() {
    // make a rectangle representing what our current canvas looks like
    RectF currentBounds = new RectF(0, 0, drawW, drawH);
    matrix.mapRect(currentBounds);
    // make a rectangle representing the scroll bounds
    RectF areaBounds = new RectF((float) getLeft(),
                                   (float) getTop(),
                                   (float) parentW + (float) getLeft(),
                                   (float) parentH + (float) getTop());

    // the difference between the current rectangle and the rectangle we want
    PointF diff = new PointF(0f, 0f);

    // x-direction
    if (currentBounds.width() > areaBounds.width()) {
        // allow scrolling only if the amount of content is too wide at this scale
        if (currentBounds.left > areaBounds.left) {
            // stop from scrolling too far left
            diff.x = (areaBounds.left - currentBounds.left);
        }
        if (currentBounds.right < areaBounds.right) {
            // stop from scrolling too far right
            diff.x = (areaBounds.right - currentBounds.right);
        }
    } else {
        // negate any scrolling
        diff.x = (areaBounds.left - currentBounds.left);
    }

    // y-direction
    if (currentBounds.height() > areaBounds.height()) {
        // allow scrolling only if the amount of content is too tall at this scale
        if (currentBounds.top > areaBounds.top) {
            // stop from scrolling too far above
            diff.y = (areaBounds.top - currentBounds.top);
        }
        if (currentBounds.bottom < areaBounds.bottom) {
            // stop from scrolling too far below
            diff.y = (areaBounds.bottom - currentBounds.bottom);
        }
    } else {
        // negate any scrolling
        diff.y = (areaBounds.top - currentBounds.top);
    }

    // translate
    matrix.postTranslate(diff.x, diff.y);
}

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

1 Ответ

0 голосов
/ 25 июля 2011

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

Моя реализация вычисляетмасштабный коэффициент, применяется с: canvas.scale (pinchZoomScale, pinchZoomScale);Затем вычисляет физический размер экрана в пикселях, преобразует его в метры и, наконец, применяет коэффициент масштабирования, чтобы все нарисованные объекты корректно смещались.

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

...