欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  移动技术

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

程序员文章站 2024-02-28 10:22:16
先来看一个很简单的核心图片缩放方法: public static bitmap scale(bitmap bitmap, float scalewidth,...

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

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>