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>
(0)

相关推荐

  • Android 实现双击退出的功能

    实现android双击后退键退出当前APP功能 实现该功能基本思路是, 1, 监听后退键 , 比较两次后退间隔 , 低于两秒则出发退出 2, 退出当前APP 我选择在基类中BaseActivity 中设置监听,代码如下: public void onBackPressed() { //Preferences 中获取是否双击退出 boolean isDoubleClick = true; //BaseApplication.get("ifDoubleClickedBack", true)

  • Android双击退出的实现方法

    本文实例讲述了Android双击退出的实现方法.分享给大家供大家参考.具体实现方法如下: 方式一: 重写onBackPressed方法直接监听返回键(建议高版本用2.0以上) 复制代码 代码如下: @Override  public void onBackPressed() {               long currentTime = System.currentTimeMillis();           if((currentTime-touchTime)>=waitTime) {

  • Android双击返回键退出程序的实现方法

    本文实例讲述了Android双击返回键退出程序的实现方法,是Android程序开发中一个非常实用的功能,分享给大家供大家参考之用.具体方法如下: 一.实现思路: 用户按下返回键时设定一个定时器来监控是否2秒内实现了退出,如果用户没有接着按返回键,则清除第一次按返回键的效果,使程序还原到第一次按下返回键之前的状态.定时器是每次用户按下返回键才去创建. 二.功能代码: /** * 菜单.返回键响应 */ @Override public boolean onKeyDown(int keyCode,

  • Android 双击返回键退出程序的方法总结

    Android 双击返回键退出程序的方法总结 下面先说说LZ思路,具体如下: 1. 第一种就是根据用户点击俩次的时间间隔去判断是否退出程序; 2. 第二种就是使用Android中计时器(Timer),其实这俩种都差不多. 思路是有了,,,接下来要怎么开搞呢???用户点击肯定会触发相应的事件,,,我们先来看下面俩个事件的作用... Activity.onKeyDown(); 当某个键被按下时会触发,但不会被任何的该Activity内的任何view处理. 默认按下KEYCODE_BACK键后会回到上

  • Android实现双击TitleBar回顶部的功能示例代码

    前言 本文介绍的内容是偶然发现的这个功能,就给移过来了,整理了一下,也是一个类就实现的,使用很方便 特别感谢@TakWolf大大的开源项目,学了好多Android方面的东西 双击返回顶部代码 public class DoubleClickBackToContentTopListener implements View.OnClickListener { private final long delayTime = 300; private long lastClickTime = 0; pri

  • Android中双击返回键退出应用实例代码

    Android中双击返回键退出程序 1.在MyAppliction中(继承Application) //运用list来保存们每一个activity是关键 private List<Activity> mList = new LinkedList<Activity>(); //为了实现每次使用该类时不创建新的对象而创建的静态对象 private static MyApplication instance; //构造方法 public MyApplication() { } //实例化

  • Android 自定义View实现单击和双击事件的方法

    自定义View, 1. 自定义一个Runnable线程TouchEventCountThread ,  用来统计500ms内的点击次数 2. 在MyView中的 onTouchEvent 中调用 上面的线程 3. 自定义一个Handler, 在TouchEventHandler 中 处理 统计到的点击事件, 单击, 双击, 三击, 都可以处理 核心代码如下: public class MyView extends View { ...... // 统计500ms内的点击次数 TouchEvent

  • 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, "s

  • Android App中实现简单的刮刮卡抽奖效果的实例详解

    主要思想: 将一个view设计成多层:背景层,含中奖信息等: 遮盖层,用于刮奖,使用关联一个Bitmap的Canvas 在该Bitmap上,使用它的canvas.drawPath的api来处理 手势滑动(类似刮奖的动作) 使用paint.setXfermode 来进行消除手势滑动区域 public class GuaView extends View { private Bitmap mBitmap; //遮盖的图层 private Canvas mCanvas; //绘制遮盖图层 privat

  • Android实现ImageView图片双击放大及缩小

    本文实例介绍了Android实现ImageView图片双击放大及缩小的相关技巧,分享给大家供大家参考,具体内容如下 public class DoubleScaleImageView extends ImageView implements OnTouchListener, OnGlobalLayoutListener { private boolean isFirst = false; private float doubleScale;// 双击放大的值 private Matrix mSc

  • Android App中进行语言的切换

    本篇简单介绍将在Android App中进行语言的切换和使用dragonFace改系统语言. 切换语言 首先需要在res 中创建个若干个不同的value文件夹(例如:values.values-en.value-ja).然后将不同的String.xml文件. 这里为 中.英.日三语切换.(value文件夹命名可以参考下面) 在res目錄下建立不同名稱的values文件來調用不同的語言包 Values文件匯總如下: 中文(中國):values-zh-rCN中文(台灣):values-zh-rTW

  • 如何在Android App中接入微信支付

    本篇简单介绍Android App中接入微信支付,包括App内支付和扫码支付.分享+支付 pofei 微信支付 wechat 官方接入文档 App内支付 源码下载 主要流程: 1.微信支付平台注册账号​ 注:注册并申请成功以后,需要在API安全中设置你的API密钥 32个字符.建议使用 MD5加密 ,并且需要妥善的保存.因为无法查看. 2.生成预支付订单 3.生成签名参数 4.调起微信,完成支付 扫码支付 扫码支付使用的是微信统一下单API ,使用的是模式二,模式一 一直说URL参数错误,完全按

  • Android实现旋转,放大,缩小图片的方法

    本文实例讲述了Android实现旋转,放大,缩小图片的方法.分享给大家供大家参考,具体如下: 项目中需要做到一个预览图片的功能 最初设想自定义个一个view,在onDraw中用的是生成新的Bitmap,来放大,缩小 但由于手机内存是有限制的,在放大几倍以后,就会core掉. 后面直接选用imageview来完成此项任务,很遗憾,虽然不会重复生成bitmap导致core掉,但是imageview的大小限制是图片无法再放大或放大也只能在这个区域中. 最后选定用 当然  Drawable来做了 pri

  • Android App内监听截图加二维码功能代码

    Android截屏功能是一个常用的功能,可以方便的用来分享或者发送给好友,本文介绍了如何实现app内截屏监控功能,当发现用户在我们的app内进行了截屏操作时,进行对图片的二次操作,例如添加二维码,公司logo等一系列*. 项目地址 测试截图: 截屏原理 android系统并没有提供截屏通知相关的API,需要我们自己利用系统能提供的相关特性变通实现.Android系统有一个媒体数据库,每拍一张照片,或使用系统截屏截取一张图片,都会把这张图片的详细信息加入到这个媒体数据库,并发出内容改变通知,我们可

  • Android App实现闪屏页广告图的全屏显示实例

    目录 1. 适配长屏幕的全面屏 2. 适配刘海屏或者水滴屏 凹形屏幕的显示模式 1. 适配长屏幕的全面屏 至于全屏展示,就得做适配工作,有以下两种方式可进行适配: 在 Android 8.0(API 26)及更高版本中,我们可以在 标签中使用 android:MaxAspectRatio 来声明其支持的屏幕最大宽高比. 比如我们可以声明最大宽高比为 2.4: <!-- Render on full screen up to screen aspect ratio of 2.4 --> <

  • Android开发中RecyclerView模仿探探左右滑动布局功能

    我在此基础上优化了部分代码, 添加了滑动回调, 可自定义性更强. 并且添加了点击按钮左右滑动的功能. 据说无图都不敢发文章了. 看图: 1:这种功能, 首先需要自己管理布局 继承 RecyclerView.LayoutManager , 显示自己管理布局, 比如最多显示4个view, 并且都是居中显示. 底部的View还需要进行缩放,平移操作. public class OverLayCardLayoutManager extends RecyclerView.LayoutManager { p

  • Android开发中ImageLoder加载网络图片时将图片设置为ImageView背景的方法

    本文实例讲述了Android开发中ImageLoder加载网络图片时将图片设置为ImageView背景的方法.分享给大家供大家参考,具体如下: 最近开始接触到android的开发,在开发中使用ImageLoder加载网络图片,但是框架加载的图片默认是通过ImageView的src属性设置,所以在某些场合是不符合需求,比如通过设置src在某些场景下是不能填充满整个ImageView,但是通过设置背景就可以实现,而框架并没有提供将图片设置为背景的方法,我在网上找了半天也看到了一些解决方案,但不是我想

随机推荐