Es posible tener una vista que admite horizontal y vertical de la cacerola/de arrastre. En la parte superior de eso, quiero ser capaz de pellizcar para hacer zoom y doble toque para acercar el zoom.
¿Este punto de vista existe en Android o ¿alguien sabe de un proyecto que hace?

Para hacerlo aún más difícil, un otro punto de vista (Botón, TextView, VideoView, …) necesita ser agregado a la vista. Cuando los primeros padres/vista ampliada o se mueve a su alrededor, la subvista (Botón) es necesario moverse con el padre.

He intentado varias soluciones, pero ninguna de ellas tiene todas las opciones que estoy buscando.

Hola, llegaste a la solución definitiva para esta pregunta? Podría usted por favor, comparta ?
Sería agradable si usted podría compartir su solución…

OriginalEl autor Ceetn | 2012-09-18

8 Comentarios

  1. 25

    Creo que es posible lograr lo que quieres, pero no hay, que yo sepa construir en una solución para eso. A partir de la segunda parte de tu pregunta supongo que no quieres un zoom View pero un ViewGroup que es la super clase de todos los puntos de vista que puede contener otro punto de vista (por ejemplo, Presentaciones). Aquí está el código que usted podría comenzar a partir de la construcción de su propio ViewGroup la mayoría provienen de este blog:

    import android.content.Context;
    import android.graphics.Canvas;
    import android.graphics.Matrix;
    import android.graphics.Rect;
    import android.view.*;
    public class ZoomableViewGroup extends ViewGroup {
    private static final int INVALID_POINTER_ID = 1;
    private int mActivePointerId = INVALID_POINTER_ID;
    private float mScaleFactor = 1;
    private ScaleGestureDetector mScaleDetector;
    private Matrix mScaleMatrix = new Matrix();
    private Matrix mScaleMatrixInverse = new Matrix();
    private float mPosX;
    private float mPosY;
    private Matrix mTranslateMatrix = new Matrix();
    private Matrix mTranslateMatrixInverse = new Matrix();
    private float mLastTouchX;
    private float mLastTouchY;
    private float mFocusY;
    private float mFocusX;
    private float[] mInvalidateWorkingArray = new float[6];
    private float[] mDispatchTouchEventWorkingArray = new float[2];
    private float[] mOnTouchEventWorkingArray = new float[2];
    public ZoomableViewGroup(Context context) {
    super(context);
    mScaleDetector = new ScaleGestureDetector(context, new ScaleListener());
    mTranslateMatrix.setTranslate(0, 0);
    mScaleMatrix.setScale(1, 1);
    }
    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
    int childCount = getChildCount();
    for (int i = 0; i < childCount; i++) {
    View child = getChildAt(i);
    if (child.getVisibility() != GONE) {
    child.layout(l, t, l+child.getMeasuredWidth(), t + child.getMeasuredHeight());
    }
    }
    }
    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    int childCount = getChildCount();
    for (int i = 0; i < childCount; i++) {
    View child = getChildAt(i);
    if (child.getVisibility() != GONE) {
    measureChild(child, widthMeasureSpec, heightMeasureSpec);
    }
    }
    }
    @Override
    protected void dispatchDraw(Canvas canvas) {
    canvas.save();
    canvas.translate(mPosX, mPosY);
    canvas.scale(mScaleFactor, mScaleFactor, mFocusX, mFocusY);
    super.dispatchDraw(canvas);
    canvas.restore();
    }
    @Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
    mDispatchTouchEventWorkingArray[0] = ev.getX();
    mDispatchTouchEventWorkingArray[1] = ev.getY();
    mDispatchTouchEventWorkingArray = screenPointsToScaledPoints(mDispatchTouchEventWorkingArray);
    ev.setLocation(mDispatchTouchEventWorkingArray[0],
    mDispatchTouchEventWorkingArray[1]);
    return super.dispatchTouchEvent(ev);
    }
    /**
    * Although the docs say that you shouldn't override this, I decided to do
    * so because it offers me an easy way to change the invalidated area to my
    * likening.
    */
    @Override
    public ViewParent invalidateChildInParent(int[] location, Rect dirty) {
    mInvalidateWorkingArray[0] = dirty.left;
    mInvalidateWorkingArray[1] = dirty.top;
    mInvalidateWorkingArray[2] = dirty.right;
    mInvalidateWorkingArray[3] = dirty.bottom;
    mInvalidateWorkingArray = scaledPointsToScreenPoints(mInvalidateWorkingArray);
    dirty.set(Math.round(mInvalidateWorkingArray[0]), Math.round(mInvalidateWorkingArray[1]),
    Math.round(mInvalidateWorkingArray[2]), Math.round(mInvalidateWorkingArray[3]));
    location[0] *= mScaleFactor;
    location[1] *= mScaleFactor;
    return super.invalidateChildInParent(location, dirty);
    }
    private float[] scaledPointsToScreenPoints(float[] a) {
    mScaleMatrix.mapPoints(a);
    mTranslateMatrix.mapPoints(a);
    return a;
    }
    private float[] screenPointsToScaledPoints(float[] a){
    mTranslateMatrixInverse.mapPoints(a);
    mScaleMatrixInverse.mapPoints(a);
    return a;
    }
    @Override
    public boolean onTouchEvent(MotionEvent ev) {
    mOnTouchEventWorkingArray[0] = ev.getX();
    mOnTouchEventWorkingArray[1] = ev.getY();
    mOnTouchEventWorkingArray = scaledPointsToScreenPoints(mOnTouchEventWorkingArray);
    ev.setLocation(mOnTouchEventWorkingArray[0], mOnTouchEventWorkingArray[1]);
    mScaleDetector.onTouchEvent(ev);
    final int action = ev.getAction();
    switch (action & MotionEvent.ACTION_MASK) {
    case MotionEvent.ACTION_DOWN: {
    final float x = ev.getX();
    final float y = ev.getY();
    mLastTouchX = x;
    mLastTouchY = y;
    //Save the ID of this pointer
    mActivePointerId = ev.getPointerId(0);
    break;
    }
    case MotionEvent.ACTION_MOVE: {
    //Find the index of the active pointer and fetch its position
    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;
    mTranslateMatrix.preTranslate(dx, dy);
    mTranslateMatrix.invert(mTranslateMatrixInverse);
    mLastTouchX = x;
    mLastTouchY = y;
    invalidate();
    break;
    }
    case MotionEvent.ACTION_UP: {
    mActivePointerId = INVALID_POINTER_ID;
    break;
    }
    case MotionEvent.ACTION_CANCEL: {
    mActivePointerId = INVALID_POINTER_ID;
    break;
    }
    case MotionEvent.ACTION_POINTER_UP: {
    //Extract the index of the pointer that left the touch sensor
    final int pointerIndex = (action & 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);
    }
    break;
    }
    }
    return true;
    }
    private class ScaleListener extends ScaleGestureDetector.SimpleOnScaleGestureListener {
    @Override
    public boolean onScale(ScaleGestureDetector detector) {
    mScaleFactor *= detector.getScaleFactor();
    if (detector.isInProgress()) {
    mFocusX = detector.getFocusX();
    mFocusY = detector.getFocusY();
    }
    mScaleFactor = Math.max(0.1f, Math.min(mScaleFactor, 5.0f));
    mScaleMatrix.setScale(mScaleFactor, mScaleFactor,
    mFocusX, mFocusY);
    mScaleMatrix.invert(mScaleMatrixInverse);
    invalidate();
    requestLayout();
    return true;
    }
    }
    }

    Lo que esta clase debe ser capaz de hacer, es arrastrar el contenido que la rodea y que permite pellizcar para hacer zoom, doble toque para acercar el zoom no es posible ahora, pero debe ser fácil de implementar en el onTouchEvent método.

    Si usted tiene preguntas de cómo el diseño de la childs en su ViewGroup he encontrado este video muy útil, o si usted tiene cualquier otra pregunta de cómo una sola métodos de trabajo o cualquier otra cosa dude en preguntar en los comentarios.

    Hay un par de errores en esto, incluyendo el que usted necesita para ampliar ViewGroup y te ha faltado un par de ‘}’.
    me preguntaba, ¿por qué no utilizar setScaleX/Y en el viewgroup en lugar de tener que tranfrom el clip, puntos de contacto, etc ti mismo?
    Es posible dejar de zoom cuando la Vista se llega a la escala 1:1? Es sencillo de lograr?
    Tuve que hacer algunas modificaciones para conseguir que esto funcione, pero no hacer el trabajo. Terminé usando una sola matriz y sólo el método onTouchEvent basado en este.
    ¿Cómo puedo limitar el ZoomableViewGroup al traducir ?

    OriginalEl autor const-ae

  2. 16

    Repost de @Artjom respuesta con los errores de menor importancia fijos, es decir, los apoyos, las importaciones, y la ampliación de ViewGroup.

    import android.content.Context;
    import android.graphics.Canvas;
    import android.graphics.Matrix;
    import android.graphics.Rect;
    import android.view.*;
    public class ZoomableViewGroup extends ViewGroup {
    private static final int INVALID_POINTER_ID = 1;
    private int mActivePointerId = INVALID_POINTER_ID;
    private float mScaleFactor = 1;
    private ScaleGestureDetector mScaleDetector;
    private Matrix mScaleMatrix = new Matrix();
    private Matrix mScaleMatrixInverse = new Matrix();
    private float mPosX;
    private float mPosY;
    private Matrix mTranslateMatrix = new Matrix();
    private Matrix mTranslateMatrixInverse = new Matrix();
    private float mLastTouchX;
    private float mLastTouchY;
    private float mFocusY;
    private float mFocusX;
    private float[] mInvalidateWorkingArray = new float[6];
    private float[] mDispatchTouchEventWorkingArray = new float[2];
    private float[] mOnTouchEventWorkingArray = new float[2];
    public ZoomableViewGroup(Context context) {
    super(context);
    mScaleDetector = new ScaleGestureDetector(context, new ScaleListener());
    mTranslateMatrix.setTranslate(0, 0);
    mScaleMatrix.setScale(1, 1);
    }
    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
    int childCount = getChildCount();
    for (int i = 0; i < childCount; i++) {
    View child = getChildAt(i);
    if (child.getVisibility() != GONE) {
    child.layout(l, t, l+child.getMeasuredWidth(), t + child.getMeasuredHeight());
    }
    }
    }
    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    int childCount = getChildCount();
    for (int i = 0; i < childCount; i++) {
    View child = getChildAt(i);
    if (child.getVisibility() != GONE) {
    measureChild(child, widthMeasureSpec, heightMeasureSpec);
    }
    }
    }
    @Override
    protected void dispatchDraw(Canvas canvas) {
    canvas.save();
    canvas.translate(mPosX, mPosY);
    canvas.scale(mScaleFactor, mScaleFactor, mFocusX, mFocusY);
    super.dispatchDraw(canvas);
    canvas.restore();
    }
    @Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
    mDispatchTouchEventWorkingArray[0] = ev.getX();
    mDispatchTouchEventWorkingArray[1] = ev.getY();
    mDispatchTouchEventWorkingArray = screenPointsToScaledPoints(mDispatchTouchEventWorkingArray);
    ev.setLocation(mDispatchTouchEventWorkingArray[0],
    mDispatchTouchEventWorkingArray[1]);
    return super.dispatchTouchEvent(ev);
    }
    /**
    * Although the docs say that you shouldn't override this, I decided to do
    * so because it offers me an easy way to change the invalidated area to my
    * likening.
    */
    @Override
    public ViewParent invalidateChildInParent(int[] location, Rect dirty) {
    mInvalidateWorkingArray[0] = dirty.left;
    mInvalidateWorkingArray[1] = dirty.top;
    mInvalidateWorkingArray[2] = dirty.right;
    mInvalidateWorkingArray[3] = dirty.bottom;
    mInvalidateWorkingArray = scaledPointsToScreenPoints(mInvalidateWorkingArray);
    dirty.set(Math.round(mInvalidateWorkingArray[0]), Math.round(mInvalidateWorkingArray[1]),
    Math.round(mInvalidateWorkingArray[2]), Math.round(mInvalidateWorkingArray[3]));
    location[0] *= mScaleFactor;
    location[1] *= mScaleFactor;
    return super.invalidateChildInParent(location, dirty);
    }
    private float[] scaledPointsToScreenPoints(float[] a) {
    mScaleMatrix.mapPoints(a);
    mTranslateMatrix.mapPoints(a);
    return a;
    }
    private float[] screenPointsToScaledPoints(float[] a){
    mTranslateMatrixInverse.mapPoints(a);
    mScaleMatrixInverse.mapPoints(a);
    return a;
    }
    @Override
    public boolean onTouchEvent(MotionEvent ev) {
    mOnTouchEventWorkingArray[0] = ev.getX();
    mOnTouchEventWorkingArray[1] = ev.getY();
    mOnTouchEventWorkingArray = scaledPointsToScreenPoints(mOnTouchEventWorkingArray);
    ev.setLocation(mOnTouchEventWorkingArray[0], mOnTouchEventWorkingArray[1]);
    mScaleDetector.onTouchEvent(ev);
    final int action = ev.getAction();
    switch (action & MotionEvent.ACTION_MASK) {
    case MotionEvent.ACTION_DOWN: {
    final float x = ev.getX();
    final float y = ev.getY();
    mLastTouchX = x;
    mLastTouchY = y;
    //Save the ID of this pointer
    mActivePointerId = ev.getPointerId(0);
    break;
    }
    case MotionEvent.ACTION_MOVE: {
    //Find the index of the active pointer and fetch its position
    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;
    mTranslateMatrix.preTranslate(dx, dy);
    mTranslateMatrix.invert(mTranslateMatrixInverse);
    mLastTouchX = x;
    mLastTouchY = y;
    invalidate();
    break;
    }
    case MotionEvent.ACTION_UP: {
    mActivePointerId = INVALID_POINTER_ID;
    break;
    }
    case MotionEvent.ACTION_CANCEL: {
    mActivePointerId = INVALID_POINTER_ID;
    break;
    }
    case MotionEvent.ACTION_POINTER_UP: {
    //Extract the index of the pointer that left the touch sensor
    final int pointerIndex = (action & 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);
    }
    break;
    }
    }
    return true;
    }
    private class ScaleListener extends ScaleGestureDetector.SimpleOnScaleGestureListener {
    @Override
    public boolean onScale(ScaleGestureDetector detector) {
    mScaleFactor *= detector.getScaleFactor();
    if (detector.isInProgress()) {
    mFocusX = detector.getFocusX();
    mFocusY = detector.getFocusY();
    }
    mScaleFactor = Math.max(0.1f, Math.min(mScaleFactor, 5.0f));
    mScaleMatrix.setScale(mScaleFactor, mScaleFactor,
    mFocusX, mFocusY);
    mScaleMatrix.invert(mScaleMatrixInverse);
    invalidate();
    requestLayout();
    return true;
    }
    }
    }
    Añado un niño en ZoomableViewGroup, después de zoom, el tocado de las coordenadas de los niños de equivocarse. ¿Cómo puedo solucionarlo ?
    para los que se enfrenta el problema como yo: cambiar el orden del método <br/>private float[] screenPointsToScaledPoints(float[] a) a: <br/> private float[] screenPointsToScaledPoints(float[] a) { mScaleMatrixInverse.mapPoints(a); mTranslateMatrixInverse.mapPoints(a); return a; }
    Por qué repost y no editarlo?

    OriginalEl autor Alex

  3. 10

    Basarse en las respuestas que utiliza este código para obtener el pan y zoom funciones de trabajo. Tuve problemas con el pivote de los puntos en primera.

    public class ZoomableViewGroup extends ViewGroup {
    //these matrices will be used to move and zoom image
    private Matrix matrix = new Matrix();
    private Matrix matrixInverse = new Matrix();
    private Matrix savedMatrix = new Matrix();
    //we can be in one of these 3 states
    private static final int NONE = 0;
    private static final int DRAG = 1;
    private static final int ZOOM = 2;
    private int mode = NONE;
    //remember some things for zooming
    private PointF start = new PointF();
    private PointF mid = new PointF();
    private float oldDist = 1f;
    private float[] lastEvent = null;
    private boolean initZoomApplied=false;
    private float[] mDispatchTouchEventWorkingArray = new float[2];
    private float[] mOnTouchEventWorkingArray = new float[2];
    @Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
    mDispatchTouchEventWorkingArray[0] = ev.getX();
    mDispatchTouchEventWorkingArray[1] = ev.getY();
    mDispatchTouchEventWorkingArray = screenPointsToScaledPoints(mDispatchTouchEventWorkingArray);
    ev.setLocation(mDispatchTouchEventWorkingArray[0],
    mDispatchTouchEventWorkingArray[1]);
    return super.dispatchTouchEvent(ev);
    }
    private float[] scaledPointsToScreenPoints(float[] a) {
    matrix.mapPoints(a);
    return a;
    }
    private float[] screenPointsToScaledPoints(float[] a){
    matrixInverse.mapPoints(a);
    return a;
    }
    public ZoomableViewGroup(Context context) {
    super(context);
    init(context);
    }
    public ZoomableViewGroup(Context context, AttributeSet attrs) {
    super(context, attrs);      
    init(context);
    }
    public ZoomableViewGroup(Context context, AttributeSet attrs,
    int defStyleAttr) {
    super(context, attrs, defStyleAttr);
    init(context);
    }
    /**
    * Determine the space between the first two fingers
    */
    private float spacing(MotionEvent event) {
    float x = event.getX(0) - event.getX(1);
    float y = event.getY(0) - event.getY(1);
    return (float)Math.sqrt(x * x + y * y);
    }
    /**
    * Calculate the mid point of the first two fingers
    */
    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);
    }
    private void init(Context context){
    }
    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
    int childCount = getChildCount();
    for (int i = 0; i < childCount; i++) {
    View child = getChildAt(i);
    if (child.getVisibility() != GONE) {
    child.layout(l, t, l+child.getMeasuredWidth(), t + child.getMeasuredHeight());
    }
    }
    }
    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    int widthSize = MeasureSpec.getSize(widthMeasureSpec);
    int heightSize = MeasureSpec.getSize(heightMeasureSpec);
    float[] values = new float[9];
    matrix.getValues(values);
    float container_width = values[Matrix.MSCALE_X]*widthSize;
    float container_height = values[Matrix.MSCALE_Y]*heightSize;
    //Log.d("zoomToFit", "m width: "+container_width+" m height: "+container_height);
    //Log.d("zoomToFit", "m x: "+pan_x+" m y: "+pan_y);
    int childCount = getChildCount();
    for (int i = 0; i < childCount; i++) {
    View child = getChildAt(i);
    if (child.getVisibility() != GONE) {
    measureChild(child, widthMeasureSpec, heightMeasureSpec);
    if(i==0 && !initZoomApplied && child.getWidth()>0){
    int c_w = child.getWidth();
    int c_h = child.getHeight();
    //zoomToFit(c_w, c_h, container_width, container_height);
    }
    }
    }        
    }
    private void zoomToFit(int c_w, int c_h, float container_width, float container_height){
    float proportion_firstChild = (float)c_w/(float)c_h;
    float proportion_container = container_width/container_height;
    //Log.d("zoomToFit", "firstChildW: "+c_w+" firstChildH: "+c_h);
    //Log.d("zoomToFit", "proportion-container: "+proportion_container);
    //Log.d("zoomToFit", "proportion_firstChild: "+proportion_firstChild);
    if(proportion_container<proportion_firstChild){
    float initZoom = container_height/c_h;
    //Log.d("zoomToFit", "adjust height with initZoom: "+initZoom);
    matrix.postScale(initZoom, initZoom);
    matrix.postTranslate(-1*(c_w*initZoom-container_width)/2, 0);
    matrix.invert(matrixInverse);
    }else {
    float initZoom = container_width/c_w;
    //Log.d("zoomToFit", "adjust width with initZoom: "+initZoom);
    matrix.postScale(initZoom, initZoom);
    matrix.postTranslate(0, -1*(c_h*initZoom-container_height)/2);
    matrix.invert(matrixInverse);
    }
    initZoomApplied=true;
    invalidate();
    }
    @Override
    protected void dispatchDraw(Canvas canvas) {
    canvas.save();
    canvas.setMatrix(matrix);
    super.dispatchDraw(canvas);
    canvas.restore();
    }
    @Override
    public boolean onTouchEvent(MotionEvent event) {
    //handle touch events here
    mOnTouchEventWorkingArray[0] = event.getX();
    mOnTouchEventWorkingArray[1] = event.getY();
    mOnTouchEventWorkingArray = scaledPointsToScreenPoints(mOnTouchEventWorkingArray);
    event.setLocation(mOnTouchEventWorkingArray[0], mOnTouchEventWorkingArray[1]);
    switch (event.getAction() & MotionEvent.ACTION_MASK) {
    case MotionEvent.ACTION_DOWN:
    savedMatrix.set(matrix);
    start.set(event.getX(), event.getY());
    mode = DRAG;
    lastEvent = null;
    break;
    case MotionEvent.ACTION_POINTER_DOWN:
    oldDist = spacing(event);
    if (oldDist > 10f) {
    savedMatrix.set(matrix);
    midPoint(mid, event);
    mode = ZOOM;
    }
    lastEvent = new float[4];
    lastEvent[0] = event.getX(0);
    lastEvent[1] = event.getX(1);
    lastEvent[2] = event.getY(0);
    lastEvent[3] = event.getY(1);
    //d = rotation(event);
    break;
    case MotionEvent.ACTION_UP:
    case MotionEvent.ACTION_POINTER_UP:
    mode = NONE;
    lastEvent = null;
    break;
    case MotionEvent.ACTION_MOVE:
    if (mode == DRAG) {
    matrix.set(savedMatrix);
    float dx = event.getX() - start.x;
    float dy = event.getY() - start.y;
    matrix.postTranslate(dx, dy);
    matrix.invert(matrixInverse);
    } else if (mode == ZOOM) {
    float newDist = spacing(event);
    if (newDist > 10f) {
    matrix.set(savedMatrix);
    float scale = (newDist / oldDist);
    matrix.postScale(scale, scale, mid.x, mid.y);
    matrix.invert(matrixInverse);
    }
    }
    break;
    }
    invalidate();
    return true;
    }
    }

    Créditos a la onTouch función ir a: http://judepereira.com/blog/multi-touch-in-android-translate-scale-and-rotate/
    Gracias a Artjom para su acercamiento a la dipatch los eventos de toque.

    He añadido un zoomToFit método que se comentó en este punto porque la mayoría de la gente no necesita no necesita esto. Se ajusta a los niños para el tamaño del recipiente y se lleva el primer hijo como referencia para la scalefactor.

    cómo limitar el factor de escala ? Quiero el zoom para estar dentro de 0,75 y 4, por ejemplo.
    Se puede definir un MAX_ZOOM: int MAX_ZOOM = 4; y, a continuación, agregue esta línea antes de publicar la escala (de la matriz.postScale): float[] values = new float[9]; matrix.getValues(values); if(scale*values[Matrix.MSCALE_X] >= MAX_ZOOM){ scale = MAX_ZOOM/values[Matrix.MSCALE_X]; }
    Un buen código! El zoom es mejor (se inicia de inmediato, que no es el caso de Alex del código) y con ninguna de pivote-puntos de problema. Sin embargo, no permite hacer zoom con un doble toque y arrastre la acción.
    En mi móvil (Nexus 4, CyanogenMod13, Android 6.0.1), la subvista se inicia por debajo de la barra de estado mientras que ZoomableViewGroup comienza justo debajo de la barra de estado. Si en dispatchDraw puedo reemplazar canvas.setMatrix(matrix); con: float[] values = new float[9]; matrix.getValues(values); canvas.translate(values[Matrix.MTRANS_X], values[Matrix.MTRANS_Y]); canvas.scale(values[Matrix.MSCALE_X], values[Matrix.MSCALE_Y]);, funciona bien…
    Los márgenes son aparentemente ignorada por defecto. Para ellos, el cambio de la línea en onLayout a child.layout(child.getLeft(), child.getTop(), child.getLeft() + child.getMeasuredWidth(), child.getTop() + child.getMeasuredHeight());. Además, puede agregar super.onLayout(changed, l, t, r, b) para el inicio del método.

    OriginalEl autor Thomas

  4. 10

    Thomas respuesta es casi la mejor (tengo una posición bug en mi teléfono): el zoom se inicia de inmediato (que no es el caso con Alex‘s código), y el zoom se hace a la derecha del pivote de punto.

    Sin embargo, contrariamente a Alex‘s código, no es posible hacer zoom con un «doble-toque y arrastre» gesto (no un conocido gesto, pero muy útil para hacer zoom con un solo dedo, como en Google Chrome o en Mapas de Google apps). Así que aquí es una modificación de Thomas‘s código para que sea posible (y la fijación de la subvista posición de error):

    public class ZoomableView extends ViewGroup {
    //States.
    private static final byte NONE = 0;
    private static final byte DRAG = 1;
    private static final byte ZOOM = 2;
    private byte mode = NONE;
    //Matrices used to move and zoom image.
    private Matrix matrix = new Matrix();
    private Matrix matrixInverse = new Matrix();
    private Matrix savedMatrix = new Matrix();
    //Parameters for zooming.
    private PointF start = new PointF();
    private PointF mid = new PointF();
    private float oldDist = 1f;
    private float[] lastEvent = null;
    private long lastDownTime = 0l;
    private float[] mDispatchTouchEventWorkingArray = new float[2];
    private float[] mOnTouchEventWorkingArray = new float[2];
    @Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
    mDispatchTouchEventWorkingArray[0] = ev.getX();
    mDispatchTouchEventWorkingArray[1] = ev.getY();
    mDispatchTouchEventWorkingArray = screenPointsToScaledPoints(mDispatchTouchEventWorkingArray);
    ev.setLocation(mDispatchTouchEventWorkingArray[0], mDispatchTouchEventWorkingArray[1]);
    return super.dispatchTouchEvent(ev);
    }
    public ZoomableView(Context context) {
    super(context);
    init(context);
    }
    public ZoomableView(Context context, AttributeSet attrs) {
    super(context, attrs);
    init(context);
    }
    public ZoomableView(Context context, AttributeSet attrs, int defStyleAttr) {
    super(context, attrs, defStyleAttr);
    init(context);
    }
    private void init(Context context) {
    }
    /**
    * Determine the space between the first two fingers
    */
    private float spacing(MotionEvent event) {
    float x = event.getX(0) - event.getX(1);
    float y = event.getY(0) - event.getY(1);
    return (float) Math.sqrt(x * x + y * y);
    }
    /**
    * Calculate the mid point of the first two fingers
    */
    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);
    }
    private float[] scaledPointsToScreenPoints(float[] a) {
    matrix.mapPoints(a);
    return a;
    }
    private float[] screenPointsToScaledPoints(float[] a) {
    matrixInverse.mapPoints(a);
    return a;
    }
    @Override
    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
    int childCount = getChildCount();
    for (int i = 0; i < childCount; i++) {
    View child = getChildAt(i);
    if (child.getVisibility() != GONE) {
    child.layout(left, top, left + child.getMeasuredWidth(), top + child.getMeasuredHeight());
    }
    }
    }
    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    int childCount = getChildCount();
    for (int i = 0; i < childCount; i++) {
    View child = getChildAt(i);
    if (child.getVisibility() != GONE) {
    measureChild(child, widthMeasureSpec, heightMeasureSpec);
    }
    }
    }
    @Override
    protected void dispatchDraw(Canvas canvas) {
    float[] values = new float[9];
    matrix.getValues(values);
    canvas.save();
    canvas.translate(values[Matrix.MTRANS_X], values[Matrix.MTRANS_Y]);
    canvas.scale(values[Matrix.MSCALE_X], values[Matrix.MSCALE_Y]);
    super.dispatchDraw(canvas);
    canvas.restore();
    }
    @Override
    public boolean onTouchEvent(MotionEvent event) {
    //handle touch events here
    mOnTouchEventWorkingArray[0] = event.getX();
    mOnTouchEventWorkingArray[1] = event.getY();
    mOnTouchEventWorkingArray = scaledPointsToScreenPoints(mOnTouchEventWorkingArray);
    event.setLocation(mOnTouchEventWorkingArray[0], mOnTouchEventWorkingArray[1]);
    switch (event.getAction() & MotionEvent.ACTION_MASK) {
    case MotionEvent.ACTION_DOWN:
    savedMatrix.set(matrix);
    mode = DRAG;
    lastEvent = null;
    long downTime = event.getDownTime();
    if (downTime - lastDownTime < 300l) {
    float density = getResources().getDisplayMetrics().density;
    if (Math.max(Math.abs(start.x - event.getX()), Math.abs(start.y - event.getY())) < 40.f * density) {
    savedMatrix.set(matrix);
    mid.set(event.getX(), event.getY());
    mode = ZOOM;
    lastEvent = new float[4];
    lastEvent[0] = lastEvent[1] = event.getX();
    lastEvent[2] = lastEvent[3] = event.getY();
    }
    lastDownTime = 0l;
    } else {
    lastDownTime = downTime;
    }
    start.set(event.getX(), event.getY());
    break;
    case MotionEvent.ACTION_POINTER_DOWN:
    oldDist = spacing(event);
    if (oldDist > 10f) {
    savedMatrix.set(matrix);
    midPoint(mid, event);
    mode = ZOOM;
    }
    lastEvent = new float[4];
    lastEvent[0] = event.getX(0);
    lastEvent[1] = event.getX(1);
    lastEvent[2] = event.getY(0);
    lastEvent[3] = event.getY(1);
    break;
    case MotionEvent.ACTION_UP:
    case MotionEvent.ACTION_POINTER_UP:
    mode = NONE;
    lastEvent = null;
    break;
    case MotionEvent.ACTION_MOVE:
    final float density = getResources().getDisplayMetrics().density;
    if (mode == DRAG) {
    matrix.set(savedMatrix);
    float dx = event.getX() - start.x;
    float dy = event.getY() - start.y;
    matrix.postTranslate(dx, dy);
    matrix.invert(matrixInverse);
    if (Math.max(Math.abs(start.x - event.getX()), Math.abs(start.y - event.getY())) > 20.f * density) {
    lastDownTime = 0l;
    }
    } else if (mode == ZOOM) {
    if (event.getPointerCount() > 1) {
    float newDist = spacing(event);
    if (newDist > 10f * density) {
    matrix.set(savedMatrix);
    float scale = (newDist / oldDist);
    matrix.postScale(scale, scale, mid.x, mid.y);
    matrix.invert(matrixInverse);
    }
    } else {
    matrix.set(savedMatrix);
    float scale = event.getY() / start.y;
    matrix.postScale(scale, scale, mid.x, mid.y);
    matrix.invert(matrixInverse);
    }
    }
    break;
    }
    invalidate();
    return true;
    }
    }
    Cómo conseguir transformado a coordenadas x,y en onTouchEvent ?
    Tuve un problema con Thomas respuesta. Mi punto de vista a golpear las cajas fue inferior, donde la vista real es (para android < 25 API). Me di cuenta de que el problema era con dispatchDraw método. Como yo no sé nada acerca de matrices yo era muy frustraded. Usted dispatchDraw se ha solucionado el problema. Yo realmente quiere entender cómo es que la magia funciona. Quiero aprender acerca de la matriz y, tal vez, de la Ventanilla técnicas con Rect objetos. Puede usted consejos de buenas fuentes para aprender por que? Yo realmente no quieren hacer algo, cuando no puedo averiguar cómo funciona. Gracias por el código, y gracias @Thomas también.
    Esta es la mejor solución.
    Tengo un problema con esta solución. Originalmente, esta solución es perfecta, pero sólo si este viewgroup no contiene clickable vistas en el interior, porque el clic de la subvista recibe el onTouchEvent(). Se puede ver si usted puede agregar soporte para arrastrar con los enlaces de las vistas en el interior? Creo que el uso de onInterceptTouchEvent() podría ser posible, pero yo no soy experto en la gestión de los eventos de toque. Gracias
    esta solución funciona sin problemas

    OriginalEl autor Donkey

  5. 1

    Esta vista personalizada es una subclase de Android estándar imageView y añade (multi)touch zoom y pan (y el doble toque zoom, así):

    https://github.com/sephiroth74/ImageViewZoom

    http://blog.sephiroth.it/2011/04/04/imageview-zoom-and-scroll/

    Es similar a la MikeOrtiz del TouchImageView que ya sabes, pero añade algunas características más.

    Puede utilizar en el interior de una vista de la «pila» (un Android FrameLayout o algo así), junto con los otros textView(s) que usted necesita. (Me refiero a una «pila» de puntos de vista, como una pila de platos, o una pila de tarjetas. En otras palabras, un montón de puntos de vista apilados uno sobre el lado en el eje Z.)

    Mover todos sus puntos de vista junto requiere que usted tome el control de Android Gestos (multitouch) mecanismo y escribir el código necesario. No hay ninguna listos-para-usar solución para su (muy complejo) requisito. Echa un vistazo a este artículo:

    http://android-developers.blogspot.it/2010/06/making-sense-of-multitouch.html

    OriginalEl autor AlexBottoni

  6. 0

    Para obtener mejores permormance de Zoom en la de Alex código de añadir siguientes cambios

    private class ScaleListener extends ScaleGestureDetector.SimpleOnScaleGestureListener {
    @Override
    public boolean onScale(ScaleGestureDetector detector) {
    mScaleFactor *= detector.getScaleFactor();
    if (detector.isInProgress()) {
    mFocusX = detector.getFocusX();
    mFocusY = detector.getFocusY();
    }
    mFocusX = (mFocusX + mLastTouchX)/2;  //get center of touch
    mFocusY = (mFocusY + mLastTouchY)/2;  //get center of touch
    mScaleFactor = Math.max(1f, Math.min(mScaleFactor, 2.0f));
    mScaleMatrix.setScale(mScaleFactor, mScaleFactor,mFocusX, mFocusY);
    mScaleMatrix.invert(mScaleMatrixInverse);
    invalidate();
    requestLayout();
    return true;
    }
    }

    OriginalEl autor user5613010

  7. 0

    Para aquellos que están interesados en un zoom/panorámica LinearLayout, he modificado la versión publicada por Alex para poner las cosas de manera vertical y la tapa de la paneo a la visible vistas. Yo uso esto para los mapas de bits de la PDFRenderer. He probado esto, pero si usted nota cualquier error por favor post porque me gustaría saber acerca de ellos, también!

    Nota: yo he optado por no implementar el doble toque, ya que QuickScale obras.

    public class ZoomableLinearLayout extends ViewGroup {
    private static final int INVALID_POINTER_ID = 1;
    private int mActivePointerId = INVALID_POINTER_ID;
    private float mScaleFactor = 1;
    private ScaleGestureDetector mScaleDetector;
    private Matrix mScaleMatrix = new Matrix();
    private Matrix mScaleMatrixInverse = new Matrix();
    private float mPosX;
    private float mPosY;
    private Matrix mTranslateMatrix = new Matrix();
    private Matrix mTranslateMatrixInverse = new Matrix();
    private float mLastTouchX;
    private float mLastTouchY;
    private float mFocusY;
    private float mFocusX;
    private int mCanvasWidth;
    private int mCanvasHeight;
    private float[] mInvalidateWorkingArray = new float[6];
    private float[] mDispatchTouchEventWorkingArray = new float[2];
    private float[] mOnTouchEventWorkingArray = new float[2];
    private boolean mIsScaling;
    public ZoomableLinearLayout(Context context) {
    super(context);
    mScaleDetector = new ScaleGestureDetector(context, new ScaleListener());
    mTranslateMatrix.setTranslate(0, 0);
    mScaleMatrix.setScale(1, 1);
    }
    public ZoomableLinearLayout(Context context, AttributeSet attributeSet) {
    super(context, attributeSet);
    mScaleDetector = new ScaleGestureDetector(context, new ScaleListener());
    mTranslateMatrix.setTranslate(0, 0);
    mScaleMatrix.setScale(1, 1);
    }
    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
    int childCount = getChildCount();
    for (int i = 0; i < childCount; i++) {
    View child = getChildAt(i);
    if (child.getVisibility() != GONE) {
    child.layout(l, t, l+child.getMeasuredWidth(), t += child.getMeasuredHeight());
    }
    }
    }
    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    int height = 0;
    int width = 0;
    int childCount = getChildCount();
    for (int i = 0; i < childCount; i++) {
    View child = getChildAt(i);
    if (child.getVisibility() != GONE) {
    measureChild(child, widthMeasureSpec, heightMeasureSpec);
    height += child.getMeasuredHeight();
    width = Math.max(width, child.getMeasuredWidth());
    }
    }
    mCanvasWidth = width;
    mCanvasHeight = height;
    }
    @Override
    protected void dispatchDraw(Canvas canvas) {
    canvas.save();
    canvas.translate(mPosX, mPosY);
    canvas.scale(mScaleFactor, mScaleFactor, mFocusX, mFocusY);
    super.dispatchDraw(canvas);
    canvas.restore();
    }
    @Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
    mDispatchTouchEventWorkingArray[0] = ev.getX();
    mDispatchTouchEventWorkingArray[1] = ev.getY();
    mDispatchTouchEventWorkingArray = screenPointsToScaledPoints(mDispatchTouchEventWorkingArray);
    ev.setLocation(mDispatchTouchEventWorkingArray[0],
    mDispatchTouchEventWorkingArray[1]);
    return super.dispatchTouchEvent(ev);
    }
    /**
    * Although the docs say that you shouldn't override this, I decided to do
    * so because it offers me an easy way to change the invalidated area to my
    * likening.
    */
    @Override
    public ViewParent invalidateChildInParent(int[] location, Rect dirty) {
    mInvalidateWorkingArray[0] = dirty.left;
    mInvalidateWorkingArray[1] = dirty.top;
    mInvalidateWorkingArray[2] = dirty.right;
    mInvalidateWorkingArray[3] = dirty.bottom;
    mInvalidateWorkingArray = scaledPointsToScreenPoints(mInvalidateWorkingArray);
    dirty.set(Math.round(mInvalidateWorkingArray[0]), Math.round(mInvalidateWorkingArray[1]),
    Math.round(mInvalidateWorkingArray[2]), Math.round(mInvalidateWorkingArray[3]));
    location[0] *= mScaleFactor;
    location[1] *= mScaleFactor;
    return super.invalidateChildInParent(location, dirty);
    }
    private float[] scaledPointsToScreenPoints(float[] a) {
    mScaleMatrix.mapPoints(a);
    mTranslateMatrix.mapPoints(a);
    return a;
    }
    private float[] screenPointsToScaledPoints(float[] a){
    mTranslateMatrixInverse.mapPoints(a);
    mScaleMatrixInverse.mapPoints(a);
    return a;
    }
    @Override
    public boolean onTouchEvent(MotionEvent ev) {
    mOnTouchEventWorkingArray[0] = ev.getX();
    mOnTouchEventWorkingArray[1] = ev.getY();
    mOnTouchEventWorkingArray = scaledPointsToScreenPoints(mOnTouchEventWorkingArray);
    ev.setLocation(mOnTouchEventWorkingArray[0], mOnTouchEventWorkingArray[1]);
    mScaleDetector.onTouchEvent(ev);
    final int action = ev.getAction();
    switch (action & MotionEvent.ACTION_MASK) {
    case MotionEvent.ACTION_DOWN: {
    final float x = ev.getX();
    final float y = ev.getY();
    mLastTouchX = x;
    mLastTouchY = y;
    //Save the ID of this pointer
    mActivePointerId = ev.getPointerId(0);
    break;
    }
    case MotionEvent.ACTION_MOVE: {
    //Find the index of the active pointer and fetch its position
    final int pointerIndex = ev.findPointerIndex(mActivePointerId);
    final float x = ev.getX(pointerIndex);
    final float y = ev.getY(pointerIndex);
    if (mIsScaling && ev.getPointerCount() == 1) {
    //Don't move during a QuickScale.
    mLastTouchX = x;
    mLastTouchY = y;
    break;
    }
    float dx = x - mLastTouchX;
    float dy = y - mLastTouchY;
    float[] topLeft = {0f, 0f};
    float[] bottomRight = {getWidth(), getHeight()};
    /*
    * Corners of the view in screen coordinates, so dx/dy should not be allowed to
    * push these beyond the canvas bounds.
    */
    float[] scaledTopLeft = screenPointsToScaledPoints(topLeft);
    float[] scaledBottomRight = screenPointsToScaledPoints(bottomRight);
    dx = Math.min(Math.max(dx, scaledBottomRight[0] - mCanvasWidth), scaledTopLeft[0]);
    dy = Math.min(Math.max(dy, scaledBottomRight[1] - mCanvasHeight), scaledTopLeft[1]);
    mPosX += dx;
    mPosY += dy;
    mTranslateMatrix.preTranslate(dx, dy);
    mTranslateMatrix.invert(mTranslateMatrixInverse);
    mLastTouchX = x;
    mLastTouchY = y;
    invalidate();
    break;
    }
    case MotionEvent.ACTION_UP: {
    mActivePointerId = INVALID_POINTER_ID;
    break;
    }
    case MotionEvent.ACTION_CANCEL: {
    mActivePointerId = INVALID_POINTER_ID;
    break;
    }
    case MotionEvent.ACTION_POINTER_UP: {
    //Extract the index of the pointer that left the touch sensor
    final int pointerIndex = (action & 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);
    }
    break;
    }
    }
    return true;
    }
    private float getMaxScale() {
    return 2f;
    }
    private float getMinScale() {
    return 1f;
    }
    private class ScaleListener extends ScaleGestureDetector.SimpleOnScaleGestureListener {
    @Override
    public boolean onScaleBegin(ScaleGestureDetector detector) {
    mIsScaling = true;
    mFocusX = detector.getFocusX();
    mFocusY = detector.getFocusY();
    float[] foci = {mFocusX, mFocusY};
    float[] scaledFoci = screenPointsToScaledPoints(foci);
    mFocusX = scaledFoci[0];
    mFocusY = scaledFoci[1];
    return true;
    }
    @Override
    public void onScaleEnd(ScaleGestureDetector detector) {
    mIsScaling = false;
    }
    @Override
    public boolean onScale(ScaleGestureDetector detector) {
    mScaleFactor *= detector.getScaleFactor();
    mScaleFactor = Math.max(getMinScale(), Math.min(mScaleFactor, getMaxScale()));
    mScaleMatrix.setScale(mScaleFactor, mScaleFactor, mFocusX, mFocusY);
    mScaleMatrix.invert(mScaleMatrixInverse);
    invalidate();
    return true;
    }
    }
    }

    OriginalEl autor Oded

  8. 0

    Estoy utilizando algunas versiones modificadas de los códigos publicados aquí. Este ZoomLayout utiliza Android gesto reconocedores de desplazamiento y la escala. Se conserva también el pivote y los límites cuando se utiliza el zoom o panorámica.

    https://github.com/maxtower/ZoomLayout/blob/master/app/src/main/java/com/maxtower/testzoomlayout/ZoomLayout.java

    Para preservar pan límites:

    if (contentSize != null)
    {
    float[] values = new float[9];
    matrix.getValues(values);
    float totX = values[Matrix.MTRANS_X] + distanceX;
    float totY = values[Matrix.MTRANS_Y] + distanceY;
    float sx = values[Matrix.MSCALE_X];
    Rect viewableRect = new Rect();
    ZoomLayout.this.getDrawingRect(viewableRect);
    float offscreenWidth = contentSize.width() - (viewableRect.right - viewableRect.left);
    float offscreenHeight = contentSize.height() - (viewableRect.bottom - viewableRect.top);
    float maxDx = (contentSize.width() - (contentSize.width() / sx)) * sx;
    float maxDy = (contentSize.height() - (contentSize.height() / sx)) * sx;
    if (totX > 0 && distanceX > 0)
    {
    distanceX = 0;
    }
    if (totY > 0 && distanceY > 0)
    {
    distanceY = 0;
    }
    if(totX*-1 > offscreenWidth+maxDx && distanceX < 0)
    {
    distanceX = 0;
    }
    if(totY*-1 > offscreenHeight+maxDy && distanceY < 0)
    {
    distanceY = 0;
    }
    }

    OriginalEl autor mtbomb

Dejar respuesta

Please enter your comment!
Please enter your name here