Android App中实现可以双击放大和缩小图片功能的实例

先来看一个很简单的核心图片缩放方法:

public static Bitmap scale(Bitmap bitmap, float scaleWidth, float scaleHeight) { 
  int width = bitmap.getWidth(); 
  int height = bitmap.getHeight(); 
  Matrix matrix = new Matrix(); 
  matrix.postScale(scaleWidth, scaleHeight); 
  Log.i(TAG, "scaleWidth:"+ scaleWidth +", scaleHeight:"+ scaleHeight); 
  return Bitmap.createBitmap(bitmap, 0, 0, width, height, matrix, true); 
} 

注意要比例设置正确否则可能会内存溢出,比如曾经使用图片缩放时遇到这么个问题:

java.lang.IllegalArgumentException: bitmap size exceeds 32bits

后来一行行查代码,发现原来是 scale 的比例计算错误,将原图给放大了 20 多倍,导致内存溢出所致,重新修改比例值后就正常了。

好了,下面真正来看一下这个实现了放大和原大两个级别的缩放的模块。
功能有:

  • 以触摸点为中心放大(这个是网上其他的代码没有的)
  • 边界控制(这个是网上其他的代码没有的)
  • 双击放大或缩小(主要考虑到电阻屏)
  • 多点触摸放大和缩小

这个模块已经通过了测试,并且用户也使用有一段时间了,是属于比较稳定的了。

下面贴上代码及使用方法(没有写测试项目,大家见谅):

ImageControl 类似一个用户自定义的ImageView控件。用法将在下面的代码中贴出。

import android.content.Context; 
import android.graphics.Bitmap; 
import android.graphics.Matrix; 
import android.util.AttributeSet; 
import android.util.FloatMath; 
import android.view.MotionEvent; 
import android.widget.ImageView; 
 
public class ImageControl extends ImageView { 
  public ImageControl(Context context) { 
    super(context); 
    // TODO Auto-generated constructor stub 
  } 
 
  public ImageControl(Context context, AttributeSet attrs) { 
    super(context, attrs); 
    // TODO Auto-generated constructor stub 
  } 
 
  public ImageControl(Context context, AttributeSet attrs, int defStyle) { 
    super(context, attrs, defStyle); 
    // TODO Auto-generated constructor stub 
  } 
 
  // ImageView img; 
  Matrix imgMatrix = null; // 定义图片的变换矩阵 
 
  static final int DOUBLE_CLICK_TIME_SPACE = 300; // 双击时间间隔 
  static final int DOUBLE_POINT_DISTANCE = 10; // 两点放大两点间最小间距 
  static final int NONE = 0; 
  static final int DRAG = 1; // 拖动操作 
  static final int ZOOM = 2; // 放大缩小操作 
  private int mode = NONE; // 当前模式 
 
  float bigScale = 3f; // 默认放大倍数 
  Boolean isBig = false; // 是否是放大状态 
  long lastClickTime = 0; // 单击时间 
  float startDistance; // 多点触摸两点距离 
  float endDistance; // 多点触摸两点距离 
 
  float topHeight; // 状态栏高度和标题栏高度 
  Bitmap primaryBitmap = null; 
 
  float contentW; // 屏幕内容区宽度 
  float contentH; // 屏幕内容区高度 
 
  float primaryW; // 原图宽度 
  float primaryH; // 原图高度 
 
  float scale; // 适合屏幕缩放倍数 
  Boolean isMoveX = true; // 是否允许在X轴拖动 
  Boolean isMoveY = true; // 是否允许在Y轴拖动 
  float startX; 
  float startY; 
  float endX; 
  float endY; 
  float subX; 
  float subY; 
  float limitX1; 
  float limitX2; 
  float limitY1; 
  float limitY2; 
  ICustomMethod mCustomMethod = null; 
 
  /** 
   * 初始化图片 
   * 
   * @param bitmap 
   *      要显示的图片 
   * @param contentW 
   *      内容区域宽度 
   * @param contentH 
   *      内容区域高度 
   * @param topHeight 
   *      状态栏高度和标题栏高度之和 
   */ 
  public void imageInit(Bitmap bitmap, int contentW, int contentH, 
      int topHeight, ICustomMethod iCustomMethod) { 
    this.primaryBitmap = bitmap; 
    this.contentW = contentW; 
    this.contentH = contentH; 
    this.topHeight = topHeight; 
    mCustomMethod = iCustomMethod; 
    primaryW = primaryBitmap.getWidth(); 
    primaryH = primaryBitmap.getHeight(); 
    float scaleX = (float) contentW / primaryW; 
    float scaleY = (float) contentH / primaryH; 
    scale = scaleX < scaleY ? scaleX : scaleY; 
    if (scale < 1 && 1 / scale < bigScale) { 
      bigScale = (float) (1 / scale + 0.5); 
    } 
 
    imgMatrix = new Matrix(); 
    subX = (contentW - primaryW * scale) / 2; 
    subY = (contentH - primaryH * scale) / 2; 
    this.setImageBitmap(primaryBitmap); 
    this.setScaleType(ScaleType.MATRIX); 
    imgMatrix.postScale(scale, scale); 
    imgMatrix.postTranslate(subX, subY); 
    this.setImageMatrix(imgMatrix); 
  } 
 
  /** 
   * 按下操作 
   * 
   * @param event 
   */ 
  public void mouseDown(MotionEvent event) { 
    mode = NONE; 
    startX = event.getRawX(); 
    startY = event.getRawY(); 
    if (event.getPointerCount() == 1) { 
      // 如果两次点击时间间隔小于一定值,则默认为双击事件 
      if (event.getEventTime() - lastClickTime < DOUBLE_CLICK_TIME_SPACE) { 
        changeSize(startX, startY); 
      } else if (isBig) { 
        mode = DRAG; 
      } 
    } 
 
    lastClickTime = event.getEventTime(); 
  } 
 
  /** 
   * 非第一个点按下操作 
   * 
   * @param event 
   */ 
  public void mousePointDown(MotionEvent event) { 
    startDistance = getDistance(event); 
    if (startDistance > DOUBLE_POINT_DISTANCE) { 
      mode = ZOOM; 
    } else { 
      mode = NONE; 
    } 
  } 
 
  /** 
   * 移动操作 
   * 
   * @param event 
   */ 
  public void mouseMove(MotionEvent event) { 
    if ((mode == DRAG) && (isMoveX || isMoveY)) { 
      float[] XY = getTranslateXY(imgMatrix); 
      float transX = 0; 
      float transY = 0; 
      if (isMoveX) { 
        endX = event.getRawX(); 
        transX = endX - startX; 
        if ((XY[0] + transX) <= limitX1) { 
          transX = limitX1 - XY[0]; 
        } 
        if ((XY[0] + transX) >= limitX2) { 
          transX = limitX2 - XY[0]; 
        } 
      } 
      if (isMoveY) { 
        endY = event.getRawY(); 
        transY = endY - startY; 
        if ((XY[1] + transY) <= limitY1) { 
          transY = limitY1 - XY[1]; 
        } 
        if ((XY[1] + transY) >= limitY2) { 
          transY = limitY2 - XY[1]; 
        } 
      } 
 
      imgMatrix.postTranslate(transX, transY); 
      startX = endX; 
      startY = endY; 
      this.setImageMatrix(imgMatrix); 
    } else if (mode == ZOOM && event.getPointerCount() > 1) { 
      endDistance = getDistance(event); 
      float dif = endDistance - startDistance; 
      if (Math.abs(endDistance - startDistance) > DOUBLE_POINT_DISTANCE) { 
        if (isBig) { 
          if (dif < 0) { 
            changeSize(0, 0); 
            mode = NONE; 
          } 
        } else if (dif > 0) { 
          float x = event.getX(0) / 2 + event.getX(1) / 2; 
          float y = event.getY(0) / 2 + event.getY(1) / 2; 
          changeSize(x, y); 
          mode = NONE; 
        } 
      } 
    } 
  } 
 
  /** 
   * 鼠标抬起事件 
   */ 
  public void mouseUp() { 
    mode = NONE; 
  } 
 
  /** 
   * 图片放大缩小 
   * 
   * @param x 
   *      点击点X坐标 
   * @param y 
   *      点击点Y坐标 
   */ 
  private void changeSize(float x, float y) { 
    if (isBig) { 
      // 如果处于最大状态,则还原 
      imgMatrix.reset(); 
      imgMatrix.postScale(scale, scale); 
      imgMatrix.postTranslate(subX, subY); 
      isBig = false; 
    } else { 
      imgMatrix.postScale(bigScale, bigScale); // 在原有矩阵后乘放大倍数 
      float transX = -((bigScale - 1) * x); 
      float transY = -((bigScale - 1) * (y - topHeight)); // (bigScale-1)(y-statusBarHeight-subY)+2*subY; 
      float currentWidth = primaryW * scale * bigScale; // 放大后图片大小 
      float currentHeight = primaryH * scale * bigScale; 
      // 如果图片放大后超出屏幕范围处理 
      if (currentHeight > contentH) { 
        limitY1 = -(currentHeight - contentH); // 平移限制 
        limitY2 = 0; 
        isMoveY = true; // 允许在Y轴上拖动 
        float currentSubY = bigScale * subY; // 当前平移距离 
        // 平移后,内容区域上部有空白处理办法 
        if (-transY < currentSubY) { 
          transY = -currentSubY; 
        } 
        // 平移后,内容区域下部有空白处理办法 
        if (currentSubY + transY < limitY1) { 
          transY = -(currentHeight + currentSubY - contentH); 
        } 
      } else { 
        // 如果图片放大后没有超出屏幕范围处理,则不允许拖动 
        isMoveY = false; 
      } 
 
      if (currentWidth > contentW) { 
        limitX1 = -(currentWidth - contentW); 
        limitX2 = 0; 
        isMoveX = true; 
        float currentSubX = bigScale * subX; 
        if (-transX < currentSubX) { 
          transX = -currentSubX; 
        } 
        if (currentSubX + transX < limitX1) { 
          transX = -(currentWidth + currentSubX - contentW); 
        } 
      } else { 
        isMoveX = false; 
      } 
 
      imgMatrix.postTranslate(transX, transY); 
      isBig = true; 
    } 
 
    this.setImageMatrix(imgMatrix); 
    if (mCustomMethod != null) { 
      mCustomMethod.customMethod(isBig); 
    } 
  } 
 
  /** 
   * 获取变换矩阵中X轴偏移量和Y轴偏移量 
   * 
   * @param matrix 
   *      变换矩阵 
   * @return 
   */ 
  private float[] getTranslateXY(Matrix matrix) { 
    float[] values = new float[9]; 
    matrix.getValues(values); 
    float[] floats = new float[2]; 
    floats[0] = values[Matrix.MTRANS_X]; 
    floats[1] = values[Matrix.MTRANS_Y]; 
    return floats; 
  } 
 
  /** 
   * 获取两点间的距离 
   * 
   * @param event 
   * @return 
   */ 
  private float getDistance(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); 
  } 
 
  /** 
   * @author Administrator 用户自定义方法 
   */ 
  public interface ICustomMethod { 
    public void customMethod(Boolean currentStatus); 
  } 
} 

 

ImageVewActivity 这个用于测试的Activity

import android.app.Activity; 
import android.graphics.Bitmap; 
import android.graphics.Rect; 
import android.graphics.drawable.BitmapDrawable; 
import android.os.Bundle; 
import android.view.MotionEvent; 
import android.view.View; 
import android.widget.LinearLayout; 
import android.widget.TextView; 
import android.widget.Toast; 
import ejiang.boiler.ImageControl.ICustomMethod; 
import ejiang.boiler.R.id; 
 
public class ImageViewActivity extends Activity { 
 
  @Override 
  protected void onCreate(Bundle savedInstanceState) { 
    // TODO Auto-generated method stub 
    super.onCreate(savedInstanceState); 
    setContentView(R.layout.common_image_view); 
    findView(); 
  } 
 
  public void onWindowFocusChanged(boolean hasFocus) { 
    super.onWindowFocusChanged(hasFocus); 
    init(); 
  } 
 
  ImageControl imgControl; 
  LinearLayout llTitle; 
  TextView tvTitle; 
 
  private void findView() { 
    imgControl = (ImageControl) findViewById(id.common_imageview_imageControl1); 
    llTitle = (LinearLayout) findViewById(id.common_imageview_llTitle); 
    tvTitle = (TextView) findViewById(id.common_imageview_title); 
  } 
 
  private void init() { 
    tvTitle.setText("图片测试"); 
    // 这里可以为imgcontrol的图片路径动态赋值 
    // ............ 
     
    Bitmap bmp; 
    if (imgControl.getDrawingCache() != null) { 
      bmp = Bitmap.createBitmap(imgControl.getDrawingCache()); 
    } else { 
      bmp = ((BitmapDrawable) imgControl.getDrawable()).getBitmap(); 
    } 
    Rect frame = new Rect(); 
    getWindow().getDecorView().getWindowVisibleDisplayFrame(frame); 
    int statusBarHeight = frame.top; 
    int screenW = this.getWindowManager().getDefaultDisplay().getWidth(); 
    int screenH = this.getWindowManager().getDefaultDisplay().getHeight() 
        - statusBarHeight; 
    if (bmp != null) { 
      imgControl.imageInit(bmp, screenW, screenH, statusBarHeight, 
          new ICustomMethod() { 
            
            @Override 
            public void customMethod(Boolean currentStatus) { 
              // 当图片处于放大或缩小状态时,控制标题是否显示 
              if (currentStatus) { 
                llTitle.setVisibility(View.GONE); 
              } else { 
                llTitle.setVisibility(View.VISIBLE); 
              } 
            } 
          }); 
    } 
    else 
    { 
      Toast.makeText(ImageViewActivity.this, "图片加载失败,请稍候再试!", Toast.LENGTH_SHORT) 
          .show(); 
    } 
 
  } 
 
  @Override 
  public boolean onTouchEvent(MotionEvent event) { 
    switch (event.getAction() & MotionEvent.ACTION_MASK) { 
    case MotionEvent.ACTION_DOWN: 
      imgControl.mouseDown(event);       
      break; 
 
    /** 
     * 非第一个点按下 
     */ 
    case MotionEvent.ACTION_POINTER_DOWN: 
     
        imgControl.mousePointDown(event); 
     
      break; 
    case MotionEvent.ACTION_MOVE: 
        imgControl.mouseMove(event); 
       
      break; 
 
    case MotionEvent.ACTION_UP: 
      imgControl.mouseUp(); 
      break; 
 
    } 
 
    return super.onTouchEvent(event); 
  } 
} 

        在上面的代码中,需要注意两点。一Activity中要重写onTouchEvent方法,将触摸事件传递到ImageControl,这点类似于WPF中的路由事件机制。二初始化imgControl即imgControl.imageInit,注意其中的参数。最后一个参数类似于C#中的委托,我这里使用接口来实现,在放大缩小的切换时要执行的操作都卸载这个方法中。


common_image_view.xml  布局文件

<?xml version="1.0" encoding="utf-8"?> 
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" 
  android:id="@+id/rl" 
  android:layout_width="fill_parent" 
  android:layout_height="fill_parent" > 
 
  <ejiang.boiler.ImageControl 
    android:id="@+id/common_imageview_imageControl1" 
    android:layout_width="fill_parent" 
    android:layout_height="fill_parent" 
    android:src="@drawable/ic_launcher" /> 
 
  <LinearLayout 
    android:id="@+id/common_imageview_llTitle" 
    style="@style/reportTitle1" 
    android:layout_alignParentLeft="true" 
    android:layout_alignParentTop="true" > 
 
    <TextView 
      android:id="@+id/common_imageview_title" 
      style="@style/title2" 
      android:layout_width="fill_parent" 
      android:layout_height="wrap_content" 
      android:layout_weight="1" 
      android:text="报告" /> 
  </LinearLayout> 
 
</RelativeLayout>