Android GestureDetector手势识别与多点触控探究

参考文章:
理解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,否则同样手势识别无法正确工作

你可能感兴趣的:(Android)