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

 更新时间:2016年03月19日 16:13:43   投稿:goldensun  
这篇文章主要介绍了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> 

相关文章

  • Android中webView加载H5绑定cookie实例

    Android中webView加载H5绑定cookie实例

    这篇文章主要介绍了Android中webView加载H5绑定cookie实例,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧
    2020-03-03
  • android 如何判断当前是否为飞行模式

    android 如何判断当前是否为飞行模式

    android 开发过程中如何判断当前是否是飞行模式和侦听airplane mode change,本文将以此问题详细介绍,需要了解的朋友可以参考下
    2012-11-11
  • Android中BaseAdapter的用法分析与理解

    Android中BaseAdapter的用法分析与理解

    这篇文章主要介绍了Android中BaseAdapter的用法分析与理解,结合一个项目开发中BaseAdapter的使用分析了BaseAdapter的功能、作用及用法理解,需要的朋友可以参考下
    2016-08-08
  • 蓝牙原理Android代码实现

    蓝牙原理Android代码实现

    这篇文章主要为大家详细介绍了蓝牙原理Android代码实现,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2016-09-09
  • Android实现微信的图片选择器

    Android实现微信的图片选择器

    这篇文章主要为大家详细介绍了Android实现微信的图片选择器,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2017-12-12
  • android生命周期深入分析(一)

    android生命周期深入分析(一)

    Android 程序的生命周期是由系统控制而非程序自身直接控制,本文将详细介绍,需要的朋友可以参考下
    2012-12-12
  • Android图片加载利器之Picasso基本用法

    Android图片加载利器之Picasso基本用法

    这篇文章主要为大家详细介绍了Android图片加载利器之Picasso的基本用法,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2017-03-03
  • Android设置TextView显示指定个数字符,超过部分显示...(省略号)的方法

    Android设置TextView显示指定个数字符,超过部分显示...(省略号)的方法

    这篇文章主要介绍了Android设置TextView显示指定个数字符,超过部分显示...(省略号)的方法,涉及Android TextView属性设置的相关技巧,需要的朋友可以参考下
    2016-02-02
  • 深入浅析Android JSON解析

    深入浅析Android JSON解析

    android中网络数据传输是经常被用到的,通常我们使用xml或者json,而json更加轻量,便捷,我们使用的更多。我自己在项目中使用很多,今天就说说android中怎么去解析JSON,帮助自己总结内容,同时帮助别人少走弯路
    2015-12-12
  • 详解Android的反编译和代码混淆

    详解Android的反编译和代码混淆

    本篇文章主要介绍了详解Android的反编译和代码混淆,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2017-11-11

最新评论