¿Cómo puedo obtener la funcionalidad de zoom para las imágenes?

¿Hay alguna forma común de mostrar una imagen grande y permitir al usuario acercar y alejar la imagen y desplazarla?

Hasta ahora encontré dos formas:

  1. sobrescribir ImageView, eso parece un poco demasiado para un problema tan común.
  2. usando una vista web pero con menos control sobre el diseño general, etc.

ACTUALIZAR

Acabo de darle a TouchImageView una nueva actualización. Ahora incluye Double Tap Zoom y Fling además de Panning y Pinch Zoom. El código a continuación es muy anticuado. Puede consultar el proyecto github para obtener el código más reciente.

USO

Coloque TouchImageView.java en su proyecto. Luego se puede usar igual que ImageView. Ejemplo:

TouchImageView img = (TouchImageView) findViewById(R.id.img); 

Si está utilizando TouchImageView en xml, debe proporcionar el nombre completo del paquete, porque es una vista personalizada. Ejemplo:

  

Nota: eliminé mi respuesta anterior, que incluía un código muy antiguo y ahora enlace directamente al código más actualizado en github.

ViewPager

Si está interesado en colocar TouchImageView en una ViewPager, consulte esta respuesta.

He adaptado algunos códigos para crear un TouchImageView que admite multitouch (> 2.1). Está inspirado en el libro ¡Hola, Android! (3ª edición)

Está contenido en los siguientes 3 archivos TouchImageView.java WrapMotionEvent.java EclairMotionEvent.java

TouchImageView.java

 import se.robertfoss.ChanImageBrowser.Viewer; import android.content.Context; import android.graphics.Bitmap; import android.graphics.Matrix; import android.graphics.PointF; import android.util.FloatMath; import android.util.Log; import android.view.MotionEvent; import android.view.View; import android.widget.ImageView; public class TouchImageView extends ImageView { private static final String TAG = "Touch"; // These matrices will be used to move and zoom image Matrix matrix = new Matrix(); Matrix savedMatrix = new Matrix(); // We can be in one of these 3 states static final int NONE = 0; static final int DRAG = 1; static final int ZOOM = 2; int mode = NONE; // Remember some things for zooming PointF start = new PointF(); PointF mid = new PointF(); float oldDist = 1f; Context context; public TouchImageView(Context context) { super(context); super.setClickable(true); this.context = context; matrix.setTranslate(1f, 1f); setImageMatrix(matrix); setScaleType(ScaleType.MATRIX); setOnTouchListener(new OnTouchListener() { @Override public boolean onTouch(View v, MotionEvent rawEvent) { WrapMotionEvent event = WrapMotionEvent.wrap(rawEvent); // Dump touch event to log if (Viewer.isDebug == true){ dumpEvent(event); } // Handle touch events here... switch (event.getAction() & MotionEvent.ACTION_MASK) { case MotionEvent.ACTION_DOWN: savedMatrix.set(matrix); start.set(event.getX(), event.getY()); Log.d(TAG, "mode=DRAG"); mode = DRAG; break; case MotionEvent.ACTION_POINTER_DOWN: oldDist = spacing(event); Log.d(TAG, "oldDist=" + oldDist); if (oldDist > 10f) { savedMatrix.set(matrix); midPoint(mid, event); mode = ZOOM; Log.d(TAG, "mode=ZOOM"); } break; case MotionEvent.ACTION_UP: int xDiff = (int) Math.abs(event.getX() - start.x); int yDiff = (int) Math.abs(event.getY() - start.y); if (xDiff < 8 && yDiff < 8){ performClick(); } case MotionEvent.ACTION_POINTER_UP: mode = NONE; Log.d(TAG, "mode=NONE"); break; case MotionEvent.ACTION_MOVE: if (mode == DRAG) { // ... matrix.set(savedMatrix); matrix.postTranslate(event.getX() - start.x, event.getY() - start.y); } else if (mode == ZOOM) { float newDist = spacing(event); Log.d(TAG, "newDist=" + newDist); if (newDist > 10f) { matrix.set(savedMatrix); float scale = newDist / oldDist; matrix.postScale(scale, scale, mid.x, mid.y); } } break; } setImageMatrix(matrix); return true; // indicate event was handled } }); } public void setImage(Bitmap bm, int displayWidth, int displayHeight) { super.setImageBitmap(bm); //Fit to screen. float scale; if ((displayHeight / bm.getHeight()) >= (displayWidth / bm.getWidth())){ scale = (float)displayWidth / (float)bm.getWidth(); } else { scale = (float)displayHeight / (float)bm.getHeight(); } savedMatrix.set(matrix); matrix.set(savedMatrix); matrix.postScale(scale, scale, mid.x, mid.y); setImageMatrix(matrix); // Center the image float redundantYSpace = (float)displayHeight - (scale * (float)bm.getHeight()) ; float redundantXSpace = (float)displayWidth - (scale * (float)bm.getWidth()); redundantYSpace /= (float)2; redundantXSpace /= (float)2; savedMatrix.set(matrix); matrix.set(savedMatrix); matrix.postTranslate(redundantXSpace, redundantYSpace); setImageMatrix(matrix); } /** Show an event in the LogCat view, for debugging */ private void dumpEvent(WrapMotionEvent event) { // ... String names[] = { "DOWN", "UP", "MOVE", "CANCEL", "OUTSIDE", "POINTER_DOWN", "POINTER_UP", "7?", "8?", "9?" }; StringBuilder sb = new StringBuilder(); int action = event.getAction(); int actionCode = action & MotionEvent.ACTION_MASK; sb.append("event ACTION_").append(names[actionCode]); if (actionCode == MotionEvent.ACTION_POINTER_DOWN || actionCode == MotionEvent.ACTION_POINTER_UP) { sb.append("(pid ").append( action >> MotionEvent.ACTION_POINTER_ID_SHIFT); sb.append(")"); } sb.append("["); for (int i = 0; i < event.getPointerCount(); i++) { sb.append("#").append(i); sb.append("(pid ").append(event.getPointerId(i)); sb.append(")=").append((int) event.getX(i)); sb.append(",").append((int) event.getY(i)); if (i + 1 < event.getPointerCount()) sb.append(";"); } sb.append("]"); Log.d(TAG, sb.toString()); } /** Determine the space between the first two fingers */ private float spacing(WrapMotionEvent event) { // ... float x = event.getX(0) - event.getX(1); float y = event.getY(0) - event.getY(1); return FloatMath.sqrt(x * x + y * y); } /** Calculate the mid point of the first two fingers */ private void midPoint(PointF point, WrapMotionEvent event) { // ... float x = event.getX(0) + event.getX(1); float y = event.getY(0) + event.getY(1); point.set(x / 2, y / 2); } } 

WrapMotionEvent.java

 import android.view.MotionEvent; public class WrapMotionEvent { protected MotionEvent event; protected WrapMotionEvent(MotionEvent event) { this.event = event; } static public WrapMotionEvent wrap(MotionEvent event) { try { return new EclairMotionEvent(event); } catch (VerifyError e) { return new WrapMotionEvent(event); } } public int getAction() { return event.getAction(); } public float getX() { return event.getX(); } public float getX(int pointerIndex) { verifyPointerIndex(pointerIndex); return getX(); } public float getY() { return event.getY(); } public float getY(int pointerIndex) { verifyPointerIndex(pointerIndex); return getY(); } public int getPointerCount() { return 1; } public int getPointerId(int pointerIndex) { verifyPointerIndex(pointerIndex); return 0; } private void verifyPointerIndex(int pointerIndex) { if (pointerIndex > 0) { throw new IllegalArgumentException( "Invalid pointer index for Donut/Cupcake"); } } } 

EclairMotionEvent.java

 import android.view.MotionEvent; public class EclairMotionEvent extends WrapMotionEvent { protected EclairMotionEvent(MotionEvent event) { super(event); } public float getX(int pointerIndex) { return event.getX(pointerIndex); } public float getY(int pointerIndex) { return event.getY(pointerIndex); } public int getPointerCount() { return event.getPointerCount(); } public int getPointerId(int pointerIndex) { return event.getPointerId(pointerIndex); } } 

Utilicé un WebView y cargué la imagen de la memoria a través de

 webview.loadUrl("file://...") 

WebView maneja todo el zoom de panorámica y desplazamiento. Si usa wrap_content, la vista web no será más grande que la imagen y no se mostrarán áreas blancas. El WebView es el mejor ImageView;)

En respuesta a la pregunta original de Janusz, hay varias maneras de lograr esto, todas las cuales varían en su nivel de dificultad y se han mencionado a continuación. Usar una vista web es bueno, pero es muy limitado en términos de apariencia y control. Si está dibujando un bitmap desde un canvas, las soluciones más versátiles que se han propuesto parecen ser las de Mike Ortiz, Robert Foss y / o lo que sugirió Jacob Nordfalk. Hay un gran ejemplo para incorporar el controlador android-multitouch de PaulBourke , y es ideal para tener la compatibilidad multitáctil y todos los tipos de vistas personalizadas.

Personalmente, si simplemente dibuja un canvas en un bitmap y luego lo visualiza en su interior y en ImageView y desea poder acercarse y moverse utilizando el tacto múltiple, la solución de MikeOrtiz es la más fácil. Sin embargo, para mis propósitos, el código del Git que ha proporcionado parece funcionar solo cuando su clase TouchViewView personalizada ImageView es la única hija o proporciona los parámetros de diseño como:

 android:layout_height="match_parent" android:layout_height="match_parent" 

Desafortunadamente, debido a mi diseño de diseño, necesitaba “wrap_content” para “layout_height”. Cuando lo cambié a esto, la imagen se recortaba en la parte inferior y no podía desplazarme ni acercarme a la región recortada. Así que eché un vistazo a Source para ImageView solo para ver cómo Android implementó “onMeasure” y cambió MikeOrtiz’s para adaptarlo.

  @Override protected void onMeasure (int widthMeasureSpec, int heightMeasureSpec) { super.onMeasure(widthMeasureSpec, heightMeasureSpec); //**** ADDED THIS ********///// int w = (int) bmWidth; int h = (int) bmHeight; width = resolveSize(w, widthMeasureSpec); height = resolveSize(h, heightMeasureSpec); //**** END ********/// // width = MeasureSpec.getSize(widthMeasureSpec); // REMOVED // height = MeasureSpec.getSize(heightMeasureSpec); // REMOVED //Fit to screen. float scale; float scaleX = (float)width / (float)bmWidth; float scaleY = (float)height / (float)bmHeight; scale = Math.min(scaleX, scaleY); matrix.setScale(scale, scale); setImageMatrix(matrix); saveScale = 1f; // Center the image redundantYSpace = (float)height - (scale * (float)bmHeight) ; redundantXSpace = (float)width - (scale * (float)bmWidth); redundantYSpace /= (float)2; redundantXSpace /= (float)2; matrix.postTranslate(redundantXSpace, redundantYSpace); origWidth = width - 2 * redundantXSpace; origHeight = height - 2 * redundantYSpace; // origHeight = bmHeight; right = width * saveScale - width - (2 * redundantXSpace * saveScale); bottom = height * saveScale - height - (2 * redundantYSpace * saveScale); setImageMatrix(matrix); } 

Aquí resolveSize (int, int) es una “Utilidad para conciliar un tamaño deseado con restricciones impuestas por una MeasureSpec, donde:

Parámetros:

  - size How big the view wants to be - MeasureSpec Constraints imposed by the parent 

Devoluciones:

  - The size this view should be." 

Así que, esencialmente, proporciona un comportamiento un poco más similar a la clase original de ImageView cuando se carga la imagen. Se podrían hacer algunos cambios más para admitir una mayor variedad de pantallas que modifiquen la relación de aspecto. Pero por ahora espero que esto ayude. Gracias a MikeOrtiz por su código original, gran trabajo.

También puedes probar http://code.google.com/p/android-multitouch-controller/

La biblioteca es realmente genial, aunque inicialmente es un poco difícil de entender.

Acabo de integrar TouchImageView de Robert Foss: ¡funcionó perfectamente! ¡Gracias!

Acabo de modificar un poco el código para poder crear una instancia de mi layout.xml.

Solo agrega dos constructores

 public TouchImageView(Context context, AttributeSet attrs) { super(context, attrs); init(context); } public TouchImageView(Context context) { super(context); init(context); } 

y transforma el viejo constructor en un método init:

 private void init(Context context){ //...old code ofconstructor of Robert Moss's code } 

@Robert Foss, @Mike Ortiz, muchas gracias por su trabajo. Fusioné tu trabajo y completé las clases Robert para android> 2.0 con Mike trabajo adicional.

Como resultado de mi trabajo presento Android Touch Gallery, basado en ViewPager y utilicé TouchImageView modificado. Las imágenes se cargan por URL y puede acercarlas y arrastrarlas. Puede encontrarlo aquí https://github.com/Dreddik/AndroidTouchGallery

Intente usar ZoomView para ampliar cualquier otra vista.

http://code.google.com/p/android-zoom-view/ ¡ es fácil, gratis y divertido de usar!

Añadiendo a la respuesta de @ Mike. También necesité doble toque para restaurar la imagen a las dimensiones originales cuando la vi por primera vez. Así que agregué un montón de variables de instancia “orig …” y agregué el SimpleOnGestureListener que hizo el truco.

 import android.content.Context; import android.graphics.Bitmap; import android.graphics.Matrix; import android.graphics.PointF; import android.view.GestureDetector; import android.view.MotionEvent; import android.view.ScaleGestureDetector; import android.view.View; import android.widget.ImageView; public class TouchImageView extends ImageView { Matrix matrix = new Matrix(); // We can be in one of these 3 states static final int NONE = 0; static final int DRAG = 1; static final int ZOOM = 2; int mode = NONE; // Remember some things for zooming PointF last = new PointF(); PointF start = new PointF(); float minScale = 1f; float maxScale = 3f; float[] m; float redundantXSpace, redundantYSpace, origRedundantXSpace, origRedundantYSpace;; float width, height; static final int CLICK = 3; static final float SAVE_SCALE = 1f; float saveScale = SAVE_SCALE; float right, bottom, origWidth, origHeight, bmWidth, bmHeight, origScale, origBottom,origRight; ScaleGestureDetector mScaleDetector; GestureDetector mGestureDetector; Context context; public TouchImageView(Context context) { super(context); super.setClickable(true); this.context = context; mScaleDetector = new ScaleGestureDetector(context, new ScaleListener()); matrix.setTranslate(1f, 1f); m = new float[9]; setImageMatrix(matrix); setScaleType(ScaleType.MATRIX); setOnTouchListener(new OnTouchListener() { @Override public boolean onTouch(View v, MotionEvent event) { boolean onDoubleTapEvent = mGestureDetector.onTouchEvent(event); if (onDoubleTapEvent) { // Reset Image to original scale values mode = NONE; bottom = origBottom; right = origRight; last = new PointF(); start = new PointF(); m = new float[9]; saveScale = SAVE_SCALE; matrix = new Matrix(); matrix.setScale(origScale, origScale); matrix.postTranslate(origRedundantXSpace, origRedundantYSpace); setImageMatrix(matrix); invalidate(); return true; } mScaleDetector.onTouchEvent(event); matrix.getValues(m); float x = m[Matrix.MTRANS_X]; float y = m[Matrix.MTRANS_Y]; PointF curr = new PointF(event.getX(), event.getY()); switch (event.getAction()) { case MotionEvent.ACTION_DOWN: last.set(event.getX(), event.getY()); start.set(last); mode = DRAG; break; case MotionEvent.ACTION_MOVE: if (mode == DRAG) { float deltaX = curr.x - last.x; float deltaY = curr.y - last.y; float scaleWidth = Math.round(origWidth * saveScale); float scaleHeight = Math.round(origHeight * saveScale); if (scaleWidth < width) { deltaX = 0; if (y + deltaY > 0) deltaY = -y; else if (y + deltaY < -bottom) deltaY = -(y + bottom); } else if (scaleHeight < height) { deltaY = 0; if (x + deltaX > 0) deltaX = -x; else if (x + deltaX < -right) deltaX = -(x + right); } else { if (x + deltaX > 0) deltaX = -x; else if (x + deltaX < -right) deltaX = -(x + right); if (y + deltaY > 0) deltaY = -y; else if (y + deltaY < -bottom) deltaY = -(y + bottom); } matrix.postTranslate(deltaX, deltaY); last.set(curr.x, curr.y); } break; case MotionEvent.ACTION_UP: mode = NONE; int xDiff = (int) Math.abs(curr.x - start.x); int yDiff = (int) Math.abs(curr.y - start.y); if (xDiff < CLICK && yDiff < CLICK) performClick(); break; case MotionEvent.ACTION_POINTER_UP: mode = NONE; break; } setImageMatrix(matrix); invalidate(); return true; // indicate event was handled } }); mGestureDetector = new GestureDetector(context, new GestureDetector.SimpleOnGestureListener() { @Override public boolean onDoubleTapEvent(MotionEvent e) { return true; } }); } @Override public void setImageBitmap(Bitmap bm) { super.setImageBitmap(bm); bmWidth = bm.getWidth(); bmHeight = bm.getHeight(); } public void setMaxZoom(float x) { maxScale = x; } private class ScaleListener extends ScaleGestureDetector.SimpleOnScaleGestureListener { @Override public boolean onScaleBegin(ScaleGestureDetector detector) { mode = ZOOM; return true; } @Override public boolean onScale(ScaleGestureDetector detector) { float mScaleFactor = (float) Math.min( Math.max(.95f, detector.getScaleFactor()), 1.05); float origScale = saveScale; saveScale *= mScaleFactor; if (saveScale > maxScale) { saveScale = maxScale; mScaleFactor = maxScale / origScale; } else if (saveScale < minScale) { saveScale = minScale; mScaleFactor = minScale / origScale; } right = width * saveScale - width - (2 * redundantXSpace * saveScale); bottom = height * saveScale - height - (2 * redundantYSpace * saveScale); if (origWidth * saveScale <= width || origHeight * saveScale <= height) { matrix.postScale(mScaleFactor, mScaleFactor, width / 2, height / 2); if (mScaleFactor < 1) { matrix.getValues(m); float x = m[Matrix.MTRANS_X]; float y = m[Matrix.MTRANS_Y]; if (mScaleFactor < 1) { if (Math.round(origWidth * saveScale) < width) { if (y < -bottom) matrix.postTranslate(0, -(y + bottom)); else if (y > 0) matrix.postTranslate(0, -y); } else { if (x < -right) matrix.postTranslate(-(x + right), 0); else if (x > 0) matrix.postTranslate(-x, 0); } } } } else { matrix.postScale(mScaleFactor, mScaleFactor, detector.getFocusX(), detector.getFocusY()); matrix.getValues(m); float x = m[Matrix.MTRANS_X]; float y = m[Matrix.MTRANS_Y]; if (mScaleFactor < 1) { if (x < -right) matrix.postTranslate(-(x + right), 0); else if (x > 0) matrix.postTranslate(-x, 0); if (y < -bottom) matrix.postTranslate(0, -(y + bottom)); else if (y > 0) matrix.postTranslate(0, -y); } } return true; } } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { super.onMeasure(widthMeasureSpec, heightMeasureSpec); width = MeasureSpec.getSize(widthMeasureSpec); height = MeasureSpec.getSize(heightMeasureSpec); // Fit to screen. float scale; float scaleX = (float) width / (float) bmWidth; float scaleY = (float) height / (float) bmHeight; scale = Math.min(scaleX, scaleY); matrix.setScale(scale, scale); setImageMatrix(matrix); saveScale = SAVE_SCALE; origScale = scale; // Center the image redundantYSpace = (float) height - (scale * (float) bmHeight); redundantXSpace = (float) width - (scale * (float) bmWidth); redundantYSpace /= (float) 2; redundantXSpace /= (float) 2; origRedundantXSpace = redundantXSpace; origRedundantYSpace = redundantYSpace; matrix.postTranslate(redundantXSpace, redundantYSpace); origWidth = width - 2 * redundantXSpace; origHeight = height - 2 * redundantYSpace; right = width * saveScale - width - (2 * redundantXSpace * saveScale); bottom = height * saveScale - height - (2 * redundantYSpace * saveScale); origRight = right; origBottom = bottom; setImageMatrix(matrix); } } 

Esta es una adición muy tardía a este hilo, pero he estado trabajando en una vista de imagen que admite zoom y pan y tiene un par de características que no he encontrado en otros lugares. Esto comenzó como una forma de mostrar imágenes muy grandes sin causar OutOfMemoryError s, submuestreando la imagen cuando se OutOfMemoryError y cargando mosaicos de mayor resolución cuando se ViewPager . Ahora admite el uso en un ViewPager , rotación manualmente o usando información EXIF ​​(paradas de 90 ° ), anula los eventos táctiles seleccionados usando OnClickListener o su propio GestureDetector u OnTouchListener , OnTouchListener subclases para agregar superposiciones, panoramización durante el acercamiento y flirteando ímpetu.

No pretende ser un reemplazo de uso general para ImageView por lo que no lo extiende, y no admite la visualización de imágenes de recursos, solo activos y archivos externos. Requiere SDK 10.

La fuente está en GitHub, y hay una muestra que ilustra el uso en un ViewPager .

https://github.com/davemorrissey/subsampling-scale-image-view

Puede intentar usar los LayoutParams para esto

 public void zoom(boolean flag){ if(flag){ int width=40; int height=40; } else{ int width=20; int height=20; } RelativeLayout.LayoutParams param=new RelativeLayout.LayoutParams(width,height); //use the parent layout of the ImageView; imageView.setLayoutParams(param); //imageView is the view which needs zooming. } 

ZoomIn = zoom (verdadero); ZoomOut = zoom (falso);

 @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); imageDetail = (ImageView) findViewById(R.id.imageView1); imageDetail.setOnTouchListener(new View.OnTouchListener() { @Override public boolean onTouch(View v, MotionEvent event) { ImageView view = (ImageView) v; System.out.println("matrix=" + savedMatrix.toString()); switch (event.getAction() & MotionEvent.ACTION_MASK) { case MotionEvent.ACTION_DOWN: savedMatrix.set(matrix); startPoint.set(event.getX(), event.getY()); mode = DRAG; break; case MotionEvent.ACTION_POINTER_DOWN: oldDist = spacing(event); if (oldDist > 10f) { savedMatrix.set(matrix); midPoint(midPoint, event); mode = ZOOM; } break; case MotionEvent.ACTION_UP: case MotionEvent.ACTION_POINTER_UP: mode = NONE; break; case MotionEvent.ACTION_MOVE: if (mode == DRAG) { matrix.set(savedMatrix); matrix.postTranslate(event.getX() - startPoint.x, event.getY() - startPoint.y); } else if (mode == ZOOM) { float newDist = spacing(event); if (newDist > 10f) { matrix.set(savedMatrix); float scale = newDist / oldDist; matrix.postScale(scale, scale, midPoint.x, midPoint.y); } } break; } view.setImageMatrix(matrix); return true; } @SuppressLint("FloatMath") private float spacing(MotionEvent event) { float x = event.getX(0) - event.getX(1); float y = event.getY(0) - event.getY(1); return FloatMath.sqrt(x * x + y * y); } 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); } }); } 

y la carpeta dibujable debe tener un archivo de imagen bticn. funciona perfectamente 🙂

Algo como abajo lo hará.

 @Override public boolean onTouch(View v,MotionEvent e) { tap=tap2=drag=pinch=none; int mask=e.getActionMasked(); posx=e.getX();posy=e.getY(); float midx= img.getWidth()/2f; float midy=img.getHeight()/2f; int fingers=e.getPointerCount(); switch(mask) { case MotionEvent.ACTION_POINTER_UP: tap2=1;break; case MotionEvent.ACTION_UP: tap=1;break; case MotionEvent.ACTION_MOVE: drag=1; } if(fingers==2){nowsp=Math.abs(e.getX(0)-e.getX(1));} if((fingers==2)&&(drag==0)){ tap2=1;tap=0;drag=0;} if((fingers==2)&&(drag==1)){ tap2=0;tap=0;drag=0;pinch=1;} if(pinch==1) { if(nowsp>oldsp)scale+=0.1; if(nowsp 

Para ver el progtwig completo, consulte aquí: Progtwig para ampliar la imagen en Android