2012-07-05 13 views
7

Utknąłem w tym problem przez osiem godzin, więc pomyślałem, że nadszedł czas, aby uzyskać pomoc.Canvas Pinch-Zoom do punktu w granicach

Zanim zacznę mój problem, dam znać, że przeszedłem przez tę stronę i Google, i żadna z odpowiedzi, które znalazłem, nie pomogła. (This jest jeden, another i another.)

Oto oferta: Mam klasy, która rozciąga SurfaceView (nazwijmy go MySurface) i zastępuje wiele metod w nim. Zwykle rysuje kilka kwadratów i pól tekstowych, co jest w porządku. Gdy tylko użytkownik zacznie się dotykać, konwertuje na Bitmap, a następnie rysuje każdą klatkę, aż użytkownik zwolni.

Oto pocieranie: chcę zaimplementować taką funkcjonalność, że użytkownik może umieścić dwa palce na ekranie, uszczypnąć, aby powiększyć, a także przesuwać (ale TYLKO dwoma palcami w dół).

znalazłem kilka implementacje pinch-to-zoom i dostosować je do mojego Canvas obiektu w MySurface poprzez następujący:

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

Chociaż oba elementy działają dobrze - przewijanie w przód iw tył, a pinch -to-zoom - jest jeden duży problem. Przybliżanie do powiększenia, gdy jest używane, powoduje powiększenie do punktu 0,0 zamiast przybliżania palcem.

Próbowałem wiele sposobów, aby rozwiązać ten problem:

  • Korzystanie canvas.scale(mScaleVector.z, mScaleVector.z, mScaleVector.x, mScaleVector.y);; oczywiście, powoduje to niepożądane wyniki, ponieważ wartości xiy są wartościami zerowymi.
  • Zarządzanie współrzędną "przestawną", która używa tego samego przesunięcia co metoda translate(), ale powoduje to ten sam problem z numerem 0,0 lub przeskakiwanie po dotknięciu widoku.
  • Wiele innych rzeczy ... Dużo zrobiłem z wyżej wymienioną współrzędną obrotu, próbując oprzeć swoją lokalizację na pierwszym dotknięciu użytkownika i przesuwając ją względem tego dotknięcia każdego kolejnego gestu.

Dodatkowo to płótno musi być ograniczone, aby użytkownik nie mógł przewijać na zawsze. Jednak, gdy używam metody .scale(sx, sy, px, py), powoduje ona przesunięcie elementów poza wszelkie granice ustawione w .translate().

Jestem ... całkiem otwarty na wszystko w tym momencie. Wiem, że tę funkcjonalność można dodać, co widać w galerii systemu Android 4.0 (podczas oglądania pojedynczego obrazu). Próbowałem wyśledzić kod źródłowy, który to obsługuje, bez skutku.

+0

Osobiście obsługuję pozycje dwóch palców bezpośrednio za pomocą 'onTouchEvent'. Dzięki temu można łatwo znaleźć punkt środkowy i ile skalowania jest wymagane. – Doomsknight

+0

Czy możesz opublikować rozwiązanie, jeśli rozwiązałeś problem, z którym się borykam, a mój klient jest .... :(, czy możesz mi powiedzieć, jak rozwiązałeś problem, dziękuję – AkashG

Odpowiedz

13

Oto kod, którego używam do implementacji powiększenia palcami w ImageView przy użyciu ScaleGestureDetector. Przy niewielkiej modyfikacji lub bez niej powinieneś być w stanie jej użyć, ponieważ możesz również użyć transformacji marines, aby narysować na 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; 
} 

W moim przypadku, obliczony na neccesary wartości w sposobie View onMeasure(), możesz to zrobić w innym miejscu swojej 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); 

A małe wyjaśnienie:

saveScale to bieżący współczynnik skali Bitmap

mScaleFactor jest th e współczynnik musisz pomnożyć współczynnik skali z.

maxScale i minScale mogą być wartościami stałymi.

width i height są wymiarami ekranu.

redundantXSpace i redundantYSpace są puste pomiędzy granicami obrazu i granic ekranu ponieważ obraz jest skoncentrowane, kiedy jest w nim mniejsza niż ekran

origHeight i origWidth są rozmiary bitmapy

matrix IS obecna macierz transformacji używana do rysowania mapy bitowej

Sztuką jest, że kiedy po raz pierwszy przeskalowałem i wyśrodkowywano obraz podczas inicjalizacji, wybrałem współczynnik skali na 1 i scaleMappingRatio Zmapowałem rzeczywistą wartość skali obrazu względem tej wartości.

+2

Och, człowieku, po przejrzeniu i dokładnie testowałem aspekty twojego kodu, odkryłem, że transformacja matrycy była kluczem! Kupiłbym ci piwo, gdybym mógł! A +, dobry sir – Eric

+1

Nie ma za co, spędziłem dużo czasu na szukaniu właściwej drogi , kiedy zacząłem pisać powyższy kod, pomyślałem, że warto go udostępnić. –

+0

@ AdamL.Mónos świetna robota Adam, okrzyki –

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

To jest kod monodromatyczny, ale można go łatwo przekonwertować do JAVA. Możesz wybrać dowolne wartości dla powiązania (tutaj jest margines 50 pikseli).

+0

Oto biblioteka, która zrobi to, https://stackoverflow.com/questions/6578320/how-to-apply-zoom-przeciągnij-i-obrót-do-obrazu-w-Androidzie/47861501 # 47861501 – user2288580