Construcción de un contenedor capaz de hacer zoom

Estoy tratando de hacer un ViewGroup que soporta panorámica y zoom de su contenido. Todo lo que pude encontrar en línea fue ideas e implementaciones para hacerlo en un ImageView, pero nunca un contenedor. Quiero mostrar un mapa, y encima de él quiero mostrar múltiples marcadores que son ImageButtons, por lo que el usuario puede tocarlos para obtener más información. Esto se logra en iOS mediante el UIScrollView, pero no pude encontrar una alternativa en Android.

Decidí utilizar un FrameView, así que podría fijar un ImageView con la imagen como fondo, y encima de él agrego un RelativeLayout, en el cual podría agregar los ImageButtons y colocarlos usando márgenes.

Tomé prestada parte de la implementación de TouchImageView aquí , pero he tenido complicaciones. He empezado con panoramización, y parcialmente he tenido éxito, que cava el contenedor alrededor, pero la panorámica funciona horriblemente, que se agita mucho. Aquí está mi código:

public class ScrollingViewGroup extends FrameLayout { private int x = 0; private int y = 0; // 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(); public ScrollingViewGroup(Context context) { super(context); sharedConstructing(context); } public ScrollingViewGroup(Context context, AttributeSet attrs) { super(context, attrs); sharedConstructing(context); } private void sharedConstructing(Context context) { super.setClickable(true); this.context = context; mScaleDetector = new ScaleGestureDetector(context, new ScaleListener()); setOnTouchListener(new OnTouchListener() { @Override public boolean onTouch(View v, MotionEvent event) { mScaleDetector.onTouchEvent(event); 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; Log.d("ScrollingViewGroup", Float.toString(deltaX)); Log.d("ScrollingViewGroup", Float.toString(deltaY)); float scaleWidth = Math.round(origWidth * saveScale); float scaleHeight = Math.round(origHeight * saveScale); x += deltaX; y += 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); setTranslationX(x); setTranslationY(y); invalidate(); return true; // indicate event was handled } }); } 

Cualquier idea es muy apreciada.

Editar: jitter parece ser causa porque cuando se mueve, deltaX y deltaY alternar entre los números positivos y negativos, la comprobación de LogCat … aún no está seguro de por qué. Esto es causado por la variable curr que da valores diferentes cada vez, pero en lugar de ser consistente, parecen como si el dedo se movería hacia adelante y hacia atrás en lugar de sólo hacia delante. Por ejemplo, en lugar de curr.x siendo 0,1,2,3,4, etc, es 0,1,0,5,2,1,5, etc. No está seguro de por qué.

Tengo una solución que funciona con algunos requisitos previos:

  • Los elementos del contenedor deben ser del tipo ImageView
  • Todas las imágenes deben ser del mismo tamaño para acercarse

Los elementos del contenedor pueden ampliarse y desplazarse (pero no al mismo tiempo). El código también es capaz de averiguar si un usuario ha hecho clic en lugar de mover o ampliar. Los ImageViews se almacenan en una ArrayList en FrameLayout y se escalan y se mueven juntos. Algunas partes de este código se toman de un artículo muy agradable en ZDNet por Ed Burnette ( Link ), que se toma de la muy buena Android libro " Hola, Android ".

Echa un vistazo a este código. Puede utilizar esta clase como diseño en cualquier actividad. Incluso debería ser capaz de utilizarlo en el XML. Por ahora hay un método initializeViews () que se llama en el constructor donde se puede codificar las vistas de imagen que se deben cargar cuando se crea el diseño. Debe agregar algunos mapas de bits en este método después de la línea "ArrayList sampleBitmaps = new ArrayList ();" Para un uso real probablemente es mejor implementar un método addImageView (elemento ImageView) donde se pueden agregar vistas dinámicamente.

 import java.util.ArrayList; import android.content.Context; import android.graphics.Bitmap; import android.graphics.Bitmap.Config; import android.graphics.Matrix; import android.graphics.PointF; import android.util.AttributeSet; import android.util.FloatMath; import android.util.Log; import android.view.MotionEvent; import android.view.View; import android.view.View.OnTouchListener; import android.widget.FrameLayout; import android.widget.ImageView; import android.widget.ImageView.ScaleType; public class TouchContainer extends FrameLayout implements OnTouchListener { // constants private static final Config DEFAULT_COLOR_DEPTH = Bitmap.Config.ARGB_4444; private static final String TAG = "TouchContainer"; // fields private ArrayList<ImageView> items; public TouchContainer(Context ctx) { this(ctx, null); } public TouchContainer(Context ctx, AttributeSet attrs) { super(ctx, attrs); initializeViews(); // initialize some sample Bitmaps } /** * This method is just to make an example */ protected void initializeViews() { ScaleType scaleType = ScaleType.MATRIX; // array needs to be created here if used in XML items = new ArrayList<ImageView>(); ArrayList<Bitmap> sampleBitmaps = new ArrayList<Bitmap>(); // here you should add some bitmaps to the Array that will then be displayed in the container // eg sampleBitmaps.add(blabla I'm a bitmap) :-) ImageView iv = null; boolean firstLoop = true; for (Bitmap bitmap : sampleBitmaps) { // Load the bitmaps into imageviews iv = new ImageView(getContext()); iv.setImageBitmap(bitmap); iv.setScaleType(scaleType); if (firstLoop) { // add the touch listener to the first image view that is stored in the ArrayList iv.setOnTouchListener(this); firstLoop = false; } // add view to the FrameLayout this.addView(iv); // add the imageview to the array items.add(iv); } } protected void transformImages(Matrix matrix) { for (ImageView image : items) { image.setImageMatrix(matrix); } } Matrix matrix = new Matrix(); Matrix savedMatrix = new Matrix(); // states static final int NONE = 0; static final int DRAG = 1; static final int ZOOM = 2; int mode = NONE; static final int CLICK = 3; PointF start = new PointF(); PointF mid = new PointF(); float oldDist = 1f; public boolean onTouch(View v, MotionEvent event) { 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: // figure out if user clicked mode = NONE; int xDiff = (int) Math.abs(event.getX() - start.x); int yDiff = (int) Math.abs(event.getY() - start.y); if (xDiff < CLICK && yDiff < CLICK) performClick(); break; 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; } transformImages(matrix); return true; } 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); } } 
  • Mantenga el mapa centrado, independientemente de dónde pinche el zoom en android
  • Cómo reducir la vista de diseño de Android en Intellij Idea12?
  • ¿Por qué los navegadores móviles cargan mi página completamente ampliada?
  • Cómo habilitar la función de zoom in / out (dos dedos) para una imagen en android
  • Zoom Vista previa de Camera2 con TextureView
  • ¿Existe una biblioteca de terceros para Android Pinch Zoom
  • Implementar pinch Zoom usando ViewPager
  • Cómo ajustar el tamaño de los iconos de marcadores según el nivel de zoom en Android
  • ¿Cómo realizar zoom in / out en VideoView en android?
  • WebView deshacerse de doble toque zoom.
  • Android - Google Maps api v2 - Desactivar el control de zoom
  • FlipAndroid es un fan de Google para Android, Todo sobre Android Phones, Android Wear, Android Dev y Aplicaciones para Android Aplicaciones.