最近在进行项目的开发,用到了多点触控这方面的知识,平时做开发的时候喜欢听hyman老师讲的课,这次的多点触控同样也是听了hyman老师的课程,感到受益匪浅,但是网上没有看到hyman关于这个项目的源码,因此发个帖子供大家阅读以及巩固一下自己学到的知识。
首先我们要自己自定义一个控件去实现自由的放大和缩小,双击放大和缩小以及放大后可以自由地移动这样的功能,布局文件和主activity文件过于简单就不贴代码了,下面就贴上关于这个控件是如何实现的相关代码。
首先是要实现该功能所要用到的知识点
1、Matrix(矩阵,其实正是一个长度为9的一维数组,小白表示之前没有接触过。。。)
2、ScaleGestureDetector(用于检测用户多指触控时缩放的手势)
3、GestureDetector(用于检测用户双击时做的一些处理)
我们先要复写ImageView,然后实现OnGlobalLayoutListener接口,此外在设置view控件的属性时scaleType的值要设置为matrix,不过最好是在构造方面里setScaleType(ScaleType.MATRIX);
然后我们要在接口所要实现的方法里面去设置合适的图片大小,总的来说就是获得控件的宽高和图片的宽高作比较然后设置合适的值并进行移动。这样我们就能显示出一个好看的界面。
到这里我们才开始实现多点触控。接下来我们要实现多点触控首先考虑到一个类ScaleGestureDetector,通过这个类捕获多点触控缩放的比例。我们这里要实现OnScaleGestureListener这个接口,同时实现OnTouchListener这个接口来处理触摸事件。在onScale方法里面在获取到缩放比例,同时还要对其进行边界控制以及图片本身的图片控制。
最后我们进行自由移动的实现。我们通过onTouch去拿到多点触控的数量也就是我们的手指头在屏幕的数量。记录位置,然后对事件的动作进行监听,主要是move和up的动作,同时移动的时候我们也要对其进行边界检查。
还有一个就是双击放大缩小。GestureDetector构造方法第二个参数要实现一个接口,但是这个接口要实现的方法太多,有8个,显得代码有点冗余,于是我们去实现他的一个内部类SimpleOnGestureListener,复写其中的onDoubleTap这个方法即可。
在放大和缩小是我们为了用户体验应该让它缓慢地放大和缩小。因此我们建立一个Runnable让它缓慢变大缩小。在新建的Runnable中的run方法中我们需要重复调用自身,怎么样去调用呢,我们可以用postDelayed方法来实现。设置的时间值尽可能小,让用户判断不出来就好。
postDelay+Runnable可以很好地实现渐变式的动画效果,这也是在听hyman老师讲解的过程中知道的一个小技巧吧。
到此我们所有的功能都以及基本实现,下面贴上跟着老师一起码的代码:
package com.example.view;
import
android.content.Context;
import
android.graphics.Matrix;
import android.graphics.RectF;
import
android.graphics.drawable.Drawable;
import
android.util.AttributeSet;
import
android.view.GestureDetector;
import
android.view.MotionEvent;
import
android.view.ScaleGestureDetector;
import
android.view.ScaleGestureDetector.OnScaleGestureListener;
import android.view.View;
import
android.view.ViewConfiguration;
import
android.view.ViewTreeObserver.OnGlobalLayoutListener;
import
android.widget.ImageView;
import
android.view.View.OnTouchListener;
public class ZoomImageView extends
ImageView implements OnGlobalLayoutListener,
OnScaleGestureListener,
OnTouchListener {
private boolean mOnce;
private float mInitScale;//
初始化时放大的值
private float mMidScale;//
双击放大到达的值
private float mMaxScale;//
放大的最大值
private Matrix mScaleMatrix;
private ScaleGestureDetector
mScaleGestureDetector;// 捕获多点触控缩放的比例
// 自由移动
private int mLastPointerCount;//
记录上一次多点触控的数量
private float mLastX;//
记录最后一次中心点为位置x,y
private float mLastY;
private int mTouchSlop;
private boolean isCanDrag;
private RectF martixRectF;
private boolean
isCheckLeftAndRight;
private boolean
isCheckTopAnom;
// 双击放大与缩小
private GestureDetector
mGestureDetector;
private boolean isAutoScale;
public ZoomImageView(Context
context) {
this(context, null);
}
public ZoomImageView(Context
context, AttributeSet attrs) {
this(context, attrs, 0);
}
public ZoomImageView(Context
context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs,
defStyleAttr);
mScaleMatrix = new Matrix();
setScaleType(ScaleType.MATRIX);
mScaleGestureDetector = new
ScaleGestureDetector(context, this);
setOnTouchListener(this);
mTouchSlop =
ViewConfiguration.get(context).getScaledTouchSlop();// 作比较的值
mGestureDetector = new
GestureDetector(context,
new
GestureDetector.SimpleOnGestureListener() {
@Override
public boolean
onDoubleTap(MotionEvent e) {
if (isAutoScale)
return true;
float x = e.getX();
float y = e.getY();
if (getScale() < mMidScale)
{
// mScaleMatrix.postScale(mMidScale
/ getScale(),
// mMidScale / getScale(), x,
y);
//
setImageMatrix(mScaleMatrix);
// 我们要触发AutoScaleRunnable
postDelayed(new
AutoScaleRunnable(mMidScale, x, y),
16);
isAutoScale = true;
} else {
// mScaleMatrix.postScale(mInitScale
/ getScale(),
// mInitScale / getScale(), x,
y);
//
setImageMatrix(mScaleMatrix);
postDelayed(
new AutoScaleRunnable(mInitScale, x,
y), 16);
isAutoScale = true;
}
return true;
}
});
}
private class AutoScaleRunnable
implements Runnable {
private float mTargetScale;//
缩放的目标值
// 缩放中心点
private float x;
private float y;
// 放大缩小的梯度
private final float BIGGER =
1.07f;
private final float SMALL =
0.93f;
private float tmpScale;
public AutoScaleRunnable(float
mTargetScale, float x, float y) {
this.mTargetScale =
mTargetScale;
this.x = x;
this.y = y;
if (getScale() < mTargetScale)
{
tmpScale = BIGGER;
}
if (getScale() > mTargetScale)
{
tmpScale = SMALL;
}
}
@Override
public void run() {
// 进行缩放
mScaleMatrix.postSkew(tmpScale,
tmpScale, x, y);
checkBorderAndCenterWhenScale();
setImageMatrix(mScaleMatrix);
float currentScale =
getScale();
if ((tmpScale > 1.0f &&
currentScale < mTargetScale)
|| (tmpScale < 1.0f &&
currentScale > tmpScale)) {
postDelayed(this, 16);
} else {// 设置为目标值
float scale = mTargetScale /
currentScale;
mScaleMatrix.postScale(scale, scale,
x, y);
checkBorderAndCenterWhenScale();
setImageMatrix(mScaleMatrix);
isAutoScale = false;
}
}
}
@Override
protected void onAttachedToWindow()
{
super.onAttachedToWindow();
getViewTreeObserver().addOnGlobalLayoutListener(this);
}
@SuppressWarnings("deprecation")
@Override
protected void
onDetachedFromWindow() {
super.onDetachedFromWindow();
getViewTreeObserver().removeGlobalOnLayoutListener(this);
}
@Override
public void onGlobalLayout() {
if (!mOnce) {
// 得到控件的宽和高
int width = getWidth();
int height = getHeight();
// 得到图片,以及宽和高
Drawable d = getDrawable();
if (d == null)
return;
int dw =
d.getIntrinsicWidth();
int dh =
d.getIntrinsicHeight();
// 进行对比并适当缩放图片
float scale = 1.0f;
if (dw > width && dh <
height) {
scale = width * 1.0f / dw;//
缩放的值
}
if (dh > height && dw
< width) {
scale = height * 1.0f / dh;//
缩放的值
}
if (dw > width && dh >
height) {
scale = Math.min(width * 1.0f / dw,
height * 1.0f / dh);
}
if (dw < width && dh <
height) {
scale = Math.min(width * 1.0f / dw,
height * 1.0f / dh);
}
mInitScale = scale;
mMaxScale = mInitScale * 4;
mMidScale = mInitScale * 2;
// 将图片移动至控件中心
int dx = getWidth() / 2 - dw /
2;
int dy = getHeight() / 2 - dh /
2;
// matrix 长度为0的一维数组,已经封装好了方法
mScaleMatrix.postTranslate(dx,
dy);// 平移
mScaleMatrix.postScale(mInitScale,
mInitScale, width / 2,
height / 2);// 缩放
setImageMatrix(mScaleMatrix);
mOnce = true;
}
}
public float getScale() {//
获取当前图片的缩放值
float[] values = new float[9];
mScaleMatrix.getValues(values);
return
values[Matrix.MSCALE_X];
}
@Override
public boolean
onScale(ScaleGestureDetector detector) {
float scale = getScale();
float scaleFactor =
detector.getScaleFactor();
if (getDrawable() == null)
return true;
if ((scale < mMaxScale &&
scaleFactor > 1.0f)
|| (scale > mInitScale &&
scaleFactor < 1.0f)) {
if (scale * scaleFactor <
mInitScale) {
scaleFactor = mInitScale /
scale;
}
if (scale * scaleFactor <
mMaxScale) {
scale = mMaxScale / scale;
}
mScaleMatrix.postScale(scaleFactor,
scaleFactor,
detector.getFocusX(),
detector.getFocusY());// 缩放
checkBorderAndCenterWhenScale();
setImageMatrix(mScaleMatrix);
}
return false;
}
private void
checkBorderAndCenterWhenScale() {// 在缩放的时候进行边界控制以及我们的位置的控制
RectF rect = getMatrixRectf();
float deltaX = 0;
float deltaY = 0;
int width = getWidth();
int height = getHeight();
// 缩放时进行边界检测,防止出现白边
if (rect.width() >= width)
{
if (rect.left > 0) {
deltaX = -rect.left;
}
if (rect.right < width) {
deltaX = width - rect.right;
}
}
if (rect.height() >= height)
{
if (rect.top > 0) {
deltaY = -rect.top;
}
if (rect.bottom < height) {
deltaY = height - rect.bottom;
}
}
// 如果宽度或者高小于控件的宽或者高;则让其居中
if (rect.width() < width) {
deltaX = width / 2f - rect.right +
rect.width() / 2f;
}
if (rect.height() < height)
{
deltaY = height / 2f - rect.bottom +
rect.height() / 2f;
}
mScaleMatrix.postScale(deltaX,
deltaY);
}
@Override
public boolean
onScaleBegin(ScaleGestureDetector detector) {
return true;
}
@Override
public void
onScaleEnd(ScaleGestureDetector detector) {
}
@Override
public boolean onTouch(View v,
MotionEvent event) {
if
(mGestureDetector.onTouchEvent(event))
return true;
mScaleGestureDetector.onTouchEvent(event);
float x = 0;
float y = 0;
// 拿到多点触控的数量
int pointcount =
event.getPointerCount();
for (int i = 0; i < pointcount;
i++) {
x += event.getX(i);
y += event.getY(i);
}
x /= pointcount;
y /= pointcount;//
总和除以数量等于中心点的位置
if (mLastPointerCount != pointcount)
{
isCanDrag = false;
mLastX = x;
mLastY = y;
}
mLastPointerCount =
pointcount;
switch (event.getAction()) {
case MotionEvent.ACTION_MOVE:
float dx = x - mLastX;
float dy = y - mLastY;
if (!isCanDrag) {
isCanDrag = isMove(dx, dy);
}
if (isCanDrag) {
RectF rectf =
getMatrixRectf();
if (getDrawable() != null) {
isCheckLeftAndRight =
isCheckTopAnom = true;
if (rectf.height() < getHeight())
{// 如果高度小于控件高度,不允许纵向移动
isCheckTopAnom = false;
dy = 0;
}
if (rectf.width() < getWidth())
{// 如果宽度小于控件宽度,不允许横向移动
isCheckLeftAndRight = false;
dx = 0;
}
mScaleMatrix.postTranslate(dx,
dy);
checkBorderWhenTranslate();
setImageMatrix(mScaleMatrix);
}
}
mLastX = x;
mLastY = y;
break;
case MotionEvent.ACTION_UP:
case
MotionEvent.ACTION_CANCEL:
mLastPointerCount = 0;
break;
default:
break;
}
return true;
}
private void
checkBorderWhenTranslate() {// 当移动是进行边界检查
RectF rect = getMatrixRectf();
float deltaX = 0;
float deltaY = 0;
int width = getWidth();
int height = getHeight();
if (rect.top > 0 &&
isCheckTopAnom) {
deltaY = -rect.top;
}
if (rect.bottom < height
&& isCheckTopAnom) {
deltaY = height - rect.bottom;
}
if (rect.left > 0 &&
isCheckLeftAndRight) {
deltaX = -rect.left;
}
if (rect.right < width &&
isCheckLeftAndRight) {
deltaX = width - rect.right;
}
mScaleMatrix.postTranslate(deltaX,
deltaY);
}
private RectF getMatrixRectf()
{
Matrix matrix = mScaleMatrix;
RectF rectf = new RectF();
Drawable d = getDrawable();
if (d != null) {
rectf.set(0, 0,
d.getIntrinsicWidth(), d.getIntrinsicHeight());
matrix.mapRect(rectf);
}
return rectf;
}
private boolean isMove(float dx,
float dy) {
return Math.sqrt(dx * dx + dy * dy)
> mTouchSlop;// 勾股定理得出斜边和标准值比较
}
}