Android 中的更多触摸处理





5.00/5 (3投票s)
Android 中的更多触摸处理
引言
在我之前关于 Android 触摸处理的博文中,我只处理了简单的View
派生控件中的触摸事件。但最近,我正在处理一个自定义的ListView
,并接触到了ViewGroup
派生控件中的触摸事件处理,其处理方式有所不同。
此外,在提供的源代码中,我已经将代码的执行配置化,以便您可以在您的 Android 手机上或没有手机时的模拟器上尝试触摸和多点触摸。
那么,废话不多说:
背景
那么,有什么不同?
ViewGroup
派生控件是一个控件,不出所料,它包含一组子控件。因此,当触摸ViewGroup
时,必须决定事件是由控件本身处理,还是由子控件处理。
为此,必须实现一个可重写的方法 onInterceptTouchEvent。
@Override
public boolean onInterceptTouchEvent (MotionEvent ev){
// Do your stuff here
}
通过选择返回值,您可以让 Android 将任何后续事件发送到子视图或您的ViewGroup
的onTouchEvent
方法。
- 返回
true
:是的,您想拦截touchevent
,这意味着任何后续事件都将传递到您的ViewGroup
的onTouchEvent
方法。 - 返回
false
:不,您不想拦截touchevent
,这意味着子视图将接收touchevent
(当然,前提是它们发生在它们的表面上)。
在最后一种情况下,如果您返回false
,Android 将继续为您的ViewGoup
派生控件的任何子控件表面上的每个新touchevent
调用您的方法。因此,根据某些条件,您总可以决定停止转发它们,最终返回true
。
请再次阅读上一句话中的““继续为您的ViewGoup
派生控件的任何子控件表面上的每个新touchevent
调用您的方法””。因此,如果您没有触摸到子控件,您的onInterceptTouchEvent
将只会被调用一次!
另一方面,一旦您从该方法返回true
,您的onTouchEvent
方法将为任何未来事件调用。但对于当前的手势,您已经无法将任何触摸发送到子视图了(当然,除非您自己编程实现)。如果任何子控件正在接收触摸事件,它将收到一个操作为MotionEvent.ACTION_CANCEL
的事件。
请在家尝试:代码
附带的代码和 Github 项目是来自原始博文 Android 中的触摸处理 的代码,并添加了新代码来演示上述用例。
TouchVisualizerViewGroupActivity
:显示新视图的 Activity。TouchVisualizerViewGroupView
:一个自定义ViewGroup
,用于演示上述概念。TouchVisualizerViewGroupChildView
:一个自定义View
,作为TouchVisualizerViewGroupView
的子控件,用于演示上述概念。TouchVisualizerViewGroupConfigActivity
:一个Activity
,用于配置各种属性以演示各种用例。
ViewGroup 子控件:TouchVisualizerViewGroupChildView
此类基于原始的TouchVisualizerSingleTouchGraphicView
类,用于演示单点触摸。下面,我将只展示为了支持演示新概念而进行的更改。
@Override
public void onDraw(Canvas canvas) {
// draw a border so we can differentiate the children in their parent
// also, if we received the cancel event in the onTouchEvent method, we draw a filled rectangle
paint.setStyle(Paint.Style.STROKE);
if(isCancelled) {
paint.setStyle(Paint.Style.FILL);
}
canvas.drawRect(0, 0, this.getWidth()-1, this.getHeight()-1, paint);
if(downX > 0)
{
paint.setStyle(Paint.Style.FILL);
canvas.drawCircle(downX, downY, getScreenSize(touchCircleRadius), paint);
}
// show the time left for the timeout to expire
if(beginChild1CaptureTime != -1) {
canvas.drawText(String.valueOf(remainderChild1CaptureTime)
+ "?"
+ String.valueOf(stopChild1CaptureTimeOut), 0, 10, paint);
}
}
在onDraw
方法中,添加了代码以绘制边框,以便我们可以区分其父ViewGroup
容器中的各个子控件。最后,添加了一些代码来显示一些计时器值。当您继续阅读时,这一点就会变得清晰。
@Override
public boolean onTouchEvent(MotionEvent event) {
// The usual processing here
// We added a callback to be able to monitor the touches of the children in the parent
if(this.touchListener != null)
{
this.touchListener.onTouchHappened(childId, action, event.getX(), event.getY());
}
remainderChild1CaptureTime =
Math.abs(beginChild1CaptureTime - System.currentTimeMillis());
if((stopChild1CaptureTimeOut != -1)
&& (remainderChild1CaptureTime > stopChild1CaptureTimeOut)) {
stopChild1CaptureTimeOut = -1;
return false;
}
boolean result = false;
switch (action) {
case MotionEvent.ACTION_DOWN:
// nothing changed here
break;
case MotionEvent.ACTION_MOVE:
// nothing changed here
break;
case MotionEvent.ACTION_UP:
// nothing changed here
break;
case MotionEvent.ACTION_CANCEL:
// We now also monitor the ACTION_CANCEL event as it will
// get triggered in some circumstances
isCancelled = true;
downX = event.getX();
downY = event.getY();
result = false;
break;
case MotionEvent.ACTION_OUTSIDE:
// nothing changed here
break;
}
invalidate();
return result;
}
这里没什么特别的。
- 我们调用一个回调,通过它可以监控发送到子控件的
MotionEvent
。我们将在父ViewGroup
控件中使用它来查看当我们移出子控件的表面时,onTouchEvent
是如何被调用的。 - 我们计算
timeout
值,使我们能够指定何时始终从方法中返回false
。 - 我们监控额外的操作
MotionEvent.ACTION_CANCEL
,如果发生这种情况,我们将设置控件的背景。
设置timeout
值是在父控件TouchVisualizerViewGroupView
视图的配置中完成的:即TouchVisualizerViewGroupConfigActivity
Activity。
ViewGroup:TouchVisualizerViewGroupView
此视图允许试验本文中的概念。
@Override
public void dispatchDraw(Canvas canvas) {
super.dispatchDraw(canvas);
interceptPaint.setStyle(Paint.Style.FILL);
canvas.drawCircle(padding / 2, lastInterceptY, padding / 2, interceptPaint);
canvas.drawCircle(lastInterceptX, padding/2, padding/2, interceptPaint);
if(downX > 0)
{
markerPaint.setStyle(Paint.Style.FILL);
canvas.drawCircle(downX, downY, getScreenSize(touchCircleRadius), markerPaint);
}
if(childDownX > 0)
{
Point ulCorner = getChildULCorner(childId, left, top, right, bottom);
interceptPaint.setStyle(Paint.Style.STROKE);
canvas.drawCircle(ulCorner.x + childDownX,
ulCorner.y + childDownY,
getScreenSize(touchCircleRadius + pressureRingOffset),
interceptPaint);
}
if(beginReturnTrueTimeOut != -1) {
canvas.drawText(String.valueOf(remainderReturnTrueTimeOut)
+ "?" + String.valueOf(startReturnTrueTimeOut), 0, 10, markerPaint);
}
if(beginReturnFalseInOnToucheventTimeOut != -1) {
canvas.drawText(String.valueOf(remainderReturnFalseInOnToucheventTimeOut)
+ "?"
+ String.valueOf(startReturnFalseInOnToucheventTimeOut), 0, 20, markerPaint);
}
}
dispatchDraw
(是的,ViewGroup
的绘制方式与简单View
不同)会在边距绘制两个红点,其 X 和 Y 坐标是最后一次onInterceptTouchEvent
调用的坐标(见下文)。接下来,在onTouchEvent
方法中设置的位置绘制一个白圆。然后,我们绘制可配置的timeout
值。
@Override
public boolean onInterceptTouchEvent (MotionEvent ev)
{
lastInterceptX = ev.getX();
lastInterceptY = ev.getY();
this.invalidate();
remainderReturnTrueTimeOut =
Math.abs(beginReturnTrueTimeOut - System.currentTimeMillis());
if((startReturnTrueTimeOut != -1)
&& (remainderReturnTrueTimeOut > startReturnTrueTimeOut)) {
startReturnTrueTimeOut = -1;
return true;
}
return interceptTouchEvent;
}
@Override
public boolean onTouchEvent(MotionEvent event) {
if(callBaseClass)
{
super.onTouchEvent(event);
}
if(!handleOnTouchEvent)
{
return false;
}
remainderReturnFalseInOnToucheventTimeOut =
Math.abs(beginReturnFalseInOnToucheventTimeOut - System.currentTimeMillis());
if((startReturnFalseInOnToucheventTimeOut != -1)
&& (remainderReturnFalseInOnToucheventTimeOut
> startReturnFalseInOnToucheventTimeOut)) {
startReturnFalseInOnToucheventTimeOut = -1;
return false;
}
int action = event.getAction();
boolean result = false;
switch (action) {
case MotionEvent.ACTION_DOWN:
downX = event.getX();
downY = event.getY();
if (returnValueOnActionDown)
{
result = returnValueOnActionDown;
}
break;
case MotionEvent.ACTION_MOVE:
downX = event.getX();
downY = event.getY();
if (returnValueOnActionMove)
{
result = returnValueOnActionMove;
}
break;
case MotionEvent.ACTION_UP:
downX = -1;
downY = -1;
if (returnValueOnActionUp)
{
result = returnValueOnActionUp;
}
break;
case MotionEvent.ACTION_OUTSIDE:
break;
}
invalidate();
return result;
}
@Override
public void onTouchHappened(int child, int action, float x, float y) {
childId = child;
childAction = action;
if(action != MotionEvent.ACTION_UP
&& action != MotionEvent.ACTION_CANCEL) {
childDownX = x;
childDownY = y;
}
else
{
childDownX = -1;
childDownY = -1;
}
}
同样,与上一篇博文一样,您有几个可配置的值,允许您试验各种用例。
下表将这些变量映射到配置设置。
配置 | 变量 | 它的作用 |
从onInterceptTouchEvent 返回TRUE |
interceptTouchEvent |
如果设置,onInterceptTouchEvent 的returnvalue 将始终为true 。如果未设置,则始终为false 。它允许测试此方法最基本的功能。 |
几秒钟后开始返回TRUE 。 |
startReturnTrueTimeOut |
如果不是-1 ,则onInterceptTouchEvent 将在设定的值之后开始返回true 。它允许测试当您从返回false 切换到返回true 时会发生什么,并且子控件背景的着色向您显示子控件收到的MotionEvent.ACTION_CANCEL (这里有一个陷阱:timeout 在onInterceptTouchEvent 方法中计算和评估,所以您必须持续移动才能获得正确的timeout )。 |
在onTouchEvent 中,在毫秒后开始返回FALSE 。 |
startReturnFalseInOnToucheventTimeOut |
如果不是-1 ,则onTouchEvent 将在设定的值之后开始返回false 。我想看看是否可以通过从onTouchEvent 方法返回false 来重新开始接收onInterceptTouch 的调用,但没有成功。(这里有一个陷阱:timeout 在onTouchEvent 方法中计算和评估,所以您必须持续移动才能获得正确的timeout 。) |
在毫秒后停止Child1 的捕获。 |
stopChild1CaptureTimeOut |
如果不是-1 ,则子控件的onTouchEvent 将在设定的值之后开始返回false 。我想看看是否可以通过从子控件的onTouchEvent 方法返回false 来重新开始接收onInterceptTouch 的调用,但没有成功。(这里有一个陷阱:timeout 在onTouchEvent 方法中计算和评估,所以您必须持续移动才能获得正确的timeout 。) |
视图中还有 2 个未配置的指示器。
红点绘制在onInterceptTouch
方法中收到的最后坐标处。因此,如果它们移动,则表示您正在接收更多此类调用。一旦它们固定不动,该方法就不再被调用。
如果您触摸子控件内部,您还会看到一个白环。这个环由父控件绘制,其坐标来自子控件。如果它移动,则表示子控件的onTouchEvent
方法正在被调用。它允许展示,一旦您在子控件中按下但移出其边界,子控件仍然会收到触摸事件。
最后,如果您触摸TouchVisualizerViewGroupView
,将在您触摸视图的位置绘制一个白圆。
结论
关于 Android 平台触摸处理的大部分内容都是针对常规View
的。通过本文,我想提供一些关于在ViewGroup
派生类中处理触摸事件的信息,并提供一个应用程序供您试验其行为。
外部参考
归类于:Android, CodeProject 标签:android, codeproject