最近在做图库的时候,遇到了图片放大缩小的问题,其实在今后的开发中图片的放大缩小移动的问题是很常见的.
ok 现在来详细探究一下放大缩小究竟要如何去实现, 实现的原理又是怎样
实现的方法其实很简单, 主要用到了一个对象Matrix 图片的缩放移动矩阵对象
Matirx 对象他是一个 3*3 的二位数组, 具体关于该对象的详细描述,后面再说,
先来谈谈如何利用该对象进行放大缩小和移动
大前提,你要想突破随你放大缩小移动, 就得让图片的缩放类型为 Matrix:
this
.setScaleType(ImageView.ScaleType.
MATRIX
);
或在xml 文件中 加入 android:scaleType
=
"matrix"
第一步 :
private
Matrix
matrix
=
new
Matrix();// 先new 一个缩放大矩阵
matrix
=
this
.getImageMatrix(); 获取当前图片的缩放矩阵
第二部:处理该矩阵
1.移动:
matrix
.postTranslate(dx, dy); 表示想x轴移动dx 像素, y轴上移动dy像素
2.放大缩小:
matrix
.postScale(scaleX, scaleY, x, y); //表示
scaleX 表示x方向上的缩放比例
// scaleY 表示Y方向的缩放比例
// 表示缩放说围绕的点
上面两个方法是可以叠加的, 就是你使用了
postTranslate 和
postScale 两者之间不会冲突
ok 上面只是把我们想要缩放处理的矩阵处理好了, 接下来样让图片说缩放和移动
家下来要做的就是把我们设置好的矩阵值 set到ImageView中
this
.setImageMatrix(
matrix
)
ok 缩放移动的 基本的实现方式就是上面讲的, 但是在实际工作共不可能这么简单,
我们对图片处理时,需要配合手势的 来进行放大缩小移动.
而且移动不能太快,太慢,要完全根据你的手指移动比例 或 移动距离来计算,那就不是上面说的获取Matrix然后在设置相关值,在set会去那几行代码可以解决的
很多情况下我们是有通过自定义 ImageView 来更加方便的实现放大缩小移动操作的
下面我自己写的放大缩小的 部分源码
自定义ImageView
public class MyImageView extends ImageView {
// 最大的缩放比
private float maxSacle = 4f;
// 最小的缩放比
private float minScale = 0.25f;
/**
* 构造方法
*/
public MyImageView(Context context) {
super(context);
init();
}
/**
* 构造方法 该View 若写在xml 中则调用从方法
*/
public MyImageView(Context context, AttributeSet attrs) {
super(context, attrs);
init();
this.getImageMatrix();
}
/**
* 初始化方法 , 初始化一些必要信息
*/
public void init() {
// 设置缩放方式为 MATRIX
this.setScaleType(ImageView.ScaleType.MATRIX);
this.setImageMatrix(new Matrix());// 设置 Matrix 防止 ImageView自己缩放
}
private final float[] mMatrixValues = new float[9];
public float getScale() {
// TODO getScale 获取 Scale 暂时使用直接返回 x上的缩放比例 其实到最后进行缩放 的实收 xy 的比例都是一样的
matrix.getValues(mMatrixValues);
float scale = mMatrixValues[matrix.MSCALE_X];
return scale;
}
private Matrix matrix = new Matrix();// 处理 Matrix 的中间项 改项一直在改变
private Matrix baseMatrix = new Matrix();// 保持最初是的状态不变
private final Matrix displayMatrix = new Matrix();// 在重画 显示是使用这个
// 注入进ImageView的是这个值
/**
* 缩放处理的方法 通过手势 触控 计算出来的缩放比例 在穿过来之前 和 之前的Scale想乘了得到了相对于图片本身的放大倍数
* 在手势缩放时使用该方法
*
* @param scale
* 相对于图片本身的放大倍数
*
* @param x
* 缩放 围绕点的坐标x
* @param y
* 缩放 围绕点的坐标y
*/
public void zoomSlowLy(float scale, float x, float y) {
Log.i("scale", "scale*currentScale -- " + scale);
// case1
// 计算缩放比是否符合要求
if (scale > maxSacle) {
scale = maxSacle;
}
if (scale < minScale) {
scale = minScale;
}
// 放入 Matrix 中变换比例 不能使我们计算好的, Matrix 他自己会去计算
// 所以 为了 是 超出缩放范围的 也能正常的进行缩放 就把 之前 乘的getScale 在除回来
// 这样 当 newScale > maxSacle 是 scale 就能等于 maxSacle / getScale();
float purScale = getScale();
scale = scale / purScale;
Log.i("scale", "purScale -- " + purScale);
Log.i("scale", "scale / purScale -- " + scale);
matrix.postScale(scale, scale, x, y);// matrix 矩阵变换 处理缩放
// 把变换完成后的 matrix 在注入回 ImageView中
this.setImageMatrix(getImageViewMatrix());
// TODO 处理图片 是的缩放后的图片能够正常显示在屏幕中央
// center(true, true);
}
/**
* 将图片 垂直 或 水平 居中
*
* @param horizontal
* @param vertical
*/
protected void center(boolean horizontal, boolean vertical) {
if (bitmap == null) {// 如果图片为空直接返回
return;
}
Matrix m = getImageViewMatrix();// 获得当前Matrix;
// 建立与图片适配的矩形
RectF rect = new RectF(0, 0, bitmap.getWidth(), bitmap.getHeight());
m.mapRect(rect); // 与当前的matrix 想匹配
// 匹配后的矩形 的 top button left right 分别表示上边 的Y坐标 ,底边的Y坐标 左边的X坐标 右边的X坐标
float height = rect.height();// 匹配后的 矩形的高度
float width = rect.width();// 匹配后 矩形的宽度
float dx = 0f;
float dy = 0f;
if (vertical) {
int viewHeight = getHeight();// 这个事获取 该View的高度,由于使用的fillparaent
// 所以次数是 屏幕高度
if (height < viewHeight) {
dy = (viewHeight - height) / 2 - rect.top;
} else if (rect.top > 0) {
dy = -rect.top;
} else if (rect.bottom < viewHeight) {
dy = viewHeight - rect.bottom;
}
}
if (horizontal) {
int viewWidth = getWidth();
if (width < viewWidth) {
dx = (viewWidth - width) / 2 - rect.left;
} else if (rect.left > 0) {
dx = -rect.left;
} else if (rect.right < viewWidth) {
dx = viewWidth - rect.right;
}
}
matrix.postTranslate(dx, dy);
setImageMatrix(getImageViewMatrix());
}
/**
* 该方法是直接 让该图片准确的去放大多少倍
* 在手势 缩放的过程中不适用该方法, 用该方法的话 体验不好,缩放的太快了
*
* @param scale
* 相对当前已购的缩放倍数 上 在 缩放 scale倍
* @param x
* @param y
*/
public void zoomTure(float scale, float x, float y) {
float curScale = getScale();
float newScale = scale * curScale;
Log.i("scale", "getScale(); -- " + getScale());
Log.i("scale", "scale -- " + scale);
Log.i("scale", "scale*currentScale -- " + newScale);
if (newScale > maxSacle) {
newScale = maxSacle;
}
if (newScale < minScale) {
newScale = minScale;
}
float sureScale = newScale / curScale;
Log.i("scale", "newScale / curScale -- " + scale);
matrix.postScale(sureScale, sureScale, x, y);// matrix 矩阵变换 处理缩放
// 把变换完成后的 matrix 在注入回 ImageView中
this.setImageMatrix(getImageViewMatrix());
// TODO 处理图片 是的缩放后的图片能够正常显示在屏幕中央
}
public void setImageMatrix(Matrix m) {
super.setImageMatrix(m);
this.matrix.set(m);
Log.i("Matrix", "" + this.getImageMatrix());
}
/**
* 获取当前 的 一个 matrix值 为了保持matrix 一直可以自己变化, 所以不能认为的随意改变matrix值,所以引入了
* baseMatrix baseMatrix 作为处理 matrix 在整个的放大缩小过程中是一直在变动的
*
* @return
*/
protected Matrix getImageViewMatrix() {
displayMatrix.set(baseMatrix);
displayMatrix.postConcat(matrix);
return displayMatrix;
}
// 改ImageView 中的图片
private Bitmap bitmap;
/**
* 重写 setImageBitmap 方法
*/
@Override
public void setImageBitmap(Bitmap bm) {
super.setImageBitmap(bm);
bitmap = bm;
// 计算 图片载入是 的缩放比例
// TODO 如果图片之前经过BitmapFactor 缩放处理过此处可能需要注意下
float scale = calcueLoadScale();
// 处理缩放
zoomTure(scale, 0, 0);// 这个时候 图片应该在最顶端
// 把图片移动到屏幕中央
layoutToCenter();
}
/**
* 计算图片在载入时 的缩放比例
*
* @return 缩放比例
*/
private float calcueLoadScale() {
float scaleWidth = MyResource.screenWidth / (float) bitmap.getWidth();
float scaleHeight = MyResource.screenHeight
/ (float) bitmap.getHeight();
float scale = Math.min(scaleWidth, scaleHeight);
minScale = scale / 2;// 重新设置图片的缩放比例
return scale;
}
/**
* 将图片放置在屏幕中央. 使用该方法的前提是 当前图片的 左上角 的坐标为 0,0
*/
public void layoutToCenter() {
float width = (float) bitmap.getWidth() * getScale();
float height = (float) bitmap.getHeight() * getScale();
float fill_width = MyResource.screenWidth - width;
float fill_height = MyResource.screenHeight - height;
// 需要到移动的 x 和y 上的距离
float tran_width = 0f;
float tran_height = 0f;
if (fill_width > 0)
tran_width = fill_width / 2;
if (fill_height > 0)
tran_height = fill_height / 2;
matrix.postTranslate(tran_width, tran_height);// 设置移动图片 的 Matrix矩阵
setImageMatrix(getImageViewMatrix());// 将设置好的 matrix 注入Imageview 中
}
/**
* 移动 图片
*
* @param dx
* x轴的移动距离
* @param dy
* y轴的移动距离
*/
public void move(float dx, float dy) {
matrix.postTranslate(dx, dy);
setImageMatrix(getImageViewMatrix());// 将设置好的 matrix 注入Imageview 中
}
}
缩放移动的的时间监听
public class MyTouchListener implements OnTouchListener {
private MyImageView myImageView;
private float currentScale;
private Matrix currentMatrix = new Matrix();
private Matrix matrix = new Matrix();
// 第一个按下的点 滑动 的开始点
private PointF startPointF = new PointF();
// 两点触控式时 两个点之间的初始距离
private float startDistace;
// 两点触控时 的 两点之间中间点
private PointF midPointF = new PointF();
/** 记录是拖拉照片模式还是放大缩小照片模式 */
private int mode = 0;// 初始状态
/** 拖拉照片模式 */
private static final int MODE_DRAG = 1;
/** 放大缩小照片模式 */
private static final int MODE_ZOOM = 2;
@Override
public boolean onTouch(View v, MotionEvent event) {
if (v instanceof MyImageView) {
myImageView = (MyImageView) v;
/** 通过与运算保留最后八位 MotionEvent.ACTION_MASK = 255 */
switch (event.getAction() & MotionEvent.ACTION_MASK) {
// 手指压下屏幕
case MotionEvent.ACTION_DOWN:
mode = MODE_DRAG;
currentMatrix.set(myImageView.getImageMatrix());
// 记录ImageView当前的移动位置
startPointF.set(event.getX(), event.getY());
break;
// 手指在屏幕上移动,改事件会被不断触发
case MotionEvent.ACTION_MOVE:
// 拖拉图片
if (mode == MODE_DRAG) {
float dx = event.getX() - startPointF.x; // 得到x轴的移动距离
float dy = event.getY() - startPointF.y; // 得到y轴的移动距离
// 在没有移动之前的位置上进行移动
matrix.set(currentMatrix); // 在不使用拖动效果较好
// // currentMatrix的更新会有一个延时
// // 如果在image 中使用 想缩放那样的方法这回导致 拖动距离很大, 那是成比例的一直 拖动
matrix.postTranslate(dx, dy);
myImageView.setImageMatrix(matrix);
}
// 放大缩小图片
else if (mode == MODE_ZOOM) {
float endDis = distance(event);// 结束距离
if (endDis > 10f) { // 两个手指并拢在一起的时候像素大于10
float scale = endDis / startDistace;// 得到缩放倍数=
// myImageView 处理缩放事件scale * currentScale得到的是相对于图片本身的缩放倍数
myImageView.zoomSlowLy(scale * currentScale,
midPointF.x, midPointF.y);
}
}
break;
// 手指离开屏幕
case MotionEvent.ACTION_UP:
// 当触点离开屏幕,但是屏幕上还有触点(手指)
case MotionEvent.ACTION_POINTER_UP:
mode = 0;
break;
// 当屏幕上已经有触点(手指),再有一个触点压下屏幕
case MotionEvent.ACTION_POINTER_DOWN:
mode = MODE_ZOOM;
/** 计算两个手指间的距离 */
startDistace = distance(event);
/** 计算两个手指间的中间点 */
if (startDistace > 10f) { // 两个手指并拢在一起的时候像素大于10
midPointF = mid(event);
// 记录当前ImageView的缩放倍数
currentScale = myImageView.getScale();
}
break;
}
}
return true;
}
/** 计算两个手指间的距离 */
private float distance(MotionEvent event) {
float dx = event.getX(1) - event.getX(0);
float dy = event.getY(1) - event.getY(0);
/** 使用勾股定理返回两点之间的距离 */
return FloatMath.sqrt(dx * dx + dy * dy);
}
/** 计算两个手指间的中间点 */
private PointF mid(MotionEvent event) {
float midX = (event.getX(1) + event.getX(0)) / 2;
float midY = (event.getY(1) + event.getY(0)) / 2;
return new PointF(midX, midY);
}
}
一些效果图片:
1.最开始 图片载入时
图片 缩小时
图片放大时
图片移动