前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Android开发笔记(四十五)手势事件

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

作者头像
aqi00
发布2019-01-18 13:00:32
1.2K0
发布2019-01-18 13:00:32
举报
文章被收录于专栏:老欧说安卓老欧说安卓

手势事件的流程

基本手势事件

基本的手势事件主要有如下三个方法: 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)->结束 更详细的生命周期见下图所示:

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接管触摸事件 : 

代码语言:javascript
复制
	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告知上级可以拦截。下面示例代码演示了这么一个意图:当用户按下或者滑动时,当前控件需要响应手势事件,请上级视图不要拦截手势;当用户松开或取消时,当前控件已经处理完毕,允许上级视图拦截手势。

代码语言:javascript
复制
@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拦截垂直滑动而放过水平滑动的功能:

代码语言:javascript
复制
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的效果图:

代码语言:javascript
复制
<span style="font-family: Arial, Helvetica, sans-serif; background-color: rgb(255, 255, 255);">ScrollTextView控件的源码如下:</span>
代码语言:javascript
复制
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调用滑动控件的代码如下:

代码语言:javascript
复制
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开发笔记的完整目录

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2016年01月12日,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 手势事件的流程
    • 基本手势事件
      • 手势方法的执行者
        • 手势事件的生命周期
          • TouchEvent
          • 手势检测GestureDetector
          • 滑动冲突的处理
          • 弹性滑动
            • 滑动计算器Scroller
              • View的滑动方法
                • 视图滑动例子
                相关产品与服务
                容器服务
                腾讯云容器服务(Tencent Kubernetes Engine, TKE)基于原生 kubernetes 提供以容器为核心的、高度可扩展的高性能容器管理服务,覆盖 Serverless、边缘计算、分布式云等多种业务部署场景,业内首创单个集群兼容多种计算节点的容器资源管理模式。同时产品作为云原生 Finops 领先布道者,主导开源项目Crane,全面助力客户实现资源优化、成本控制。
                领券
                问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档