简单讲解Android开发中触摸和点击事件的相关编程方法

在Android上,不止一个途径来侦听用户和应用程序之间交互的事件。对于用户界面里的事件,侦听方法就是从与用户交互的特定视图对象截获这些事件。视图类提供了相应的手段。

在各种用来组建布局的视图类里面,你可能会注意到一些公共的回调方法看起来对用户界面事件有用。这些方法在该对象的相关动作发生时被Android框架调用。比如,当一个视图(如一个按钮)被触摸时,该对象上的onTouchEvent()方法会被调用。不过,为了侦听这个事件,你必须扩展这个类并重写该方法。很明显,扩展每个你想使用的视图对象(只是处理一个事件)是荒唐的。这就是为什么视图类也包含了一个嵌套接口的集合,这些接口含有实现起来简单得多的回调函数。这些接口叫做事件侦听器event listeners,是用来截获用户和你的界面交互动作的"门票"。

当你更为普遍的使用事件侦听器来侦听用户动作时,总有那么一次你可能得为了创建一个自定义组件而扩展一个视图类。也许你想扩展按钮Button类来使某些事更花哨。在这种情况下,你将能够使事件处理器event handlers类来为你的类定义缺省事件行为。

事件侦听器Event Listeners

事件侦听器是视图View类的接口,包含一个单独的回调方法。这些方法将在视图中注册的侦听器被用户界面操作触发时由Android框架调用。下面这些回调方法被包含在事件侦听器接口中:

onClick():包含于View.OnClickListener。当用户触摸这个item(在触摸模式下),或者通过浏览键或跟踪球聚焦在这个item上,然后按下"确认"键或者按下跟踪球时被调用。
onLongClick():包含于View.OnLongClickListener。当用户触摸并控制住这个item(在触摸模式下),或者通过浏览键或跟踪球聚焦在这个item上,然后保持按下"确认"键或者按下跟踪球(一秒钟)时被调用。
onFocusChange():包含于View.OnFocusChangeListener。当用户使用浏览键或跟踪球浏览进入或离开这个item时被调用。
onKey():包含于View.OnKeyListener。当用户聚焦在这个item上并按下或释放设备上的一个按键时被调用。
onTouch():包含于View.OnTouchListener。当用户执行的动作被当做一个触摸事件时被调用,包括按下,释放,或者屏幕上任何的移动手势(在这个item的边界内)。
onCreateContextMenu():包含于View.OnCreateContextMenuListener。当正在创建一个上下文菜单的时候被调用(作为持续的"长点击"动作的结果)。
这些方法是它们相应接口的唯一"住户"。要定义这些方法并处理你的事件,在你的活动中实现这个嵌套接口或定义它为一个匿名类。然后,传递你的实现的一个实例给各自的View.set...Listener() 方法。(比如,调用setOnClickListener()并传递给它你的OnClickListener实现。)

下面的例子说明了如何为一个按钮注册一个点击侦听器:

// Create an anonymous implementation of OnClickListener
private OnClickListener mCorkyListener = new OnClickListener() {
  public void onClick(View v) {
   // do something when the button is clicked
  }
};

protected void onCreate(Bundle savedValues) {
  ...
  // Capture our button from layout
  Button button = (Button)findViewById(R.id.corky);
  // Register the onClick listener with the implementation above
  button.setOnClickListener(mCorkyListener);
  ...
}

你可能会发现把OnClickListener作为活动的一部分来实现会便利的多。这将避免额外的类加载和对象分配。比如:

public class ExampleActivity extends Activity implements OnClickListener {
  protected void onCreate(Bundle savedValues) {
    ...
    Button button = (Button)findViewById(R.id.corky);
    button.setOnClickListener(this);
  }

  // Implement the OnClickListener callback
  public void onClick(View v) {
   // do something when the button is clicked
  }
  ...
}

注意上面例子中的onClick()回调没有返回值,但是一些其它事件侦听器必须返回一个布尔值。原因和事件相关。对于其中一些,原因如下:

onLongClick() – 返回一个布尔值来指示你是否已经消费了这个事件而不应该再进一步处理它。也就是说,返回true 表示你已经处理了这个事件而且到此为止;返回false 表示你还没有处理它和/或这个事件应该继续交给其他on-click侦听器。
onKey() –返回一个布尔值来指示你是否已经消费了这个事件而不应该再进一步处理它。也就是说,返回true 表示你已经处理了这个事件而且到此为止;返回false 表示你还没有处理它和/或这个事件应该继续交给其他on-key侦听器。
onTouch() - 返回一个布尔值来指示你的侦听器是否已经消费了这个事件。重要的是这个事件可以有多个彼此跟随的动作。因此,如果当接收到向下动作事件时你返回false,那表明你还没有消费这个事件而且对后续动作也不感兴趣。那么,你将不会被该事件中的其他动作调用,比如手势或最后出现向上动作事件。
记住按键事件总是递交给当前焦点所在的视图。它们从视图层次的顶层开始被分发,然后依次向下,直到到达恰当的目标。如果你的视图(或者一个子视图)当前拥有焦点,那么你可以看到事件经由dispatchKeyEvent()方法分发。除了从你的视图截获按键事件,还有一个可选方案,你还可以在你的活动中使用onKeyDown() and onKeyUp()来接收所有的事件。

注意: Android 将首先调用事件处理器,其次是类定义中合适的缺省处理器。这样,从这些事情侦听器中返回true 将停止事件向其它事件侦听器传播并且也会阻塞视图中的缺事件处理器的回调函数。因此当你返回true时确认你希望终止这个事件。

事件处理器Event Handlers

如果你从视图创建一个自定义组件,那么你将能够定义一些回调方法被用作缺省的事件处理器。在创建自定义组件Building Custom Components的文档中,你将学习到一些用作事件处理的通用回调函数,包括:

  • onKeyDown(int, KeyEvent) - 当一个新的按键事件发生时被调用。
  • onKeyUp(int, KeyEvent) - 当一个向上键事件发生时被调用。
  • onTrackballEvent(MotionEvent) - 当一个跟踪球运动事件发生时被调用。
  • onTouchEvent(MotionEvent) - 当一个触摸屏移动事件发生时调用。
  • onFocusChanged(boolean, int, Rect) - 当视图获得或者丢失焦点时被调用。

你应该知道还有一些其它方法,并不属于视图类的一部分,但可以直接影响你处理事件的方式。所以,当在一个布局里管理更复杂的事件时,考虑一下这些方法:

  • Activity.dispatchTouchEvent(MotionEvent) - 这允许你的活动可以在分发给窗口之前捕获所有的触摸事件。
  • ViewGroup.onInterceptTouchEvent(MotionEvent) - 这允许一个视图组ViewGroup 在分发给子视图时观察这些事件。
  • ViewParent.requestDisallowInterceptTouchEvent(boolean) - 在一个父视图之上调用这个方法来表示它不应该通过onInterceptTouchEvent(MotionEvent)来捕获触摸事件。

触摸模式Touch Mode

当用户使用方向键或跟踪球浏览用户界面时,有必要给用户可操作的item(比如按钮)设置焦点,这样用户可以知道哪个item将接受输入。不过,如果这个设备有触摸功能,而且用户通过触摸来和界面交互,那么就没必要高亮items,或者设定焦点到一个特定的视图。这样,就有一个交互模式 叫"触摸模式"。

对于一个具备触摸功能的设备,一旦用户触摸屏幕,设备将进入触摸模式。自此以后,只有isFocusableInTouchMode()为真的视图才可以被聚焦,比如文本编辑部件。其他可触摸视图,如按钮,在被触摸时将不会接受焦点;它们将只是在被按下时简单的触发on-click侦听器。任何时候用户按下方向键或滚动跟踪球,这个设备将退出触摸模式,然后找一个视图来接受焦点,用户也许不会通过触摸屏幕的方式来恢复界面交互。

触摸模式状态的维护贯穿整个系统(所有窗口和活动)。为了查询当前状态,你可以调用isInTouchMode() 来查看这个设备当前是否处于触摸模式中。

处理焦点Handling Focus

框架将根据用户输入处理常规的焦点移动。这包含当视图删除或隐藏,或者新视图出现时改变焦点。视图通过isFocusable()方法表明它们想获取焦点的意愿。

要改变视图是否可以接受焦点,可以调用setFocusable()。在触摸模式中,你可以通过isFocusableInTouchMode()查询一个视图是否允许接受焦点。你可以通过setFocusableInTouchMode()方法来改变它。焦点移动基于一个在给定方向查找最近邻居的算法。少有的情况是,缺省算法可能和开发者的意愿行为不匹配。在这些情况下,你可以通过下面布局文件中的XML属性提供显式的重写:nextFocusDown, nextFocusLeft, nextFocusRight, 和nextFocusUp。为失去焦点的视图增加这些属性之一。定义属性值为拥有焦点的视图的ID。比如:

<LinearLayout
  android:orientation="vertical"
  ... >
 <Button android:id="@+id/top"
     android:nextFocusUp="@+id/bottom"
     ... />
 <Button android:id="@+id/bottom"
     android:nextFocusDown="@+id/top"
     ... />
</LinearLayout>

通常,在这个竖向布局中,从第一个按钮向上浏览或者从第二个按钮向下都不会移动到其它地方。现在这个顶部按钮已经定义了底部按钮为nextFocusUp (反之亦然),浏览焦点将从上到下和从下到上循环移动。

如果你希望在用户界面中声明一个可聚焦的视图(通常不是这样),可以在你的布局定义中,为这个视图增加android:focusable XML 属性。把它的值设置成true。你还可以通过android:focusableInTouchMode在触摸模式下声明一个视图为可聚焦。

想请求一个接受焦点的特定视图,调用requestFocus()。

要侦听焦点事件(当一个视图获得或者失去焦点时被通知到),使用onFocusChange(),如上面事件侦听器Event Listeners所描述的那样。

触摸事件、点击事件的区别

针对屏幕上的一个View控件,Android如何区分应当触发onTouchEvent,还是onClick,亦或是onLongClick事件?
在Android中,一次用户操作可以被不同的View按次序分别处理,并将完全响应了用户一次UI操作称之为消费了该事件(consume),那么Android是按什么次序将事件传递的呢?又在什么情况下判定为消费了该事件?
      搞清楚这些问题对于编写出能正确响应UI操作的代码是很重要的,尤其当屏幕上的不同View需要针对此次UI操作做出各种不同响应的时候更是如此,一个典型例子就是用户在桌面上放置了一个Widget,那么当用户针对widget做各种操作时,桌面本身有的时候要对用户的操作做出响应,有时忽略。只有搞清楚事件触发和传递的机制才有可能保证在界面布局非常复杂的情况下,UI控件仍然能正确响应用户操作。
 
1.  onTouchEvent
     onTouchEvent中要处理的最常用的3个事件就是:ACTION_DOWN、ACTION_MOVE、ACTION_UP。
     这三个事件标识出了最基本的用户触摸屏幕的操作,含义也很清楚。虽然大家天天都在用它们,但是有一点请留意,ACTION_DOWN事件作为起始事件,它的重要性是要超过ACTION_MOVE和ACTION_UP的,如果发生了ACTION_MOVE或者ACTION_UP,那么一定曾经发生了ACTION_DOWN。
     从Android的源代码中能看到基于这种不同重要性的理解而实现的一些交互机制,SDK中也有明确的提及,例如在ViewGroup的onInterceptTouchEvent方法中,如果在ACTION_DOWN事件中返回了true,那么后续的事件将直接发给onTouchEvent,而不是继续发给onInterceptTouchEvent。
 
2.  onClick、onLongClick与onTouchEvent
     曾经看过一篇帖子提到,如果在View中处理了onTouchEvent,那么就不用再处理onClick了,因为Android只会触发其中一个方法。这个理解是不太正确的,针对某个view,用户完成了一次触碰操作,显然从传感器上得到的信号是手指按下和抬起两个操作,我们可以理解为一次Click,也可以理解为发生了一次ACTION_DOWN和ACTION_UP,那么Android是如何理解和处理的呢?
     在Android中,onClick、onLongClick的触发是和ACTION_DOWN及ACTION_UP相关的,在时序上,如果我们在一个View中同时覆写了onClick、onLongClick及onTouchEvent的话,onTouchEvent是最先捕捉到ACTION_DOWN和ACTION_UP事件的,其次才可能触发onClick或者onLongClick。主要的逻辑在View.java中的onTouchEvent方法中实现的:

case MotionEvent.ACTION_DOWN: 

  mPrivateFlags |= PRESSED; 

  refreshDrawableState(); 

  if ((mViewFlags & LONG_CLICKABLE) == LONG_CLICKABLE) { 

     postCheckForLongClick();  

  } 

  break; 

case MotionEvent.ACTION_UP: 

  if ((mPrivateFlags & PRESSED) != 0) { 

     boolean focusTaken = false; 

     if (isFocusable() && isFocusableInTouchMode() && !isFocused()) { 

        focusTaken = requestFocus(); 

     } 

  if (!mHasPerformedLongPress) { 

    if (mPendingCheckForLongPress != null) { 

       removeCallbacks(mPendingCheckForLongPress); 

    } 

    if (!focusTaken) { 

       performClick(); 

    } 

  } 

  … 

  break;

可以看到,Click的触发是在系统捕捉到ACTION_UP后发生并由performClick()执行的,performClick里会调用先前注册的监听器的onClick()方法:

public boolean performClick() { 

  … 

  if (mOnClickListener != null) { 

    playSoundEffect(SoundEffectConstants.CLICK); 

    mOnClickListener.onClick(this); 

    return true; 

  } 

    return false; 

}

LongClick的触发则是从ACTION_DOWN开始,由postCheckForLongClick()方法完成:

private void postCheckForLongClick() { 

   mHasPerformedLongPress = false; 

   if (mPendingCheckForLongPress == null) { 

     mPendingCheckForLongPress = new CheckForLongPress(); 

   } 

   mPendingCheckForLongPress.rememberWindowAttachCount(); 

   postDelayed(mPendingCheckForLongPress, ViewConfiguration.getLongPressTimeout()); 

}

可以看到,在ACTION_DOWN事件被捕捉后,系统会开始触发一个postDelayed操作,delay的时间在Eclair2.1上为500ms,500ms后会触发CheckForLongPress线程的执行:

class CheckForLongPress implements Runnable { 

… 

    public void run() { 

      if (isPressed() && (mParent != null) 

          && mOriginalWindowAttachCount == mWindowAttachCount) { 

        if (performLongClick()) { 

          mHasPerformedLongPress = true; 

        } 

      } 

    } 

… 

}

如果各种条件都满足,那么在CheckForLongPress中执行performLongClick(),在这个方法中将调用onLongClick():

public boolean performLongClick() { 

   … 

   if (mOnLongClickListener != null) { 

     handled = mOnLongClickListener.onLongClick(View.this); 

   } 

   … 

}

从实现中可以看到onClick()和onLongClick()方法是由ACTION_DOWN和ACTION_UP事件捕捉后根据各种情况最终确定是否触发的,也就是说如果我们在一个Activity或者View中同时监听或者覆写了onClick(),onLongClick()和onTouchEvent()方法,并不意味着只会发生其中一种。

(0)

相关推荐

  • android 多点触摸图片缩放的具体实现方法

    布局: 复制代码 代码如下: <?xml version="1.0" encoding="utf-8"?><RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"    android:id="@+id/relativeLayout1"    android:layout_width="fill_parent

  • 解析Android开发中多点触摸的实现方法

    多点触摸技术在实际开发过程中,用的最多的就是放大缩小功能.比如有一些图片浏览器,就可以用多个手指在屏幕上操作,对图片进行放大或者缩小.再比如一些浏览器,也可以通过多点触摸放大或者缩小字体.其实放大缩小也只是多点触摸的实际应用样例之一,有了多点触摸技术,在一定程度上就可以创新出更多的操作方式来,实现更酷的人机交互. 理论上,Android系统本身可以处理多达256个手指的触摸,这主要取决于手机硬件的支持.当然,支持多点触摸的手机,也不会支持这么多点,一般是支持2个点或者4个点.对于开发者来说,编写

  • Android实现手势滑动多点触摸放大缩小图片效果

    网上文章虽多,但是这种效果少之又少,我真诚的献上以供大家参考 实现原理:自定义ImageView对此控件进行相应的layout(动态布局). 这里你要明白几个方法执行的流程: 首先ImageView是继承自View的子类. onLayout方法:是一个回调方法.该方法会在在View中的layout方法中执行,在执行layout方法前面会首先执行setFrame方法. setFrame方法:判断我们的View是否发生变化,如果发生变化,那么将最新的l,t,r,b传递给View,然后刷新进行动态更新

  • Android应用开发中触摸屏手势识别的实现方法解析

    很多时候,利用触摸屏的Fling.Scroll等Gesture(手势)操作来操作会使得应用程序的用户体验大大提升,比如用Scroll手势在 浏览器中滚屏,用Fling在阅读器中翻页等.在Android系统中,手势的识别是通过 GestureDetector.OnGestureListener接口来实现的,不过William翻遍了Android的官方文档也没有找到一个相 关的例子,API Demo中的TouchPaint也仅仅是提到了onTouch事件的处理,没有涉及到手势. 我们先来明确一些概念

  • Android修改源码解决Alertdialog触摸对话框边缘消失的问题

    研究其父类时候发现,可以设置这么一条属性,在AlertDialog.Builder.create()之后才能调用这两个方法 方法一: setCanceledOnTouchOutside(false);调用这个方法时,按对话框以外的地方不起作用.按返回键还起作用 方法二: setCanceleable(false);调用这个方法时,按对话框以外的地方不起作用.按返回键也不起作用 这两个方法都属于Dialog方法,可查阅源码 修改后的源码如下: 复制代码 代码如下: case 1:         

  • android中处理各种触摸事件的方法浅谈

    Android里有两个类android.view.GestureDetectorandroid.view.GestureDetector.SimpleOnGestureListener(另外android.widget.Gallery好像是更牛x的OnGestureListener )1)新建一个类继承SimpleOnGestureListener,HahaGestureDetectorListener可以实现以下event事件.boolean onDoubleTap(MotionEvent e

  • Android触摸事件传递机制初识

    前言 今天总结的一个知识点是Andorid中View事件传递机制,也是核心知识点,相信很多开发者在面对这个问题时候会觉得困惑,另外,View的另外一个难题滑动冲突,比如在ScrollView中嵌套ListView,都是上下滑动,这该如何解决呢,它解决的依据就是View事件的传递机制,所以开发者需要对View的事件传递机制有较深入的理解. 目录 Activity.View.ViewGroup三者关系 触摸事件类型 事件传递三个阶段 View事件传递机制 ViewGroup事件传递机制 小结 Act

  • Android实现手势滑动多点触摸缩放平移图片效果

    现在app中,图片预览功能肯定是少不了的,用户基本已经形成条件反射,看到小图,点击看大图,看到大图两个手指开始进行放大,放大后,开始移动到指定部位. 一.概述 想要做到图片支持多点触控,自由的进行缩放.平移,需要了解几个知识点:Matrix , GestureDetector , ScaleGestureDetector 以及事件分发机制,ps:不会咋办,不会你懂的. 1.Matrix 矩阵,看深入了都是3维矩阵的乘啊什么的,怪麻烦的~~ 其实这么了解下就行了: Matrix 数据结构:3维矩阵

  • 解决Android SurfaceView绘制触摸轨迹闪烁问题的方法

    本文分享了解决SurfaceView触摸轨迹闪烁问题的方法,供大家参考,具体内容如下 第一种解决SurfaceView触摸轨迹闪烁问题的方法: 由于SurfaceView使用双缓存机制,两张画布轮流显示到屏幕上.那么,要存储触摸轨迹并避免两张画布内容不一致造成的闪烁问题,完全可以利用保存绘制过程并不断重新绘制的方法解决闪烁,而且这样还顺带解决了多次试验中偶尔出现的因为moveTo()函数不能读取到参数执行默认设置(参数设为上次的触摸点)而出现的断线连接闪烁问题,详细代码如下: package c

  • Android中SurfaceView和view画出触摸轨迹

    一.引言          想实现一个空白的画板,上面可以画出手滑动的轨迹,就这么一个小需求.一般就来讲就两种实现方式,view或者surfaceview.下面看看两种是如何实现的. 二.实现原理          先简单说一下实现原理:        (1)用一张白色的Bitmap作为画板        (2)用canvas在bitmap上画线        (3)为了画出平滑的曲线,要用canvas的drawPath(Path,Paint)方法.        (4)同时使用贝塞尔曲线来使曲

随机推荐