ANDROID — Просмотр карты

#android #bitmap #zooming #pan #android-canvas

#Android #растровое изображение #масштабирование #панорамирование #android-холст

Вопрос:

Может кто-нибудь, пожалуйста, помочь мне в нескольких вещах, касающихся mapview, который я пытаюсь создать. У меня есть программа визуализации, которая берет холст и рисует на нем растровое изображение. Я создал представление и в конструкторе я создаю:

 bitmap = Bitmap.createBitmap(windowWidth, windowHeight, Bitmap.Config.ARGB_8888);
canvas = new Canvas(bitmap);
drawable = new BitmapDrawable(bitmap);
drawable.setBounds(0, 0, drawable.getIntrinsicWidth(), drawable.getIntrinsicHeight());
this.Render();
  

В методе onDraw у меня есть это:

 super.onDraw(canvas);
canvas.save();
canvas.translate(mPosX, mPosY);
canvas.scale(mScaleFactor, mScaleFactor);
drawable.draw(canvas);
canvas.restore();
  

Теперь я хотел бы реализовать панорамирование и масштабирование (в настоящее время я работаю без сохраненных плиток)
Вот мой onTouch:

mScaleDetector.onTouchEvent(ev) — масштабируемый детектор.onTouchEvent(ev);

 final int action = ev.getAction();
switch (action amp; MotionEvent.ACTION_MASK) {

case MotionEvent.ACTION_DOWN: {
    this.hasPanned = false;
    final float x = ev.getX();
    final float y = ev.getY();

    mLastTouchX = x;
    mLastTouchY = y;
    mActivePointerId = ev.getPointerId(0);
    break;
}

case MotionEvent.ACTION_MOVE: {
    final int pointerIndex = ev.findPointerIndex(mActivePointerId);
    final float x = ev.getX(pointerIndex);
    final float y = ev.getY(pointerIndex);

    if (!mScaleDetector.isInProgress()) {
        final float dx = x - mLastTouchX;
        final float dy = y - mLastTouchY;

        mPosX  = dx;
        mPosY  = dy;

        invalidate();
    }

    mLastTouchX = x;
    mLastTouchY = y;

    break;
}

case MotionEvent.ACTION_UP: {
    mActivePointerId = INVALID_POINTER_ID;
    if (this.mDownTouchX != this.mLastTouchX amp;amp; this.mDownTouchY != mLastTouchY)
        this.hasPanned = true;
    break;
}

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

case MotionEvent.ACTION_POINTER_UP: {
    final int pointerIndex = (ev.getAction() amp; MotionEvent.ACTION_POINTER_INDEX_MASK) 
            >> MotionEvent.ACTION_POINTER_INDEX_SHIFT;
    final int pointerId = ev.getPointerId(pointerIndex);
    if (pointerId == mActivePointerId) {
        final int newPointerIndex = pointerIndex == 0 ? 1 : 0;
        mLastTouchX = ev.getX(newPointerIndex);
        mLastTouchY = ev.getY(newPointerIndex);
        mActivePointerId = ev.getPointerId(newPointerIndex);
    }
    break;
}
}
return true;
  

Вот чего бы я хотел, и проблемы, которые у меня есть:

  1. При панорамировании я хотел бы рассчитать расстояние в пикселях, чтобы я мог сообщить своему средству визуализации, когда оно снова отобразит изображение. Как это достигается? Запоминая координаты в ACTION_DOWN и сравнивая с новыми координатами в ACTION_UP?
  2. Когда панорамирование завершено (и изображение повторно визуализировано), я хотел бы вернуть холст (изображение) в исходное положение. В настоящее время он остается открытым (из-за canvas.translate?)
  3. Как мне обрабатывать жесты масштабирования? С помощью этого кода при уменьшении масштаба пальцами изображение «перетаскивается» в верхний левый угол…

Я новичок в Android (и Java), поэтому любая помощь приветствуется.

Комментарии:

1. может ли кто-нибудь, пожалуйста, просто объяснить, почему, когда я увеличиваю масштаб, изображение приклеивается к верхнему левому углу?

2. Не совсем уверен, как это связано с MapView … можете ли вы подробнее рассказать об этом?

Ответ №1:

Если ваш код масштабирования увеличивает холст, но перемещает его неправильно, я подозреваю, что вы вызываете неправильный метод Canvas.scale(). Используемый вами метод, похоже, —

 scale(float sx, float sy)
  

и я думаю, вам нужно использовать —

 scale(float sx, float sy, float px, float py)
  

очевидно, что sx и sy — это масштабные коэффициенты x и y (обычно я устанавливаю для них одинаковое значение). px и py — это центральные точки для операции масштабирования, например, средняя точка между событиями движения.ACTION_DOWN и MotionEvent.Координаты события ACTION_POINTER_DOWN.

В моем MotionEvent.Обработчик ACTION_POINTER_DOWN, который я вызываю —

 midPoint(mid, event);
  

mid определяется как —

 private PointF mid = new PointF();
  

и функция средней точки является —

 private void midPoint(PointF point, MotionEvent event) 
{
   float x = event.getX(0)   event.getX(1);
   float y = event.getY(0)   event.getY(1);
   point.set(x / 2, y / 2);
}
  

итак, в моем MotionEvent.Обработчик ACTION_MOVE, который я бы вызвал —

 Canvas.scale(sx, sy, mid.x, mid.y);
  

Комментарии:

1. спасибо ответчику Дейву! Не могли бы вы, пожалуйста, посоветовать мне дополнительную проблему. У меня есть карта на экране. Я знаю центральную точку. Когда я выполняю масштабирование, я знаю среднюю точку. Изображение растягивается или сжимается, а центральная точка перемещается на размер средней точки, а палец, который находится дальше от средней точки, приближается к средней точке или удаляется от нее. Это означает, что мне пришлось бы вычислять центр на основе жеста масштабирования. Я надеюсь, вы понимаете, что я имею в виду. Я проверил карты Google для Android, и они сохраняют центральную точку, которая неверна… Есть предложения?

2. Я думаю, у вас возникли проблемы с масштабированием, влияющим на координаты X, Y события касания. Что вам нужно сделать, это использовать ПЕРЕВЕРНУТУЮ матрицу для отображения событий X, Y в обратном порядке, например — float[] pts = new float[2]; pts[0] = event.getX(); pts[1] = event.getY(); Matrix invertedMatrix = new Matrix(); transMatrix.invert(invertedMatrix); invertedMatrix.mapPoints(pts); int invX = pts[0]; int invY = pts[1]; , а затем использовать преобразованные X, Y для определения вашей реальной средней точки.

Ответ №2:

1) Да, в ACTION_DOWN просто сделайте

 PontF downAt = new PointF(event.getX(), event.getY());
  

и в ACTION_UP вычислите разницу между пикселями.

2) MapView работает иначе, чем ImageView. Для просмотра карты вы бы использовали

 mapView.getController().animateTo(GeoPoint);
  

но я думаю, вы действительно хотите создать подкласс из ImageView и переопределить метод onDraw (Canvas холст) для прямого доступа к холсту. Затем используйте матрицу для обработки перевода и масштабирования, например

 imageView.setScaleType(ImageView.ScaleType.MATRIX);
Matrix imageMatrix = new Matrix();
imageMatrix.postTranslate(xMove, yMove);
imageMatrix.postScale(scaleX, scaleY, midX, midY);
imageView.setImageMatrix(imageMatrix);
  

3) Для обработки жестов масштабирования вам необходимо обработать события ACTION_POINTER_DOWN, ACTION_MOVE и ACTION_POINTER_UP. Здесь есть действительно хороший учебник —
Как использовать мультитач в Android

Будьте осторожны при МАСШТАБИРОВАНИИ изображения, для любого последующего рисования на нем координаты X, Y также должны быть масштабированы, для этого используйте метод Matrix mapPoints (Pts).

Комментарии:

1. опубликованный мной код (класс MyMapView) расширяет представление. Я пытался использовать код из этого примера, но я не могу заставить его работать в моем классе, который расширяет представление. Масштабирование с помощью моего кода работает, просто изображение «приклеивается» к верхнему левому углу при выполнении масштабирования. После того, как будет отпущен зум (жест ущипка), я снова отрисоваю холст. Также … как мне реализовать двойное нажатие и двухкнопочное нажатие в моем коде? В представлении нет методов, поэтому, я думаю, мне нужно реализовать gesturedetector? Двигаюсь ли я в правильном направлении?

2. Это отдельный вопрос к тому, который вы опубликовали. Пожалуйста, отправьте отдельный вопрос, если хотите получить ответы на события tap.