Android App中实现可以双击放大和缩小图片功能的实例
作者:goldensun 发布时间:2023-04-01 16:41:17
标签:Android,图片
先来看一个很简单的核心图片缩放方法:
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>


猜你喜欢
- 问题描述:在用fabric集成后编译出现如下错误,Error:Cause: hostname in certificate didn'
- 最近接触到一个需求要求压缩导出文件,于是乎便要致力于研究一下工具类啦,故也诞生了此篇文章。下面代码中,溪源也将import导入的依赖也贴出来
- Eclipse 最佳字体 推荐:步骤:Eclipse->Windows[窗口]->Preferences[首选项]->Ge
- WHY朋友在群里求助一个问题,问题原型是这样的:String str = "{{10.14, 11.24, 44.55, 41.0
- logback过滤部分日志输出场景使用监控异常日志进行告警时,部分异常日志可能只是不需要告警,但无法通过编码去除时,可以通过不输出这类异常日
- 本文实例为大家分享了java实现TCPSocket聊天室功能的相关代码,供大家参考,具体内容如下1.TCPserver.javaimport
- @AutoConfiguration读取所有jar包下的 /META-INF/spring.factories 并追加到一个 LinkedM
- 本文实例讲述了java中static关键字用法,分享给大家供大家参考。具体分析如下:一、介绍:1、在类中,用static声明的成员变量为静态
- 在上一篇文章中,我为大家介绍了《5种创建文件并写入文件数据的方法》,本节我们为大家来介绍6种从文件中读取数据的方法.另外为了方便大家理解,我
- 据JDK5的新特性,用For循环Map,例如循环Map的Keyfor(String dataKey : paraMap.keySet())&
- 首次使用idea需要配置哪些东西?最近因为我的eclipse无法配置sts,于是将战场转移至idea,首次使用idea,所有的配置都得重新开
- 一、首先将网页内容整个抓取下来,数据放在byte[]中(网络上传输时形式是byte),进一步转化为String,以便于对其操作,实例如下:p
- 目录1、前提知识2、实现思路:1、前提知识需要知道简单的IO流操作,以及简单的UDP发送数据包的原理。需要用到的类:DatagramSock
- 简介在 Java 开发领域,热部署一直是一个难以解决的问题,目前的 Java 虚拟机只能实现方法体的修改热部署,对于整个类的结构修改,仍然需
- 前言在C语言中,没有专门用来表示字符串的类型。C语言的字符串是一系列以’\0’为结尾的字符的集合。虽
- 本文介绍如何通过C#程序代码方法将XML文件转换为Word文档,包括转为.doc /.docx等格式。并附VB.NET代码,有需要可供参考。
- 本文实例总结了Android实现计时与倒计时的常用方法。分享给大家供大家参考,具体如下:方法一Timer与TimerTask(Java实现)
- 前段时间在写直播的时候,需要观众在看直播的时候点赞的效果,在此参照了腾讯大神写的点赞(飘心动画效果)。下面是效果图:1.自定义飘心动画的属性
- 本文实例讲述了Android开发之多媒体文件获取工具类。分享给大家供大家参考,具体如下:package com.android.ocr.ut
- 根据UGUI的射线检测机制获取当前鼠标下的UI:/// <summary> /// 获取鼠标停留处UI