Zoom y panorámica de ImageView Android

He creado una clase de zoom y panorámica para un ImageView.

Características que estoy intentando crear. – Se filtra en el toque de un solo dedo y el movimiento – Zooms y cazuelas en el toque de dos dedos y el movimiento

En su mayor parte esto funciona muy bien. Tiene un error ligero cuando hago lo siguiente: – Me muevo alrededor con un dedo (Estado: No hay problema) – Puse un segundo dedo, zoom y pan (Estado: No hay problema) – Libero mi segundo dedo (Estado: La imagen salta un poco)

Esperaba que alguien pudiera ayudarme a resolver esto.

Estoy pensando que debe tener que hacer con reajustar el mLastTouchX y mLastTouchY en "caso MotionEvent.ACTION_POINTER_UP"

Cualquier ayuda sería muy apreciada!

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; } } } 

Parece que canvas.scale () en la instrucción 'else' del método 'onDraw' requirió mLastGestureX y mLastGestureY para detener el salto. También actualizo mLastTouchX y mLastTouchY al volver a la exploración de un solo dedo en el 'caso MotionEvent.ACTION_POINTER_UP'

Aquí está la final, no puede adaptarse a todas las causas que no limita el panorama de los límites de la imagen pasado, pero que debe ser fácil de lograr, hay un montón de discusiones por ahí sobre ese tema.

 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; } } } 

He estado jugando con una solución a este problema durante más de una semana, y me está dando un montón de problemas. Sin embargo, he reducido significativamente el problema. Su solución anterior NO funcionó para mí, pero mi solución a continuación está cerca. El problema es que salta cuando un segundo dedo es presionado o levantado. He descubierto que esto sucede porque mPosX y mPosY no siempre son verdaderamente lo que se supone que representan las variables. Esto es lo que quiero decir:

Cuando ACTION_MOVE es llamado y el código entra en la instrucción "else" (para tratar con eventos de zoom), mPosX y mPosY solo están cambiando de acuerdo al cambio de enfoque , no al cambio de zoom . Esto significa que el desplazamiento con dos dedos funciona, y el zoom con dos dedos funciona, pero mPosX y mPosY no están cambiando adecuadamente con respecto al cambio de zoom.

He estado tratando de encontrar maneras de arreglar esto utilizando los cambios diferenciales en el zoom (mScaleDetector.getScaleFactor ()) y los cambios diferenciales en el enfoque, pero no puedo parecen trabajar a través de la lógica lo suficientemente bien para encontrar algo que funcione .

Otra solución es mover todas las operaciones de zoom en OnTouchListener y deshacerse completamente del ScaleListener. Esto significa mucho más matemáticas, pero definitivamente sería una solución.

Aquí está 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(); } 

Aquí está cómo el código reacciona a las pulsaciones de dedo:

  @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; } 

Y aunque es principalmente sin relación, aquí está el ScaleListener:

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

Una vez más, este código NO está funcionando perfectamente, pero está muy cerca. He explicado la cuestión exacta anterior y todavía estoy teniendo problemas para que funcione. No sé si esto aparecerá en sus notificaciones, Hank, pero espero que alguien lo vea y me ayude.

La solución de Hank funciona para mí. He añadido una función de reinicio para que las imágenes siguientes se pueden mostrar normalmente.

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

W / o mirando el código que asumiría que usted está haciendo un cálculo de la posición basado en 2 dedos cuando hay 2 dedos. Siempre obtendrá un salto en ese caso.

  • Android: ¿Cómo puedo configurar el nivel de zoom de la vista del mapa en un radio de 1 km alrededor de mi ubicación actual?
  • Marcadores de mapa de escala por Zoom, Android
  • Canvas.scale (mScaleFactor, mScaleFactor, detector.getFocusX (), detector.getFocusY ()) arrastrando límite de lío?
  • Zoom suave en vista de mapa
  • Zoom en los sitios web en android sdk emulator
  • TextView setScaleX () / setScaleY () y setTextIsSelectable (true) selección
  • Lona de Android, múltiples rutas con diferentes cantidades de zoom
  • Cómo generar el gesto de zoom / pellizco para probar para Android
  • Construcción de un contenedor capaz de hacer zoom
  • Lienzo Zoom va al punto (0,0)
  • Cómo capturar el final de una animación Zoom In / Out de un MapController?
  • FlipAndroid es un fan de Google para Android, Todo sobre Android Phones, Android Wear, Android Dev y Aplicaciones para Android Aplicaciones.