2014年2月7日

Android 寫一個 ImageViewHelper 來處理圖片放大縮小等功能 (matrix)

寫一個 ImageViewHelper 來處理圖片放大縮小等功能 (matrix)

上次小弟有介紹 使用gridview 顯示相簿中的縮圖 http://blog.maxkit.com.tw/2014/01/android-gridview.html 在當時的範例中提到,我們可以點擊縮相簿中的縮圖,將圖片放大顯示,這部份主要是透過ImageView的來實現,但是看來看去就覺得「它很陽春」,沒錯,真的很陽春。 我們還可以做點什麼功能呢? 是的,一般的相本功能中,顯示圖片時不是可以放大縮小,還可以拖拉位置嗎? 後來發現android 提供了一個matrix的元件,可以用來實現Scale image, 我們在上次的project中,再增加一個ImageViewHelper.java檔案,帶入imageview來擴充它的功能。

ImageViewHelper.java

package com.example.gridview;

import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Matrix;
import android.graphics.PointF;
import android.graphics.RectF;
import android.util.DisplayMetrics;
import android.util.FloatMath;
import android.view.MotionEvent;
import android.view.View;
import android.view.View.OnTouchListener;
import android.widget.ImageView;

public class ImageViewHelper {

     private ImageView imageView;
     private Matrix matrix = new Matrix();
     private Matrix savedMatrix = new Matrix();
     private Bitmap bitmap;

     private static final int NONE = 0;// 初始狀態
     private static final int DRAG = 1;// 拖曳狀態
     private static final int ZOOM = 2;// 縮放狀態

     private int mode = NONE;
     private int lastaction = 0;

     private PointF pA = new PointF();
     private PointF pB = new PointF();
     private PointF prev = new PointF();
     private PointF mid = new PointF();
     private PointF lastClickPos = new PointF();

     private float dist = 1f;
     private DisplayMetrics dm;
     private float minScaleR = 0.1f; // 最少缩放比例
     private static final float MAX_SCALE = 10f; // 最大缩放比例
     private static final float DOUBLECLICK_SCALE = 2f; // 雙擊放大比例

     long lastClickTime = 0;
     float viewW;
     float viewH;
     private long down_time = 0;

     /**
      * 紀錄圖片狀態是否超出螢幕
      */
     private boolean isZoom;

     public ImageViewHelper(DisplayMetrics dm, ImageView imageView, Bitmap bitmap) {
            this. dm = dm;
            this. imageView = imageView;
            this. bitmap = bitmap;
           setImageSize();
           minZoom();
           center();
           imageView.setImageMatrix( matrix);
            isZoom = false;

     }

     public Matrix getMatrix() {
            return matrix;
     }

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

           Context c = imageView .getContext();

            minScaleR = Math. min(
                     ( float) dm. widthPixels / ( float) bitmap.getWidth(),
                     ( float) dm. heightPixels / ( float) bitmap.getHeight());

            if ( minScaleR > 1) {
                 minScaleR = 1;
           }
            matrix.postScale( minScaleR, minScaleR);

     }

     public void center() {
           center( true, true);
     }

     // 橫向、縱向置中
     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;

            isZoom = true;

            if (vertical) {
                 // 圖片小於螢幕大小,則置中顯示。
                 // 大於螢幕,上方則留空白則往上移,下方留空白則往下移
                 int screenHeight = dm. heightPixels;
                 if (height < screenHeight) {
                     deltaY = (screenHeight - height) / 2 - rect.top;
                      isZoom = false;
                } 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;
                      isZoom = false;
                } else if (rect. left > 0) {
                     deltaX = -rect. left;
                } else if (rect. right < screenWidth) {
                     deltaX = screenWidth - rect. right;
                }
           }
            matrix.postTranslate(deltaX, deltaY);
     }

     // 兩點的距離
     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 float spacing( float x1, float y1, float x2, float y2) {
            float x = x1 - x2;
            float y = y1 - y2;
            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);
     }

     protected void doubleClick( float x, float y) {
            float p[] = new float[9];
            matrix.getValues(p);

            if ( mode == DRAG) {
                 if (p[0] > minScaleR) {
                      matrix.setScale( minScaleR, minScaleR);
                } else {
                      matrix.setScale( DOUBLECLICK_SCALE, DOUBLECLICK_SCALE, x, y);
                }
                center();
           }
            imageView.setImageMatrix( matrix);

     }

     // 限制最大最小缩放比例
     protected void CheckScale() {
            float p[] = new float[9];
            matrix.getValues(p);
            if ( mode == ZOOM) {
                 if (p[0] < minScaleR) {
                      matrix.setScale( minScaleR, minScaleR);
                }
                 if (p[0] > MAX_SCALE) {
                      matrix.set( savedMatrix);
                }
           }
     }

     // 建立圖片移動縮放事件
     public void setImageSize() {

            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());
                            // pA.set(event.getX(), event.getY());
                            // pB.set(event.getX(), event.getY());
                            mode = DRAG; // 設定為拖拉模式
                            down_time = System.currentTimeMillis();
                            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:

                            if ( mode == DRAG) {

                                 long now = System.currentTimeMillis();

                                 if ((now - lastClickTime) < 300) {
                                     Context c = imageView.getContext();
                                      // Toast.makeText(c, "click time:" + (now -
                                      // lastClickTime) , Toast.LENGTH_SHORT).show();
                                     doubleClick(event.getX(), event.getY());
                                     now = 0;
                                } else {

                                      if (now - down_time >= 300
                                                && now - down_time < 1000) {
                                           Context c = imageView.getContext();
                                     }
                                }

                                 lastClickTime = now;

                           }
                            mode = NONE;
                            break;
                      case MotionEvent. ACTION_MOVE:

                            if ( mode == DRAG && isZoom) { // 超出圖片
                                 // if (isZoom) { // 超出圖片
                                 matrix.set( savedMatrix);
                                 matrix.postTranslate(event.getX() - prev. x,
                                           event.getY() - prev. y);
                                 // matrix.postTranslate(event.getX() - prev.x, 0);
                                arg0.getParent().requestDisallowInterceptTouchEvent(
                                            true); // 父類別onTouch()不可使用,圖片放大後才能左右查看圖片

                           } 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);
                     CheckScale(); // 限制缩放范围
                     center();
                      lastaction = event.getAction();
                      return true;
                }

           });

     }
}

這個檔案實作後,原來的MainActivity.java 還是得調整一下

package com.example.gridview;

import java.util.ArrayList;
import java.util.List;

import android.app.Activity;
import android.content.ClipData.Item;
import android.content.ContentResolver;
import android.database.Cursor;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.os.Bundle;
import android.provider.MediaStore;
import android.util.DisplayMetrics;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.widget.GridView;
import android.widget.ImageView;

public class MainActivity extends Activity {

  private GridView gridView ;
  private ImageView imageView;
  private List<String> thumbs;
  private List<String> imagePaths;
  private ImageAdapter imageAdapter;
  private Item photos;
  @Override
  protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
       setContentView(R.layout. activity_main);

        gridView = (GridView) findViewById(R.id. gridView1);
        imageView = (ImageView) findViewById(R.id.imageView1 );

       ContentResolver cr = getContentResolver();
       String[] projection = { MediaStore.Images.Media. _ID,
                 MediaStore.Images.Media. DATA };

       Cursor cursor = cr.query(MediaStore.Images.Media.EXTERNAL_CONTENT_URI ,
                 projection, null, null, null);

        thumbs = new ArrayList<String>();
        imagePaths = new ArrayList<String>();
        for ( int i = 0; i < cursor.getCount(); i++) {

            cursor.moveToPosition(i);
             int id = cursor.getInt(cursor
                      .getColumnIndex(MediaStore.Images.Media. _ID)); // �Y��ID
             thumbs.add(id + "");

            String filepath = cursor.getString(cursor
                      .getColumnIndex(MediaStore.Images.Media. DATA));

             imagePaths.add(filepath);
       }

       cursor.close();

        imageAdapter = new ImageAdapter(MainActivity. this, thumbs);
        gridView .setAdapter(imageAdapter );
        imageAdapter.notifyDataSetChanged();

//         imageView.setOnClickListener(new OnClickListener() {
//
//              @Override
//              public void onClick(View v) {
//                   // TODO Auto-generated method stub
//                   imageView.setVisibility(View.GONE);
//                   gridView.setVisibility(View.VISIBLE);
//              }
//
//         });
        imageView.setVisibility(View. GONE);

 }

  @Override
  public boolean onCreateOptionsMenu(Menu menu) {
        // Inflate the menu; this adds items to the action bar if it is present.
       getMenuInflater().inflate(R.menu. main, menu);
        return true;
 }
  @Override
  public boolean onOptionsItemSelected(MenuItem item) {
     // Handle item selection
     switch (item.getItemId()) {
         case R.id. action_photos:
             imageView.setVisibility(View. GONE);
                  gridView .setVisibility(View. VISIBLE);
             return true;
         default:
             return super.onOptionsItemSelected(item);
     }
 }

  public void setImageView( int position) {
       Bitmap bm = BitmapFactory.decodeFile( imagePaths.get(position));
        imageView.setImageBitmap(bm);
        imageView.setVisibility(View. VISIBLE);
        gridView .setVisibility(View. GONE);
       DisplayMetrics dm = this.getResources().getDisplayMetrics();
        new ImageViewHelper(dm, imageView, bm);
 }
}

我在ImageViewHelper的 imageView.setOnTouchListener 加上了雙點還原、放大及兩點放大縮小的事件, 於是MainActivity的imageView.setOnClickListener 就失效了,於是我把它onclick的事件移除,用menu的按鈕來取代。

menu的作法請看程式碼中 onCreateOptionsMenu(Menu menu) {…} 、onOptionsItemSelected(MenuItem item) {...} 這兩段。

main.xml請放到res/menu的資料中

< menu xmlns:android= "http://schemas.android.com/apk/res/android" >

    <item
        android:id= "@+id/action_photos"
        android:orderInCategory= "100"
        android:showAsAction= "never"
        android:title= "@string/action_photos" />

</ menu>

如果你做到這裡發現........... 根本就不能放大縮小嘛! 那可能是跟我一樣忽略了一個地方 請檢查xml中imageview的設定屬性,是否少了一個 ## android:scaleType = "matrix" ##,好,趕快加上去再試試看吧。