2012-07-05 11 views
7

Sono stato bloccato su questo problema per otto ore, quindi ho pensato che fosse giunto il momento di ottenere aiuto.Canvas Pinch-Zoom to Point Within Bounds

Prima di iniziare il mio problema, farò sapere che ho visitato questo sito e Google, e nessuna delle risposte che ho trovato ha aiutato. (This è uno, another e another.)

Ecco il punto: ho una classe che estende SurfaceView (chiamiamolo MySurface) e le sostituzioni molti metodi in esso. Normalmente, disegna diversi quadrati e caselle di testo, il che va bene. Non appena un utente inizia a toccare, converte in uno Bitmap, quindi disegna ogni fotogramma fino a quando l'utente non rilascia.

Ecco il problema: voglio implementare una funzionalità di questo tipo che l'utente può posizionare due dita sullo schermo, pizzicare per ingrandire, e anche scorrere intorno (ma SOLO con due dita verso il basso).

ho trovato un paio di implementazioni di pinch-to-zoom e adattato alla mia Canvas oggetto MySurface tramite il seguente:

public void onDraw(Canvas canvas) { 
    super.onDraw(canvas); 

    canvas.save(); 

    canvas.scale(mScaleVector.z, mScaleVector.z); // This is the scale factor as seen below 
    canvas.translate(mScaleVector.x, mScaleVector.y); // These are offset values from 0,0, both working fine 

    // Start draw code 

    // ... 

    // End draw code 

    canvas.restore(); 
} 
private class ScaleListener extends ScaleGestureDetector.SimpleOnScaleGestureListener { 
    @Override 
    public boolean onScale(ScaleGestureDetector detector) { 
     float factor = detector.getScaleFactor(); 
     if (Math.abs(factor - 1.0f) >= 0.0075f) { 
      mScaleVector.z *= factor; 
      mScaleVector.z = Math.max(MIN_ZOOM, Math.min(mScaleVector.z, MAX_ZOOM)); 
     } 

     // ... 

     invalidate(); 

     return true; 
    } 
} 
public boolean onTouchEvent(MotionEvent event) { 
    int action = event.getAction() & MotionEvent.ACTION_MASK; 
    int pointerIndex = (event.getAction() & MotionEvent.ACTION_POINTER_INDEX_MASK) >> MotionEvent.ACTION_POINTER_INDEX_SHIFT; 
    if (event.getPointerCount() == 2) { 
     if (action == MotionEvent.ACTION_POINTER_DOWN && pointerIndex == 1) { 
      // The various pivot coordinate codes would belong here 
     } 
    } 

    detector.onTouchEvent(event); // Calls the Scale Gesture Detector 
    return true; 
} 

Mentre entrambi gli elementi funzionano bene - lo scorrimento avanti e indietro e il pizzico -to-zoom: c'è un grosso problema. Il pinch-to-zoom, se utilizzato, ingrandisce il punto 0,0, anziché lo zoom nel punto dito.

Ho provato un sacco di modi per risolvere questo:

  • Utilizzando canvas.scale(mScaleVector.z, mScaleVector.z, mScaleVector.x, mScaleVector.y);; ovviamente, questo produce risultati indesiderati poiché i valori di xxe sono 0-offset.
  • Gestione di una coordinata "pivot" che utilizza lo stesso offset del metodo translate(), ma questo produce lo stesso problema 0,0 o il salto quando la vista viene toccata.
  • Numerose altre cose ... Ho fatto molto con la coordinata del perno di cui sopra, cercando di basare la sua posizione sul primo tocco dell'utente e spostandolo rispetto a quel tocco ogni gesto successivo.

Inoltre, questa tela deve essere limitata, quindi l'utente non può scorrere per sempre. Tuttavia, quando utilizzo il metodo .scale(sx, sy, px, py), spinge le cose oltre i limiti impostati in .translate().

Sono ... praticamente aperto a tutto a questo punto. So che questa funzionalità può essere aggiunta, come si vede nella galleria di Android 4.0 (durante la visualizzazione di una singola immagine). Ho provato a rintracciare il codice sorgente che gestisce questo, senza alcun risultato.

+0

Gestisco personalmente le posizioni delle due dita direttamente con 'onTouchEvent'. Con questo, è possibile trovare facilmente il punto medio e la quantità di ridimensionamento richiesta. – Doomsknight

+0

puoi postare la soluzione se hai risolto il problema che sto affrontando lo stesso problema, e il mio cliente è .... :(, o puoi dirmi come hai risolto il problema grazie – AkashG

risposta

13

Ecco il codice che uso per implementare lo zoom con pizzico in un ImageView utilizzando ScaleGestureDetector. Con poca o nessuna modifica dovresti essere in grado di usarlo anche, dato che puoi usare anche le marce di trasformazione, per disegnare su un Canvas.

@Override 
public boolean onScale(ScaleGestureDetector detector) { 
    float mScaleFactor = (float) Math.min(
     Math.max(.8f, detector.getScaleFactor()), 1.2); 
    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; 
} 

Nel mio caso, ho calcolato il valore neccesary nel metodo della View onMeasure(), si potrebbe desiderare di farlo da qualche altra parte nel vostro SurfaceView

width = MeasureSpec.getSize(widthMeasureSpec); // Change this according to your screen size 
height = MeasureSpec.getSize(heightMeasureSpec); // Change this according to your screen size 

// 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; 
scaleMappingRatio = saveScale/scale; 

// 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; 
right = width * saveScale - width - (2 * redundantXSpace * saveScale); 
bottom = height * saveScale - height - (2 * redundantYSpace * saveScale); 
setImageMatrix(matrix); 

Una piccola spiegazione:

saveScale è il rapporto di scala corrente di Bitmap

mScaleFactor è th Fattore che devi moltiplicare il tuo rapporto di scala con.

maxScale e minScale possono essere valori costanti.

width e height sono le dimensioni dello schermo.

redundantXSpace e redundantYSpace sono il vuoto tra i bordi dell'immagine ei bordi dello schermo poiché l'immagine è centrata quando in esso è più piccolo schermo

origHeight e origWidth sono le dimensioni della bitmap

matrix è il matrice di trasformazione corrente utilizzata per disegnare la bitmap

Il trucco è che quando ho ridimensionato e centrato l'immagine prima dell'inizializzazione, ho scelto il rapporto di scala come 1 e con scaleMappingRatio Ho mappato i valori di scala effettivi dell'immagine in relazione a quello.

+2

Oh uomo, dopo aver esaminato e ho provato a fondo gli aspetti del tuo codice, ho scoperto che quelle trasformazioni di matrice erano la chiave! ti comprerei una birra se potessi! A +, buon signore. – Eric

+1

Sei il benvenuto, ho passato molto tempo a capire anche nel modo giusto , quando ho iniziato a scrivere il codice sopra, ho pensato che sarebbe valsa la pena. –

+0

@ AdamL.Mónos ottimo lavoro Adam, evviva! –

-1
protected override void OnDraw(Canvas canvas) 
    { 
     canvas.Save(); 

     int x = (int)(canvas.Width * mScaleFactor - canvas.Width)/2; 
     int y = (int)(canvas.Height * mScaleFactor - canvas.Height)/2; 

     if (mPosX > (canvas.Width + x - 50)) 
     { 
      mPosX = canvas.Width + x - 50; 
     } 
     if (mPosX < (50 - canvas.Width - x)) 
     { 
      mPosX = 50 - canvas.Width - x; 
     } 
     if (mPosY > (canvas.Height + y - 50)) 
     { 
      mPosY = canvas.Height + y - 50; 
     } 
     if (mPosY < (50 - canvas.Height - y)) 
     { 
      mPosY = 50 - canvas.Height - y; 
     } 

     canvas.Translate(mPosX, mPosY); 
     canvas.Scale(mScaleFactor, mScaleFactor, canvas.Width/2, canvas.Height/2); 
     base.OnDraw(canvas); 
     canvas.Restore(); 
    } 

Questo è il codice monodroid, ma può essere facilmente convertito in JAVA. È possibile scegliere qualsiasi valore per il limite (qui è un margine di 50 pixel)

+0

Ecco una libreria che farà questo. https://stackoverflow.com/questions/6578320/how-to-apply-zoom-drag-and-rotazione-to-an-image-in-android/47861501 # 47861501 – user2288580