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

作者:goldensun 时间:2023-04-01 16:41:17 

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


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,图片
0
投稿

猜你喜欢

  • 带着问题读CLR via C#(笔记二)类型基础

    2022-01-31 07:31:08
  • c#中SAPI使用总结——SpVoice的使用方法

    2022-02-20 09:50:07
  • c# 通过wbemtest和WMI Code Cretor更加高效的访问WMI

    2022-11-17 16:30:16
  • 详解C#如何实现隐式类型转换

    2022-08-30 06:32:42
  • Android CardView+ViewPager实现ViewPager翻页动画的方法

    2022-10-19 00:44:57
  • Android实现ListView数据动态加载的方法

    2021-11-02 23:01:02
  • Java创建表格实例详解 <font color=red>原创</font>

    2022-12-22 04:59:13
  • 如何基于LoadingCache实现Java本地缓存

    2023-04-02 00:14:55
  • SpringCloud微服务之Hystrix组件实现服务熔断的方法

    2021-12-04 16:30:45
  • SpringMVC源码解读之 HandlerMapping - AbstractDetectingUrlHandlerMapping系列初始化

    2023-02-12 16:14:21
  • JavaWeb Servlet实现文件上传与下载功能实例

    2023-06-16 16:41:27
  • java 引用类型的数据传递的是内存地址实例

    2023-11-29 15:13:53
  • Java绘制迷宫动画并显示的示例代码

    2022-04-06 22:37:45
  • 深入理解TextView实现Rich Text--在同一个TextView设置不同字体风格

    2023-05-25 04:52:01
  • Java Spring Controller 获取请求参数的几种方法详解

    2023-04-07 02:11:17
  • C#开启线程的四种示例

    2022-06-01 13:04:37
  • 关于Spring源码是如何解决Bean的循环依赖

    2023-09-16 14:15:17
  • 详解App保活实现原理

    2022-01-20 17:59:10
  • 基于C#技术实现身份证识别功能

    2023-10-01 14:16:26
  • Android TextView实现跑马灯效果的方法

    2023-07-30 20:44:12
  • asp之家 软件编程 m.aspxhome.com