Android實戰之手勢與多點觸控探究

來源:互聯網
上載者:User

一、提要

         最近在iPhone有一款應用非常火,較做Clear,這個是示範視頻:http://v.youku.com/v_show/id_XMzUyNjQ2NDk2.html

 實施上它的功能非常的簡單,類似一個ToDoList,但它將使用者體驗做到了極致,其中一個最大的特點就是將手勢和多點觸控成功得融入到了應用之中。

         這篇文章要探究的就是在Android中的手勢和多點觸控的原理及實現。


二、最原始的單點拖拽和兩點縮放

原理:對於常規的控制項觸控操作,在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;  @Overridepublic void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);myImageView=(ImageView)findViewById(R.id.myImageView);myImageView.setOnTouchListener(this);}@Overridepublic boolean onCreateOptionsMenu(Menu menu) {getMenuInflater().inflate(R.menu.activity_main, menu);return true;}@Overridepublic 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;  //有手指抬起,將模式設為NONEcase MotionEvent.ACTION_UP:  case MotionEvent.ACTION_POINTER_UP:  mode = NONE;  break;  default:}          myImageView.setImageMatrix(tmpMatrix); return true;  }}

代碼解釋:MainActivity實現OnTouchLietener的介面,將ImageView的觸控 監聽器設定為this,在重載函數OnTouch中實現對觸控事件的處理。

這裡的映像的位置和大小的變化都用到了矩陣運算,不太清楚的話可以先補充一下線性代數的知識。

拖拽的實現就是用矩陣記錄手指移動的距離;縮放的時候,首先要記錄兩隻手指最開始的距離,然後當手指移動的時候,Realtime Compute出手指的距離,與之前的距離相除得到縮放的比例,然後用矩陣的scale方法儲存。

函數的最後調用 setImageMatrix()來實現對TextView的縮放或移動。


二、手勢識別

        上面的例子雖然實現了基本的觸控功能,而且低版本的系統也能很好的支援,但如果遇到了進階的觸控事件,比如雙擊,長按之類,實現起來就非常麻煩了!

         好在後續版本的api提供了更加棒的介面,我們可以很簡單地來實現想要的效果。

         這裡要用到的是Android給我們提供的手勢識別工具GestureDetector,需要2.2及以上的系統版本。

         下面的例子實現的效果是:單點拖拽,滑動切換imageView的內容,兩點縮放,雙擊映像改變映像顯示狀態。

效果:

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;@Overridepublic 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;  }  @Overridepublic 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();  @Overridepublic boolean onScale(ScaleGestureDetector detector) {// TODO Auto-generated method stubnewDist=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;}@Overridepublic boolean onScaleBegin(ScaleGestureDetector detector) {// TODO Auto-generated method stuboldDist=detector.getCurrentSpan();newDist=detector.getCurrentSpan();return true;}@Overridepublic void onScaleEnd(ScaleGestureDetector detector) {// TODO Auto-generated method stub}}@Overridepublic boolean onCreateOptionsMenu(Menu menu) {getMenuInflater().inflate(R.menu.activity_main, menu);return true;}}


代碼解釋:

這裡我定義了兩個GestrueListener,一個專門用於處理縮放的ScaleOnGestrueListener一個SimpleOnGestrueListener,當觸控的點數為2的時候調用前者來處理,一般常用的手勢用後者來處理。

原理和前面的差不多,只是調用不同的介面和不同的方法來實現,但是更加方便也更加清晰.

三、一點後記

學習Andorid中的某個類的時候,其實最好的方法是去看官方的API,有時候網上雖然有現成的代碼給你,但實際運用的時候還是會有各種各樣的問題,很多文章大都有雷同,甚至代碼本身就有bug還往上粘,唉.....所以,最好還是自己踏踏實實研究。

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.