如何使用ImageView寫滑動跟縮放的功能

忘記從哪個網站下載的程式碼, 我改了一下, 變成自己的版本。 如果要處理一個圖片的滑動跟縮放, 要怎麼寫呢?
 一開始正常狀態
然後用兩根手指拉開或者按下放大鍵
兩根手指縮近或者按下縮小鍵
單一手指點圖片, 往左拉
會自動彈回最初的位置
一開始我們就布局好, 整個畫面只有一個ImageView, 以及兩個放大縮小的按鈕。
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_gravity="center" 
    android:orientation="vertical" 
    android:layout_width="fill_parent" 
    android:layout_height="fill_parent">
    <FrameLayout 
        android:layout_width="fill_parent" 
        android:layout_height="fill_parent">

        <ImageView 
           android:layout_gravity="center" 
           android:id="@+id/image_view" 
           android:layout_width="fill_parent" 
           android:layout_height="fill_parent" 
           android:scaleType="matrix" 
           android:adjustViewBounds="true" />


        <LinearLayout 
            android:layout_gravity="bottom|right|center" 
            android:orientation="horizontal" 
            android:layout_width="wrap_content" 
            android:layout_height="wrap_content" 
            android:height="20.0dip">
            <ImageButton 
                android:id="@+id/zoomInButton" 
                android:background="@null" 
                android:paddingRight="5.0dip" 
                android:layout_width="wrap_content" 
                android:layout_height="wrap_content" 
                android:src="@drawable/zoom_in" 
                android:text="In" />
            <ImageButton 
                android:id="@+id/zoomOutButton" 
                android:background="@null" 
                android:paddingLeft="5.0dip" 
                android:layout_width="wrap_content" 
                android:layout_height="wrap_content" 
                android:src="@drawable/zoom_out" 
                android:text="Out" />
        </LinearLayout>
    </FrameLayout>
</LinearLayout>

接下來把這些元件從Activity的類別拿出來。
public class MainActivity extends Activity {
 private ImageView imageView;
 private ImageButton zoomInButton;
 private ImageButton zoomOutButton;

 @Override
 protected void onCreate(Bundle savedInstanceState) {
           // TODO Auto-generated method stub
           super.onCreate(savedInstanceState);
           setContentView(R.layout.activity_main);

           imageView = (ImageView)findViewById(R.id.image_view);
           zoomInButton = (ImageButton)findViewById(R.id.zoomInButton);
           zoomOutButton = (ImageButton)findViewById(R.id.zoomOutButton);    
       }
}

接著我們來新增一個類別叫做ImageViewHelper.java, 用來處理ImageView的移動跟縮放, 把Activity的三個元件由建構子傳入。 由於之後要操作圖片, 這時候需要判斷螢幕大小, 以及圖片的大小, 所以我們在建構子多加傳入兩個參數,分別是DisplayMetrics以及Bitmap的物件。
public ImageViewHelper(DisplayMetrics dm,ImageView imageView,Bitmap bitmap, ImageButton zoomInButton, ImageButton zoomOutButton){
    this.dm = dm;
    this.imageView = imageView;
    this.zoomInButton = zoomInButton;
    this.zoomOutButton = zoomOutButton;
    this.bitmap = bitmap;
}
在操作一張圖片, 最簡單的方式就是利用Matrix這個類別所產生的物件來操作, 一開始我們就先寫放大跟縮小的方法。
    public void setZoomIn(){
        minScaleR = Math.min(
            (float) dm.widthPixels / (float) bitmap.getWidth(),
            (float) dm.heightPixels / (float) bitmap.getHeight());
        if (minScaleR < 1.0) {
            matrix.postScale(minScaleR+1f, minScaleR+1f);
        }
        else{
            matrix.postScale(minScaleR, minScaleR);
        }
    }
    public void setZoomOut(){
        minScaleR = Math.max(
            (float) dm.widthPixels / (float) bitmap.getWidth(),
            (float) dm.heightPixels / (float) bitmap.getHeight());
        if (minScaleR > 1.0) {
            matrix.postScale((minScaleR-(int)minScaleR),(minScaleR-(int)minScaleR));
        }
        else{
            matrix.postScale(minScaleR, minScaleR);
        }
    }
然後圖片如果超出螢幕範圍, 則自動跳回中心點。
    //橫向、縱向置中
    public void center(boolean horizontal, boolean vertical) {

        Matrix m = new Matrix();
        m.set(matrix);
        RectF rect = new RectF(0, 0, bitmap.getWidth(), bitmap.getHeight());
        m.mapRect(rect);

        float height = rect.height();
        float width = rect.width();

        float deltaX = 0, deltaY = 0;

        if (vertical) {
         // 圖片小於螢幕大小,則置中顯示。
         //大於螢幕,上方則留空白則往上移,下方留空白則往下移
            int screenHeight = dm.heightPixels;
            if (height < screenHeight) {
                deltaY = (screenHeight - height) / 2 - rect.top;
            } else if (rect.top > 0) {
                deltaY = -rect.top;
            } else if (rect.bottom < screenHeight) {
                deltaY = imageView.getHeight() - rect.bottom;
            }
        }

        if (horizontal) {
            int screenWidth = dm.widthPixels;
            if (width < screenWidth) {
                deltaX = (screenWidth - width) / 2 - rect.left;
            } else if (rect.left > 0) {
                deltaX = -rect.left;
            } else if (rect.right < screenWidth) {
                deltaX = screenWidth - rect.right;
            }
        }
        matrix.postTranslate(deltaX, deltaY);
    }
接著將傳入的zoomin,zoomout的元件加入事件。
zoomInButton.setOnClickListener(new OnClickListener(){

    @Override
    public void onClick(View v) {
        // TODO Auto-generated method stub
        setZoomIn();
        center();
        imageView.setImageMatrix(matrix);
    }
         
});
zoomOutButton.setOnClickListener(new OnClickListener(){

    @Override
    public void onClick(View v) {
        // TODO Auto-generated method stub
        setZoomOut();
        center();
        imageView.setImageMatrix(matrix);
    }
         
});
你會發現center()這個方法從哪來的, 其實是呼叫center(true,true)的。
    public void center(){
        center(true, true);
    }


再來就是最後一個元件, 也是我們的主角, ImageView加入事件,
一開始mode變數來存放目前是屬於什麼模式,
當手指按下去, 先讓模式變成拖曳(Drag),
而移動的時候, 假設模式是拖曳, 則我們讓matrix的x,y軸進行變化。

如果兩點觸控, 就會變成縮放的模式, 根據兩指的距離來判斷是放大還是縮小。

imageView.setOnTouchListener(new OnTouchListener(){
    @Override
    public boolean onTouch(View arg0, MotionEvent event) {

        switch (event.getAction() & MotionEvent.ACTION_MASK) {

        case MotionEvent.ACTION_DOWN:

            savedMatrix.set(matrix);

            prev.set(event.getX(), event.getY());

            mode = DRAG;

            break;



        case MotionEvent.ACTION_POINTER_DOWN:

            dist = spacing(event);

            // 如果兩點距離超過10, 就判斷為多點觸控模式 即為縮放模式

            if (spacing(event) > 10f) {

                savedMatrix.set(matrix);

                midPoint(mid, event);

                mode = ZOOM;

            }

            break;

        case MotionEvent.ACTION_UP:

        case MotionEvent.ACTION_POINTER_UP:

            mode = NONE;

            break;

        case MotionEvent.ACTION_MOVE:

            if (mode == DRAG) {

                matrix.set(savedMatrix);

                matrix.postTranslate(event.getX() - prev.x, event.getY()

                        - prev.y);

            } else if (mode == ZOOM) {

                float newDist = spacing(event);//偵測兩根手指移動的距離

                if (newDist > 10f) {

                    matrix.set(savedMatrix);

                    float tScale = newDist / dist;

                    matrix.postScale(tScale, tScale, mid.x, mid.y);
                }
            }

            break;

        }

        imageView.setImageMatrix(matrix);

        center();

        return true;

    }
});

而上面有spacing()是計算兩點距離, midPoint()是測量兩點的中點。

    //兩點的距離
    public 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);

    }



    //兩點的中點

    public 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);

    }

最後在建構子裡面, 對一開始的圖片進行一些初始化的動作。
public ImageViewHelper(DisplayMetrics dm,ImageView imageView,Bitmap bitmap, ImageButton zoomInButton, ImageButton zoomOutButton){

     this.dm = dm;
     this.imageView = imageView;
     this.zoomInButton = zoomInButton;
     this.zoomOutButton = zoomOutButton;
     this.bitmap = bitmap;

     setImageSize();
     minZoom();
     center();
     imageView.setImageMatrix(matrix);

}

minZoom()是為了將圖片跟螢幕做比較好的調整。
    //取得最小的比例, 假設圖片比螢幕大
    //則螢幕(寬/長)/圖片(寬/長)會小於1 那麼也就是將圖片進行縮小
    //反之 則進行放大 而圖片越小 放大倍數則會越大
    //如果螢幕跟圖片大小相同 則倍數會為1 即不變
    public void minZoom() {

        minScaleR = Math.min(

                (float) dm.widthPixels / (float) bitmap.getWidth(),

                (float) dm.heightPixels / (float) bitmap.getHeight());

        if (minScaleR <= 1.0) {

            matrix.postScale(minScaleR, minScaleR);

        }

        else{

         matrix.postScale(1.5f, 1.5f);

       }

   }


最後回到MainActivity.java, 將ImageViewHelper.java所需要的參數傳進去,
就可以把一張圖進行一些操作了。
@Override
protected void onCreate(Bundle savedInstanceState) {

     // TODO Auto-generated method stub
     super.onCreate(savedInstanceState);
     setContentView(R.layout.activity_main);
     dm = new DisplayMetrics();
     getWindowManager().getDefaultDisplay().getMetrics(dm);

     imageView = (ImageView)findViewById(R.id.image_view);
     zoomInButton = (ImageButton)findViewById(R.id.zoomInButton);
     zoomOutButton = (ImageButton)findViewById(R.id.zoomOutButton);

     bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.cat);
     imageView.setImageBitmap(bitmap);
     new ImageViewHelper(dm,imageView,bitmap,zoomInButton,zoomOutButton);    

}



程式碼
http://uploadingit.com/file/naplkvdnpsrkmqry/DragImageDemo.zip