参考文章:
理解Android的手势识别
Android实战之手势与多点触控探究
Android开发中实现多点触摸
Android学习指南之三十八:Android手势操作编程
Android GestureDetector手势识别类
Android 自定义View可拖动移动位置及边缘拉伸放大缩小
一、最原始的单点拖拽和两点缩放
原理:对于常规的控件触控操作,在setOnTouchListener()接口中,实现 onTouchEvent()方法来处理。
代码清单:
package com.example.multitouch;
import android.os.Bundle;
import android.annotation.SuppressLint;
import android.annotation.TargetApi;
import android.app.Activity;
import android.graphics.Matrix;
import android.graphics.PointF;
import android.view.GestureDetector;
import android.view.Menu;
import android.view.MotionEvent;
import android.view.View;
import android.view.View.OnTouchListener;
import android.widget.ImageView;
import android.widget.Toast;
import android.view.GestureDetector.OnGestureListener;
public class MainActivity extends Activity implements OnTouchListener{
public ImageView myImageView;
private static final int NONE = 0;
private static final int DRAG = 1;
private static final int ZOOM = 2;
private int mode = NONE;
private Matrix tmpMatrix=new Matrix();;
private Matrix savedMatrix = new Matrix();
private PointF startPoint = new PointF();
private PointF endPoint=new PointF();
private PointF midPoint = new PointF();
private float oldDistance;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
myImageView=(ImageView)findViewById(R.id.myImageView);
myImageView.setOnTouchListener(this);
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.activity_main, menu);
return true;
}
@Override
public boolean onTouch(View v, MotionEvent event) {
//获取触控的点数
int pointCount = event.getPointerCount();
switch(event.getAction() & MotionEvent.ACTION_MASK){
//单手指按下
case MotionEvent.ACTION_DOWN:
//将当前的坐标保存为起始点
startPoint.set(event.getX(), event.getY());
tmpMatrix.set(myImageView.getImageMatrix());
savedMatrix.set(tmpMatrix);
mode = DRAG;
break;
//第二根手指按下
case MotionEvent.ACTION_POINTER_DOWN:
oldDistance = (float) Math.sqrt((event.getX(0) - event.getX(1)) * (event.getX(0) - event.getX(1)) + (event.getY(0) - event.getY(1)) * (event.getY(0) - event.getY(1)));
if (oldDistance > 10f)
{
savedMatrix.set(tmpMatrix);
midPoint.set((event.getX(0) + event.getX(1))/2, (event.getY(0) + event.getY(1))/2);
mode = ZOOM;
}
break;
//指点杆保持按下,并且进行位移
case MotionEvent.ACTION_MOVE:
//拖拽模式
if (mode == DRAG) {
tmpMatrix.set(savedMatrix);
tmpMatrix.postTranslate(event.getX() - startPoint.x, event.getY()
- startPoint.y);
}
//缩放模式
else if (mode == ZOOM)
{
float newDist = (float) Math.sqrt((event.getX(0) - event.getX(1)) * (event.getX(0) - event.getX(1)) + (event.getY(0) - event.getY(1)) * (event.getY(0) - event.getY(1)));
if (newDist > 10f)
{
tmpMatrix.set(savedMatrix);
float scale = newDist / oldDistance;
tmpMatrix.postScale(scale, scale, midPoint.x, midPoint.y);
}
}
break;
//有手指抬起,将模式设为NONE
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_POINTER_UP:
mode = NONE;
break;
default:
}
myImageView.setImageMatrix(tmpMatrix);
return true;
}
}
代码解释:MainActivity实现OnTouchLietener的接口,将ImageView的触控 监听器设置为this,在重载函数OnTouch中实现对触控事件的处理。
这里的图像的位置和大小的变化都用到了矩阵运算,不太清楚的话可以先补充一下线性代数的知识。
拖拽的实现就是用矩阵记录手指移动的距离;缩放的时候,首先要记录两只手指最开始的距离,然后当手指移动的时候,实时计算出手指的距离,与之前的距离相除得到缩放的比例,然后用矩阵的scale方法存储。
二、手势识别
上面的例子虽然实现了基本的触控功能,而且低版本的系统也能很好的支持,但如果遇到了高级的触控事件,比如双击,长按之类,实现起来就非常麻烦了!
好在后续版本的api提供了更加棒的接口,我们可以很简单地来实现想要的效果。
这里要用到的是Android给我们提供的手势识别工具GestureDetector,需要2.2及以上的系统版本。下面的例子实现的效果是:单点拖拽,滑动切换imageView的内容,两点缩放,双击图像改变图像显示状态。
函数的最后调用 setImageMatrix()来实现对TextView的缩放或移动。
package com.example.gesture;
import java.util.Random;
import android.os.Bundle;
import android.app.Activity;
import android.graphics.Matrix;
import android.graphics.PointF;
import android.view.GestureDetector;
import android.view.GestureDetector.SimpleOnGestureListener;
import android.view.Menu;
import android.view.MotionEvent;
import android.view.ScaleGestureDetector;
import android.view.ScaleGestureDetector.OnScaleGestureListener;
import android.view.View;
import android.widget.ImageView;
import android.widget.Toast;
public class MainActivity extends Activity {
private GestureDetector myDetector;
private Matrix matrix;
private ImageView myImageView;
private Random random;
private ScaleGestureDetector mScaleGestureDetector;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
myDetector=new GestureDetector(this,new MyGestureListener());
mScaleGestureDetector=new ScaleGestureDetector(this,new MyScaleGestureListener());
matrix=new Matrix();
myImageView=(ImageView)findViewById(R.id.myImageView);
random=new Random();
}
@Override
public boolean onTouchEvent(MotionEvent event) {
int pointCount = event.getPointerCount();
if(pointCount==1)
return myDetector.onTouchEvent(event);
else
return mScaleGestureDetector.onTouchEvent(event);
}
private class MyGestureListener extends SimpleOnGestureListener
{
Matrix mMatrix=new Matrix();
PointF startPoint=new PointF();
@Override
public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX,
float distanceY) {
// TODO Auto-generated method stub
mMatrix.set(myImageView.getImageMatrix());
System.out.println("distanceX:"+distanceX+"distanceY:"+distanceY);
startPoint.set(e1.getRawX(), e1.getRawY());
mMatrix.postTranslate(-distanceX,-distanceY);
myImageView.setImageMatrix(mMatrix);
return false;
}
@Override
public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY)
{
final int FLING_MIN_DISTANCE = 100, FLING_MIN_VELOCITY = 200;
if (e1.getX() - e2.getX() > FLING_MIN_DISTANCE && Math.abs(velocityX) > FLING_MIN_VELOCITY) {
// Fling left
myImageView.setImageResource(R.drawable.pic0);
Toast.makeText(getApplicationContext(), "Fling Left", Toast.LENGTH_SHORT).show();
} else if (e2.getX() - e1.getX() > FLING_MIN_DISTANCE && Math.abs(velocityX) > FLING_MIN_VELOCITY) {
// Fling right
switch(random.nextInt(5))
{
case 0:
myImageView.setImageResource(R.drawable.pic2);
break;
case 1:
myImageView.setImageResource(R.drawable.pic3);
break;
case 2:
myImageView.setImageResource(R.drawable.pic7);
break;
case 3:
myImageView.setImageResource(R.drawable.pic5);
break;
case 4:
myImageView.setImageResource(R.drawable.pic6);
break;
default:
}
Toast.makeText(getApplicationContext(), "Fling Right", Toast.LENGTH_SHORT).show();
}
return false;
}
// 用户轻触触摸屏,由1个MotionEvent ACTION_DOWN触发
public boolean onDown(MotionEvent arg0) {
Toast.makeText(getApplicationContext(), "onDown", Toast.LENGTH_SHORT).show();
return true;
}
@Override
public boolean onDoubleTap(MotionEvent e)
{
if(myImageView.isShown())
myImageView.setVisibility(View.INVISIBLE);
else myImageView.setVisibility(View.VISIBLE);
return false;
}
}
private class MyScaleGestureListener implements OnScaleGestureListener
{
private float oldDist;
private float newDist;
Matrix mMatrix = new Matrix();
@Override
public boolean onScale(ScaleGestureDetector detector) {
// TODO Auto-generated method stub
newDist=detector.getCurrentSpan();
mMatrix.set(myImageView.getImageMatrix());
//缩放比例
//float scale = detector.getScaleFactor()/3;
float scale=newDist/oldDist;
System.out.println("scale:"+scale);
//mMatrix.setScale(scale, scale,detector.getFocusX(),detector.getFocusY());
mMatrix.postScale(scale, scale,detector.getFocusX(),detector.getFocusY());
myImageView.setImageMatrix(mMatrix);
oldDist=newDist;
return false;
}
@Override
public boolean onScaleBegin(ScaleGestureDetector detector) {
// TODO Auto-generated method stub
oldDist=detector.getCurrentSpan();
newDist=detector.getCurrentSpan();
return true;
}
@Override
public void onScaleEnd(ScaleGestureDetector detector) {
// TODO Auto-generated method stub
}
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.activity_main, menu);
return true;
}
}
代码解释:
这里我定义了两个GestrueListener,一个专门用于处理缩放的ScaleOnGestrueListener一个SimpleOnGestrueListener,当触控的点数为2的时候调用前者来处理,一般常用的手势用后者来处理。
原理和前面的差不多,只是调用不同的接口和不同的方法来实现,但是更加方便也更加清晰.
三、一点后记
学习Andorid中的某个类的时候,其实最好的方法是去看官方的API,有时候网上虽然有现成的代码给你,但实际运用的时候还是会有各种各样的问题,很多文章大都有雷同,甚至代码本身就有bug还往上粘,唉.....所以,最好还是自己踏踏实实研究。
四、陷阱
对于自定义View,使用手势识别有两处陷阱可能会浪费你的不少时间。
1:View必须设置longClickable为true,否则手势识别无法正确工作,只会返回Down, Show, Long三种手势
2:必须在View的onTouchListener中调用手势识别,而不能像Activity一样重载onTouchEvent,否则同样手势识别无法正确工作