Android开发笔记(四十五)手势事件

手势事件的流程

基本手势事件

基本的手势事件主要有如下三个方法:
dispatchTouchEvent : 判断该事件是否需要下发。返回true表示需要下发给下级视图,返回false表示不需要下发(交给自身的onTouchEvent处理)。但是否最终下发,还需根据onInterceptTouchEvent的拦截结果。
onInterceptTouchEvent : 判断当前容器是否需要拦截该事件。返回true表示予以拦截(交给自身的onTouchEvent处理)、不放给下级视图,返回false表示不拦截该事件。
onTouchEvent : 判断该事件是否处理完毕。返回true表示处理完毕,则无需处理上级视图的onTouchEvent,一路返回结束流程。返回false表示该事件未完成,则返回继续处理上级视图的onTouchEvent,然后再根据上级onTouchEvent的返回值判断是直接结束还是由再上级处理。


手势方法的执行者

页面类:包括Activity及Activity的派生类。页面类可操作dispatchTouchEvent和onTouchEvent。注意Fragment不能操作基本手势方法,只能通过实现OnTouchListener接口来响应手势事件。

控件类:包括从View类派生出的各类控件,包括TextView、ImageView、Button等及它们的派生类。控件类可操作dispatchTouchEvent和onTouchEvent。

容器类:包括从ViewGroup类派生出的各类容器,如三个布局LinearLayout、RelativeLayout、FrameLayout,以及AdapterView派生出来的GridView、ListView、Spinner,还有ViewPager、ViewFlipper等等。容器类可操作dispatchTouchEvent、onInterceptTouchEvent和onTouchEvent。

上面可以看出,只有容器类才能操作onInterceptTouchEvent方法,这是因为该方法用于拦截发往下层视图的事件,而控件类已经位于底层只有被拦截的份没有拦截别人的份,同样页面类本身并不拥有下层视图。


手势事件的生命周期

控件响应
Activity.dispatchTouchEvent(返回true)->ViewGroup.dispatchTouchEvent(返回true)->ViewGroup.onInterceptTouchEvent(返回false)->View.dispatchTouchEvent(返回true)->View.onTouchEvent(返回true)->结束

容器响应
方式一:Activity.dispatchTouchEvent(返回true)->ViewGroup.dispatchTouchEvent(返回false)->ViewGroup.onTouchEvent(返回true)->结束
方式二:Activity.dispatchTouchEvent(返回true)->ViewGroup.dispatchTouchEvent(返回true)->ViewGroup.onInterceptTouchEvent(返回true)->ViewGroup.onTouchEvent(返回true)->结束
方式三:Activity.dispatchTouchEvent(返回true)->ViewGroup.dispatchTouchEvent(返回true)->ViewGroup.onInterceptTouchEvent(返回false)->View.dispatchTouchEvent(返回true或false)->View.onTouchEvent(返回false)->ViewGroup.onTouchEvent(返回true)->结束

Activity响应
Activity.dispatchTouchEvent(返回false)->Activity.onTouchEvent(返回true)->结束

更详细的生命周期见下图所示:
Android开发笔记(四十五)手势事件_第1张图片


TouchEvent

下面是触摸事件的常用方法:
getAction : 获取当前的动作
getX : 获取当前在控件内部的相对坐标X
getY : 获取当前在控件内部的相对坐标Y
getRawX : 获取当前在屏幕上的相对坐标X
getRawY : 获取当前在屏幕上的相对坐标Y
getEventTime : 获取当前的事件时间



手势检测GestureDetector

由于在onTouchEvent中判断用户手势的真实想法很不容易,因此Android提供了GestureDetector检测器来帮助我们识别手势。借助于GestureDetector,可以在大多数场合下辨别出常用的几个手势事件,如点击、长按、翻页等等。下面是GestureDetector的相关方法:

构造函数 : GestureDetector(Context context, OnGestureListener listener)
监听器类名 : OnGestureListener
设置监听器的方法,先给指定控件注册触摸监听器,然后在触摸方法onTouch中由GestureDetector接管触摸事件 : 
	private ScrollTextView tv_rough;
	private GestureDetector mGesture;
	
		tv_rough = (ScrollTextView) findViewById(R.id.tv_rough);
		tv_rough.setOnTouchListener(this);
		mGesture = new GestureDetector(this, this);
		
	@Override
	public boolean onTouch(View v, MotionEvent event) {
		return mGesture.onTouchEvent(event);
	}
另外,也可在当前视图或当前Activity中重写onTouchEvent方法,在该方法中由GestureDetector接管触摸事件。

监听器需要重写的方法 : 
onDown : 在用户按下时调用
onShowPress : 已按下但还未滑动或松开时调用,通常用于pressed状态时的高亮显示
onSingleTapUp : 在用户轻点一下再弹起时调用,通常用于点击事件
onScroll : 在用户滑动过程中调用
onLongPress : 在用户长按时调用,通常用于长按事件
onFling : 在用户飞快掠出一段距离时调用,通常用于翻页事件



滑动冲突的处理

app功能多起来之后,页面上有多个控件是可以滑动的,比如说ScrollView、下拉刷新、ViewFlipper、ViewPager等等,有的需要处理上下滑动手势,有的需要处理左右滑动手势。这样多个控件争相响应同一个手势事件,就会产生滑动冲突,如果没处理好冲突,页面上的某些控件便无法正常使用。避免滑动冲突的处理办法,主要有以下三个:


1、对不同的手势事件,要返回正确的布尔值。
手势监听器OnGestureListener需要重写的方法中,onDown、onScroll、onSingleTapUp、onFling这四个方法得返回布尔值,返回true表示其他事件仍需响应,返回false表示其他事件无需响应。一般情况下,onDown和onScroll要返回true,因为这两个方法尚无法构成具体的事件意图;而onSingleTapUp和onFling要返回false,因为onSingleTapUp表明了此次是点击事件,onFling表明了此次是翻页事件。


2、在底层控件中,如果当前手势还未处理完成,那么必须阻止上级视图的手势拦截。requestDisallowInterceptTouchEvent就是底层控件用来通知上级视图是否拦截的方法,参数输入true告知上级不要拦截,输入false告知上级可以拦截。下面示例代码演示了这么一个意图:当用户按下或者滑动时,当前控件需要响应手势事件,请上级视图不要拦截手势;当用户松开或取消时,当前控件已经处理完毕,允许上级视图拦截手势。
@Override
public boolean onTouch(View v, MotionEvent event) {
    switch (event.getAction()) {
    case MotionEvent.ACTION_DOWN:
    case MotionEvent.ACTION_MOVE:
        getParent().requestDisallowInterceptTouchEvent(true);
        ...//响应手势
        break;
    case MotionEvent.ACTION_UP:
    case MotionEvent.ACTION_CANCEL:
        ...//处理完毕
        getParent().requestDisallowInterceptTouchEvent(false);
        break;
    }
}


3、上级视图先在onInterceptTouchEvent方法中拦截手势,对手势事件进行筛选,如果需要上级处理,则返回true,表示我拦截了自己处理;如果无需上级处理,则返回false,表示我不要了给你用吧。下面示例代码演示了ScrollView拦截垂直滑动而放过水平滑动的功能:
import android.annotation.SuppressLint;
import android.content.Context;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import android.widget.ScrollView;


@SuppressLint("ClickableViewAccessibility")
public class CustomScrollView extends ScrollView {
	private final static String TAG = "CustomScrollView";
	private float mOffsetX;
	private float mOffsetY;
	private float mLastPosX;
	private float mLastPosY;


	public CustomScrollView(Context context) {
		super(context);
	}


	public CustomScrollView(Context context, AttributeSet attr) {
		super(context, attr);
		setFadingEdgeLength(0);
	}


	@Override
	public boolean dispatchTouchEvent(MotionEvent event) {
		return super.dispatchTouchEvent(event);
	}


	@Override
	public boolean onTouchEvent(MotionEvent event) {
		return super.onTouchEvent(event);
	}


	@Override
	public boolean onInterceptTouchEvent(MotionEvent event) {
		Log.d(TAG, "onInterceptTouchEvent");
		boolean result = false;
		switch (event.getAction()) {
		case MotionEvent.ACTION_DOWN:
			mOffsetX = 0.0F;
			mOffsetY = 0.0F;
			mLastPosX = event.getX();
			mLastPosY = event.getY();
			result = super.onInterceptTouchEvent(event); // false传给子控件
			break;
		default:
			float thisPosX = event.getX();
			float thisPosY = event.getY();
			mOffsetX += Math.abs(thisPosX - mLastPosX); // x轴偏差
			mOffsetY += Math.abs(thisPosY - mLastPosY); // y轴偏差
			mLastPosX = thisPosX;
			mLastPosY = thisPosY;
			if (mOffsetX < 3 && mOffsetY < 3) {
				result = false; // false传给子控件(点击事件)
			} else if (mOffsetX < mOffsetY) {
				result = true; // true不传给子控件(垂直滑动)
			} else {
				result = false; // false传给子控件
			}
			break;
		}
		return result;
	}
}



弹性滑动

滑动计算器Scroller

Scroller是Android用于计算滑动参数的辅助类,常用方法如下:
startScroll : 设置开始滑动的参数,包括起始的xy坐标、xy偏移量,另一个重载的方法还可以设置滑动的持续时间。
computeScrollOffset : 计算滑动偏移量。返回值可判断滑动是否结束,返回fasle表示滑动结束,返回true表示还在滑动当中。
getCurrX : 获得当前的X坐标
getCurrY : 获得当前的Y坐标
getDuration : 获得滑动的持续时间
forceFinished : 强行停止滑动
isFinished : 判断滑动是否结束。返回fasle表示还未结束,返回true表示滑动结束。该方法与computeScrollOffset的区别在于:1、computeScrollOffset内部还有计算偏移量,而isFinished只返回标志不做其他处理;2、computeScrollOffset返回fasle表示滑动结束,而isFinished返回true表示滑动结束。


View的滑动方法

虽然Scroller提供了滑动的相关计算函数,但是Scroller本身并不能直接滑动控件。因为Scroller只是个运算模拟器,根据时间的流逝计算xy坐标,所以我们必须调用控件自身的滑动方法,才能真正让控件动起来。View类中操纵滑动的方法有两个:
scrollTo : 将控件滑动到指定坐标位置
scrollBy : 将控件滑动指定偏移量。查看源码会发现scrollBy内部就是调用scrollTo,当然得先把当前坐标加上偏移量,从而得到滑动后的绝对坐标。


视图滑动例子

下面是一个简单滑动TextView的效果图:


<span style="font-family: Arial, Helvetica, sans-serif; background-color: rgb(255, 255, 255);">ScrollTextView控件的源码如下:</span>
import android.content.Context;
import android.util.AttributeSet;
import android.widget.Scroller;
import android.widget.TextView;

public class ScrollTextView extends TextView {

    private static final String TAG = "ScrollTextView";
    private Scroller mScroller;
  
    public ScrollTextView(Context context, AttributeSet attrs) {
        super(context, attrs);
        mScroller = new Scroller(context);
    }
  
    public void smoothScrollTo(int fx, int fy) {
        int dx = fx - mScroller.getFinalX();
        int dy = fy - mScroller.getFinalY();
        smoothScrollBy(dx, dy);
    }
  
    public void smoothScrollBy(int dx, int dy) {
        //设置滚动偏移量,注意正数是往左滚往上滚,负数才是往右滚往下滚
        mScroller.startScroll(mScroller.getFinalX(), mScroller.getFinalY(), -dx, -dy);
        //调用invalidate()才能保证computeScroll()会被调用
        invalidate();
    }
       
    @Override
    public void computeScroll() {
        //先判断mScroller滚动是否完成
        if (mScroller.computeScrollOffset()) {
            //这里调用View的scrollTo()完成实际的滚动
            scrollTo(mScroller.getCurrX(), mScroller.getCurrY());
            //刷新页面
            postInvalidate();
        }
        super.computeScroll();
    }
}


通过onFling调用滑动控件的代码如下:
import com.example.exmgesture.widget.ScrollTextView;

import android.annotation.SuppressLint;
import android.app.Activity;
import android.os.Bundle;
import android.view.GestureDetector;
import android.view.GestureDetector.OnGestureListener;
import android.view.MotionEvent;
import android.view.View;
import android.view.View.OnTouchListener;

@SuppressLint("ClickableViewAccessibility")
public class RoughActivity extends Activity implements OnTouchListener,OnGestureListener {

	private final static String TAG = "RoughActivity";
	private ScrollTextView tv_rough;
	private GestureDetector mGesture;
	
	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.activity_rough);
		
		tv_rough = (ScrollTextView) findViewById(R.id.tv_rough);
		tv_rough.setOnTouchListener(this);
		mGesture = new GestureDetector(this, this);
	}
	
	@Override
	public boolean dispatchTouchEvent(MotionEvent ev) {
		// TODO Auto-generated method stub
		return super.dispatchTouchEvent(ev);
	}
	
	@Override
	public boolean onTouch(View v, MotionEvent event) {
		return mGesture.onTouchEvent(event);
	}

	@Override
	public boolean onDown(MotionEvent e) {
		return true;
	}

	@Override
	public void onShowPress(MotionEvent e) {
	}

	@Override
	public boolean onSingleTapUp(MotionEvent e) {
		tv_rough.setText("您轻轻点击了一下");
		return false;
	}

	@Override
	public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
		return true;
	}

	@Override
	public void onLongPress(MotionEvent e) {
	}

	@Override
	public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
		tv_rough.setText("您拖动我啦");
		float offsetX = e2.getRawX() - e1.getRawX();
		float offsetY = e2.getRawY() - e1.getRawY();
		tv_rough.smoothScrollBy((int)offsetX, (int)offsetY);
		return false;
	}

}




点此查看Android开发笔记的完整目录



你可能感兴趣的:(android,scroller,手势,gesturedetector,TouchEvent)