Example of double click zoom in and zoom out in Android App

  • 2021-06-28 09:42:46
  • OfStack

Let's start with a very simple zoom method for core pictures:


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

Note to set the scale correctly or you may run out of memory, such as when you used to zoom in pictures:


java.lang.IllegalArgumentException: bitmap size exceeds 32bits

Later, a line of code checked, found that the original scale scale calculation error, zoomed in the original image more than 20 times, resulting in memory overflow, after the scale value was modified normally.

Okay, here's a real look at the module that implements both zoom-in and zoom-in levels.
Functions are:

Zoom in centered on the touch point (this is not available with other code on the web) Boundary control (this is not available with other code on the web) Double-click to zoom in or out (mainly considering resistance screens) Multipoint touch zoom in and out

This module has passed the test and has been in use for some time. It is stable.

Following is the code and how to use it (no test project written, excuse me):

ImageControl is similar to a user-defined ImageView control.Usage will be pasted out in the code below.


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) { 
      // 如果两次点击时间间隔小于1定值,则默认为双击事件 
      if (event.getEventTime() - lastClickTime < DOUBLE_CLICK_TIME_SPACE) { 
        changeSize(startX, startY); 
      } else if (isBig) { 
        mode = DRAG; 
      } 
    } 
 
    lastClickTime = event.getEventTime(); 
  } 
 
  /** 
   * 非第1个点按下操作 
   * 
   * @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 This Activity for testing


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(" Picture Test "); 
    //  Here you can imgcontrol Image Path Dynamic Assignment  
    // ............ 
     
    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) { 
              //  Controls whether the title is displayed when the picture is zoomed in or out  
              if (currentStatus) { 
                llTitle.setVisibility(View.GONE); 
              } else { 
                llTitle.setVisibility(View.VISIBLE); 
              } 
            } 
          }); 
    } 
    else 
    { 
      Toast.makeText(ImageViewActivity.this, " Picture loading failed, please try again later! ", Toast.LENGTH_SHORT) 
          .show(); 
    } 
 
  } 
 
  @Override 
  public boolean onTouchEvent(MotionEvent event) { 
    switch (event.getAction() & MotionEvent.ACTION_MASK) { 
    case MotionEvent.ACTION_DOWN: 
      imgControl.mouseDown(event);       
      break; 
 
    /** 
     *  Not No. 1 Point press  
     */ 
    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); 
  } 
} 

There are two things to note in the code above.1Activity overrides the onTouchEvent method to pass touch events to ImageControl, which is similar to the routing event mechanism in WPF.2 Initialize imgControl, imgControl.imageInit, noting the parameters.The last parameter is similar to the delegate in C#and I use the interface here to uninstall this method for all operations to be performed while zooming in and out of the switch.


common_image_view.xml Layout File


<?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=" Presentation " /> 
  </LinearLayout> 
 
</RelativeLayout> 


Related articles: