65.9K
CodeProject 正在变化。 阅读更多。
Home

Android 中的更多触摸处理

starIconstarIconstarIconstarIconstarIcon

5.00/5 (3投票s)

2015年2月4日

CPOL

6分钟阅读

viewsIcon

11915

Android 中的更多触摸处理

引言

在我之前关于 Android 触摸处理的博文中,我只处理了简单的View派生控件中的触摸事件。但最近,我正在处理一个自定义的ListView,并接触到了ViewGroup派生控件中的触摸事件处理,其处理方式有所不同。

此外,在提供的源代码中,我已经将代码的执行配置化,以便您可以在您的 Android 手机上或没有手机时的模拟器上尝试触摸和多点触摸。

那么,废话不多说: 

背景

那么,有什么不同?

ViewGroup派生控件是一个控件,不出所料,它包含一组子控件。因此,当触摸ViewGroup时,必须决定事件是由控件本身处理,还是由子控件处理。

为此,必须实现一个可重写的方法 onInterceptTouchEvent

@Override
public boolean onInterceptTouchEvent (MotionEvent ev){
    // Do your stuff here
}

通过选择返回值,您可以让 Android 将任何后续事件发送到子视图或您的ViewGrouponTouchEvent方法。

  • 返回true:是的,您想拦截touchevent,这意味着任何后续事件都将传递到您的ViewGrouponTouchEvent方法。
  • 返回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 如果设置,onInterceptTouchEventreturnvalue将始终为true。如果未设置,则始终为false。它允许测试此方法最基本的功能。
几秒钟后开始返回TRUE startReturnTrueTimeOut 如果不是-1,则onInterceptTouchEvent将在设定的值之后开始返回true。它允许测试当您从返回false切换到返回true时会发生什么,并且子控件背景的着色向您显示子控件收到的MotionEvent.ACTION_CANCEL(这里有一个陷阱:timeoutonInterceptTouchEvent方法中计算和评估,所以您必须持续移动才能获得正确的timeout)。
onTouchEvent中,在毫秒后开始返回FALSE startReturnFalseInOnToucheventTimeOut 如果不是-1,则onTouchEvent将在设定的值之后开始返回false。我想看看是否可以通过从onTouchEvent方法返回false来重新开始接收onInterceptTouch的调用,但没有成功。(这里有一个陷阱:timeoutonTouchEvent方法中计算和评估,所以您必须持续移动才能获得正确的timeout。)
在毫秒后停止Child1的捕获。 stopChild1CaptureTimeOut 如果不是-1,则子控件的onTouchEvent将在设定的值之后开始返回false。我想看看是否可以通过从子控件的onTouchEvent方法返回false来重新开始接收onInterceptTouch的调用,但没有成功。(这里有一个陷阱:timeoutonTouchEvent方法中计算和评估,所以您必须持续移动才能获得正确的timeout。)

视图中还有 2 个未配置的指示器。

红点绘制在onInterceptTouch方法中收到的最后坐标处。因此,如果它们移动,则表示您正在接收更多此类调用。一旦它们固定不动,该方法就不再被调用。

如果您触摸子控件内部,您还会看到一个白环。这个环由父控件绘制,其坐标来自子控件。如果它移动,则表示子控件的onTouchEvent方法正在被调用。它允许展示,一旦您在子控件中按下但移出其边界,子控件仍然会收到触摸事件。

最后,如果您触摸TouchVisualizerViewGroupView,将在您触摸视图的位置绘制一个白圆。

结论

关于 Android 平台触摸处理的大部分内容都是针对常规View的。通过本文,我想提供一些关于在ViewGroup派生类中处理触摸事件的信息,并提供一个应用程序供您试验其行为。

外部参考

归类于:Android, CodeProject 标签:android, codeproject

© . All rights reserved.