2012-12-19 28 views
5

Khi tôi nghiên cứu các phương pháp tiếp cận để chạm/thu phóng trên ảnh, tôi thường tìm thấy mã đơn giản, hiệu quả - nhưng không có gì thực hiện được những gì tôi muốn. Hình ảnh cần không bao giờ hiển thị một khoảng trống giữa cạnh của hình ảnh thực tế (bitmap) và Chế độ xem của nó. Nếu bitmap là 200x100 và Chế độ xem là 50x50, người dùng chỉ có thể thu nhỏ đến 100x50, cho phép họ trượt hình ảnh theo chiều ngang chứ không phải theo chiều dọc.Dịch bitmap/tỷ lệ trong phạm vi ranh giới?

Mã của tôi hoạt động tốt khi di chuyển (dịch) hình ảnh - cho đến khi hình ảnh được phóng to. Sau đó, một cái gì đó được ném ra; Tôi có thể di chuyển bitmap đủ xa để thấy những khoảng trống xung quanh nó. Nó có thể là một cái gì đó đơn giản và rõ ràng liên quan đến các phép đo pixel bao thanh toán bởi các yếu tố quy mô hiện tại, nhưng tôi không thể tìm thấy nó. Tôi nghi ngờ nó đã làm với các tính toán của maxX và maxY trong onDraw() dưới đây. Bất kỳ ý tưởng?

import android.content.Context; 
import android.graphics.Bitmap; 
import android.graphics.Canvas; 
import android.graphics.drawable.BitmapDrawable; 
import android.graphics.drawable.Drawable; 
import android.util.AttributeSet; 
import android.util.Log; 
import android.view.MotionEvent; 
import android.view.ScaleGestureDetector; 
import android.widget.ImageView; 

/** 
* Most code from 
* http://android-developers.blogspot.com/2010/06/making-sense-of-multitouch.html 
* 
* @author Chad Schultz 
* 
*/ 
public class PanZoomImageView extends ImageView { 

    public static final String TAG = PanZoomImageView.class.getName(); 

    private static final int INVALID_POINTER_ID = -1; 

    // The ‘active pointer’ is the one currently moving our object. 
    private int mActivePointerId = INVALID_POINTER_ID; 

    private Bitmap bitmap; 

    private ScaleGestureDetector mScaleDetector; 
    private float mScaleFactor = 1.f; 
    private float minScaleFactor; 

    private float mPosX; 
    private float mPosY; 

    private float mLastTouchX, mLastTouchY; 

    private boolean firstDraw = true; 

    private boolean panEnabled = true; 
    private boolean zoomEnabled = true; 

    public PanZoomImageView(Context context) { 
     super(context); 
     setup(); 
    } 

    public PanZoomImageView(Context context, AttributeSet attrs, int defStyle) { 
     super(context, attrs, defStyle); 
     setup(); 
    } 

    public PanZoomImageView(Context context, AttributeSet attrs) { 
     super(context, attrs); 
     setup(); 
    } 

    private void setup() { 
     mScaleDetector = new ScaleGestureDetector(getContext(), new ScaleListener()); 
    } 

    @Override 
    public void setImageBitmap(Bitmap bmp) { 
     super.setImageBitmap(bmp); 
     bitmap = bmp; 
     firstDraw = true; 
    } 

    @Override 
    public void setImageDrawable(Drawable drawable) { 
     super.setImageDrawable(drawable); 
     bitmap = ((BitmapDrawable) drawable).getBitmap(); 
     firstDraw = true; 
    } 

    public void onDraw(Canvas canvas) { 
     Log.v(TAG, "onDraw()"); 
     if (bitmap == null) { 
      Log.w(TAG, "nothing to draw - bitmap is null"); 
      super.onDraw(canvas); 
      return; 
     } 

     if (firstDraw 
       && (bitmap.getHeight() > 0) 
       && (bitmap.getWidth() > 0) 
       && (canvas.getHeight() > 0) 
       && (canvas.getWidth() > 0)) { 
      //Don't let the user zoom out so much that the image is smaller 
      //than its containing frame 
      float minXScaleFactor = (float) canvas.getWidth()/(float) bitmap.getWidth(); 
      float minYScaleFactor = (float) canvas.getHeight()/(float) bitmap.getHeight(); 
      minScaleFactor = Math.max(minXScaleFactor, minYScaleFactor); 
      Log.d(TAG, "minScaleFactor: " + minScaleFactor); 
      firstDraw = false; 
     } 
     mScaleFactor = Math.max(mScaleFactor, minScaleFactor); 
     Log.d(TAG, "mScaleFactor: " + mScaleFactor); 

     //Save the canvas without translating (panning) or scaling (zooming) 
     //After each change, restore to this state, instead of compounding 
     //changes upon changes 
     canvas.save(); 

     int maxX, minX, maxY, minY; 
     //How far can we move the image horizontally without having a gap between image and frame? 
     maxX = (int) (mScaleFactor * (bitmap.getWidth()/2) - (canvas.getWidth()/2)); 
     minX = -1 * maxX; 
     //How far can we move the image vertically without having a gap between image and frame? 
     maxY = (int) (mScaleFactor * (bitmap.getHeight()/2) - (canvas.getWidth()/2)); 
     minY = -1 * maxY; 
     //Do not go beyond the boundaries of the image 
     if (mPosX > maxX) { 
      mPosX = maxX; 
     } 
     if (mPosX < minX) { 
      mPosX = minX; 
     } 
     if (mPosY > maxY) { 
      mPosY = maxY; 
     } 
     if (mPosY < minY) { 
      mPosY = minY; 
     } 

     Log.d(TAG, "canvas width: " + canvas.getWidth() + " canvas height: " 
       + canvas.getHeight()); 
     Log.d(TAG, "bitmap width: " + bitmap.getWidth() + " height: " + bitmap.getHeight()); 
     Log.d(TAG, "translating mPosX: " + mPosX + " mPosY: " + mPosY); 

     if (zoomEnabled) { 
      Log.d(TAG, "zooming to scale factor of " + mScaleFactor); 
      canvas.scale(mScaleFactor, mScaleFactor); 
     } else { 
      Log.d(TAG, "zooming disabled"); 
     } 

     if (panEnabled) { 
      Log.d(TAG, "panning to " + mPosX + "," + mPosY); 
      canvas.translate(mPosX, mPosY); 
     } else { 
      Log.d(TAG, "panning disabled"); 
     } 

     super.onDraw(canvas); 
     canvas.restore(); //clear translation/scaling 
    } 

    @Override 
    public boolean onTouchEvent(MotionEvent ev) { 
     // Let the ScaleGestureDetector inspect all events. 
     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; 
       mActivePointerId = ev.getPointerId(0); 
       break; 
      } 

      case MotionEvent.ACTION_MOVE: { 
       final int pointerIndex = ev.findPointerIndex(mActivePointerId); 
       final float x = ev.getX(pointerIndex); 
       final float y = ev.getY(pointerIndex); 

       // Only move if the ScaleGestureDetector isn't processing a gesture. 
       if (!mScaleDetector.isInProgress()) { 
        float dx = x - mLastTouchX; 
        float dy = y - mLastTouchY; 

        //Adjust for zoom factor. Otherwise, the user's finger moving 10 pixels 
        //at 200% zoom causes the image to slide 20 pixels instead of perfectly 
        //following the user's touch 
        dx /= mScaleFactor; 
        dy /= mScaleFactor; 

        mPosX += dx; 
        mPosY += dy; 

        invalidate(); 
       } 

       mLastTouchX = x; 
       mLastTouchY = y; 

       break; 
      } 

      case MotionEvent.ACTION_UP: { 
       mActivePointerId = INVALID_POINTER_ID; 
       break; 
      } 

      case MotionEvent.ACTION_CANCEL: { 
       mActivePointerId = INVALID_POINTER_ID; 
       break; 
      } 

      case MotionEvent.ACTION_POINTER_UP: { 
       final int pointerIndex = (ev.getAction() & 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(); 
      // Don't let the object get too small or too large. 
      mScaleFactor = Math.max(0.1f, Math.min(mScaleFactor, 5.0f)); 
      Log.d(TAG, "detector scale factor: " + detector.getScaleFactor() + " mscalefactor: " + mScaleFactor); 

      invalidate(); 
      return true; 
     } 
    } 

    //Currently zoomEnabled/panEnabled can only be set programmatically, not in XML 

    public boolean isPanEnabled() { 
     return panEnabled; 
    } 

    public void setPanEnabled(boolean panEnabled) { 
     this.panEnabled = panEnabled; 
    } 

    public boolean isZoomEnabled() { 
     return zoomEnabled; 
    } 

    public void setZoomEnabled(boolean zoomEnabled) { 
     this.zoomEnabled = zoomEnabled; 
    } 

} 

Trả lời

6

Đây là những gì cuối cùng tôi tự nghĩ ra sau một thử nghiệm đau đớn - học một số điều thú vị trên đường về cách xử lý Bitmap trong Android. Mã này là hoàn hảo, nhưng nó phù hợp với mục đích của tôi - hy vọng nó cũng sẽ giúp người khác.

import android.content.Context; 
import android.graphics.Bitmap; 
import android.graphics.Canvas; 
import android.graphics.drawable.BitmapDrawable; 
import android.graphics.drawable.Drawable; 
import android.util.AttributeSet; 
import android.util.Log; 
import android.view.MotionEvent; 
import android.view.ScaleGestureDetector; 
import android.view.View; 

/** 
* @author Chad Schultz 
* @version 1 
*/ 
public class PanZoomView extends View { 

    public static final String TAG = PanZoomView.class.getName(); 

    private static final int INVALID_POINTER_ID = -1; 

    // The ‘active pointer’ is the one currently moving our object. 
    private int mActivePointerId = INVALID_POINTER_ID; 

    private Bitmap bitmap; 
    private float viewHeight; 
    private float viewWidth; 
    float canvasWidth, canvasHeight; 

    private ScaleGestureDetector mScaleDetector; 
    private float mScaleFactor = 1.f; 
    private float minScaleFactor; 

    private float mPosX; 
    private float mPosY; 

    private float mLastTouchX, mLastTouchY; 

    private boolean firstDraw = true; 

    private boolean panEnabled = true; 
    private boolean zoomEnabled = true; 

    public PanZoomView(Context context) { 
     super(context); 
     setup(); 
    } 

    public PanZoomView(Context context, AttributeSet attrs, int defStyle) { 
     super(context, attrs, defStyle); 
     setup(); 
    } 

    public PanZoomView(Context context, AttributeSet attrs) { 
     super(context, attrs); 
     setup(); 
    } 

    private void setup() { 
     mScaleDetector = new ScaleGestureDetector(getContext(), new ScaleListener()); 
    } 

    public void setBitmap(Bitmap bmp) { 
     setImageBitmap(bmp); 
    } 

    public void setImageBitmap(Bitmap bmp) { 
     bitmap = bmp; 
     resetZoom(); 
     resetPan(); 
     firstDraw = true; 
     invalidate(); 
    } 

    public Bitmap getImageBitmap() { 
     return bitmap; 
    } 

    public Bitmap getBitmap() { 
     return getImageBitmap(); 
    } 

    public void resetZoom() { 
     mScaleFactor = 1.0f; 
    } 

    public void resetPan() { 
     mPosX = 0f; 
     mPosY = 0f; 
    } 

    public void setImageDrawable(Drawable drawable) { 
     setImageBitmap(((BitmapDrawable) drawable).getBitmap()); 
    } 

    public BitmapDrawable getImageDrawable() { 
     BitmapDrawable bd = new BitmapDrawable(getContext().getResources(), bitmap); 
     return bd; 
    } 

    public BitmapDrawable getDrawable() { 
     return getImageDrawable(); 
    } 

    public void onDraw(Canvas canvas) { 
//  Log.v(TAG, "onDraw()"); 

     if (bitmap == null) { 
      Log.w(TAG, "nothing to draw - bitmap is null"); 
      super.onDraw(canvas); 
      return; 
     } 

     if (firstDraw 
       && (bitmap.getHeight() > 0) 
       && (bitmap.getWidth() > 0)) { 
      //Don't let the user zoom out so much that the image is smaller 
      //than its containing frame 
      float minXScaleFactor = (float) viewWidth/(float) bitmap.getWidth(); 
      float minYScaleFactor = (float) viewHeight/(float) bitmap.getHeight(); 
      minScaleFactor = Math.max(minXScaleFactor, minYScaleFactor); 
      Log.d(TAG, "minScaleFactor: " + minScaleFactor); 
      mScaleFactor = minScaleFactor; //start out "zoomed out" all the way 

      mPosX = mPosY = 0; 
      firstDraw = false; 

     } 
     mScaleFactor = Math.max(mScaleFactor, minScaleFactor); 

     canvasHeight = canvas.getHeight(); 
     canvasWidth = canvas.getWidth(); 
//  Log.d(TAG, "canvas density: " + canvas.getDensity() + " bitmap density: " + bitmap.getDensity()); 

//  Log.d(TAG, "mScaleFactor: " + mScaleFactor); 

     //Save the canvas without translating (panning) or scaling (zooming) 
     //After each change, restore to this state, instead of compounding 
     //changes upon changes 
     canvas.save(); 
     int maxX, minX, maxY, minY; 
     //Regardless of the screen density (HDPI, MDPI) or the scale factor, 
     //The image always consists of bitmap width divided by 2 pixels. If an image 
     //is 200 pixels wide and you scroll right 100 pixels, you just scrolled the image 
     //off the screen to the left. 
     minX = (int) (((viewWidth/mScaleFactor) - bitmap.getWidth())/2); 
     maxX = 0; 
     //How far can we move the image vertically without having a gap between image and frame? 
     minY = (int) (((viewHeight/mScaleFactor) - bitmap.getHeight())/2); 
     maxY = 0; 
     Log.d(TAG, "minX: " + minX + " maxX: " + maxX + " minY: " + minY + " maxY: " + maxY); 
     //Do not go beyond the boundaries of the image 
     if (mPosX > maxX) { 
      mPosX = maxX; 
     } 
     if (mPosX < minX) { 
      mPosX = minX; 
     } 
     if (mPosY > maxY) { 
      mPosY = maxY; 
     } 
     if (mPosY < minY) { 
      mPosY = minY; 
     } 

//  Log.d(TAG, "view width: " + viewWidth + " view height: " 
//    + viewHeight); 
//  Log.d(TAG, "bitmap width: " + bitmap.getWidth() + " height: " + bitmap.getHeight()); 
//  Log.d(TAG, "translating mPosX: " + mPosX + " mPosY: " + mPosY); 

//  Log.d(TAG, "zooming to scale factor of " + mScaleFactor); 
     canvas.scale(mScaleFactor, mScaleFactor); 

//  Log.d(TAG, "panning to " + mPosX + "," + mPosY); 
     canvas.translate(mPosX, mPosY); 

     super.onDraw(canvas); 
     canvas.drawBitmap(bitmap, mPosX, mPosY, null); 
     canvas.restore(); //clear translation/scaling 
    } 

    @Override 
    public boolean onTouchEvent(MotionEvent ev) { 
     // Let the ScaleGestureDetector inspect all events. 
     if (zoomEnabled) { 
      mScaleDetector.onTouchEvent(ev); 
     } 

     if (panEnabled) { 
      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; 
        mActivePointerId = ev.getPointerId(0); 
        break; 
       } 

       case MotionEvent.ACTION_MOVE: { 
        final int pointerIndex = ev.findPointerIndex(mActivePointerId); 
        final float x = ev.getX(pointerIndex); 
        final float y = ev.getY(pointerIndex); 

        // Only move if the ScaleGestureDetector isn't processing a gesture. 
        if (!mScaleDetector.isInProgress()) { 
         float dx = x - mLastTouchX; 
         float dy = y - mLastTouchY; 

         //Adjust for zoom factor. Otherwise, the user's finger moving 10 pixels 
         //at 200% zoom causes the image to slide 20 pixels instead of perfectly 
         //following the user's touch 
         dx /= (mScaleFactor * 2); 
         dy /= (mScaleFactor * 2); 

         mPosX += dx; 
         mPosY += dy; 

         Log.v(TAG, "moving by " + dx + "," + dy + " mScaleFactor: " + mScaleFactor); 

         invalidate(); 
        } 

        mLastTouchX = x; 
        mLastTouchY = y; 

        break; 
       } 

       case MotionEvent.ACTION_UP: { 
        mActivePointerId = INVALID_POINTER_ID; 
        break; 
       } 

       case MotionEvent.ACTION_CANCEL: { 
        mActivePointerId = INVALID_POINTER_ID; 
        break; 
       } 

       case MotionEvent.ACTION_POINTER_UP: { 
        final int pointerIndex = (ev.getAction() & 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(); 
      // Don't let the object get too small or too large. 
      mScaleFactor = Math.max(0.1f, Math.min(mScaleFactor, 5.0f)); 
//   Log.d(TAG, "detector scale factor: " + detector.getScaleFactor() + " mscalefactor: " + mScaleFactor); 

      invalidate(); 
      return true; 
     } 
    } 

    //Currently zoomEnabled/panEnabled can only be set programmatically, not in XML 

    public boolean isPanEnabled() { 
     return panEnabled; 
    } 

    public void setPanEnabled(boolean panEnabled) { 
     this.panEnabled = panEnabled; 
    } 

    public boolean isZoomEnabled() { 
     return zoomEnabled; 
    } 

    public void setZoomEnabled(boolean zoomEnabled) { 
     this.zoomEnabled = zoomEnabled; 
    } 

    /** 
    * Calls getCroppedBitmap(int outputWidth, int outputHeight) without 
    * scaling the resulting bitmap to any specific size. 
    * @return 
    */ 
    public Bitmap getCroppedBitmap() { 
     return getCroppedBitmap(0, 0); 
    } 

    /** 
    * Takes the section of the bitmap visible in its View object 
    * and exports that to a Bitmap object, taking into account both 
    * the translation (panning) and zoom (scaling). 
    * WARNING: run this in a separate thread, not on the UI thread! 
    * If you specify that a 200x200 image should have an outputWidth 
    * of 400 and an outputHeight of 50, the image will be squished 
    * and stretched to those dimensions. 
    * @param outputWidth desired width of output Bitmap in pixels 
    * @param outputHeight desired height of output Bitmap in pixels 
    * @return the visible portion of the image in the PanZoomImageView 
    */ 
    public Bitmap getCroppedBitmap(int outputWidth, int outputHeight) { 
     int origX = -1 * (int) mPosX * 2; 
     int origY = -1 * (int) mPosY * 2; 
     int width = (int) (viewWidth/mScaleFactor); 
     int height = (int) (viewHeight/mScaleFactor); 
     Log.e(TAG, "origX: " + origX + " origY: " + origY + " width: " + width + " height: " + height + " outputWidth: " + outputWidth + " outputHeight: " + outputHeight + "getLayoutParams().width: " + getLayoutParams().width + " getLayoutParams().height: " + getLayoutParams().height); 
     Bitmap b = Bitmap.createBitmap(bitmap, origX, origY, width, height); 

     if (outputWidth > 0 && outputWidth > 0) { 
      //Use the exact dimensions given--chance this won't match the aspect ratio 
      b = Bitmap.createScaledBitmap(b, outputWidth, outputHeight, true); 
     } 

     return b; 
    } 

     @Override 
     protected void onSizeChanged(int w, int h, int oldw, int oldh) { 
      super.onSizeChanged(w, h, oldw, oldh); 
      viewHeight = h; 
      viewWidth = w; 
     } 
} 
+0

Tôi không có bitmap. Tôi vẽ một hình chữ nhật bằng canvas. Làm cách nào để đặt minX Khi tôi di chuyển canvas để nó không vượt quá giới hạn – Prabs

0
mScaleFactor = Math.max(0.1f, Math.min(mScaleFactor, 5.0f)); 

Bằng cách này bạn có thể mở rộng lên đến 5 lần, tính toán quy mô tối đa dựa trên kích thước mà bạn muốn và thêm vào đó thay vì sử dụng 5 tất cả các thời gian.

Ngoài ra khi tôi làm việc với thu phóng pinch trên dự án, tôi thấy dễ sử dụng hơn nếu bạn sử dụng giá trị tuyệt đối thay vì nhân. Chỉ cần nhận được khoảng cách của các ngón tay trên các liên lạc đầu tiên và khi di chuyển các ngón tay, tính toán khoảng cách và sau đó quy mô dựa trên khoảng cách đầu tiên. Bằng cách này nó theo các ngón tay tốt hơn và hoạt động tốt hơn khi bạn giới hạn quy mô tối thiểu và tối đa.

Các vấn đề liên quan