演练 - 在 Android 中使用触控
我们来看一看如何在工作应用程序中使用上一部分中的概念。 我们将创建一个包含四个活动的应用程序。 第一个活动将是一个菜单或一个切换板,用于启动其他活动来演示各种 API。 以下屏幕截图显示了主要活动:
第一个活动是触控示例,它将演示如何使用事件处理程序来触控视图。 手势识别器活动将演示如何创建子类 Android.View.Views
和处理事件,以及演示如何处理捏合手势。 第三个和最后一个活动是自定义手势,它将显示如何使用自定义手势。 为了便于理解,我们将本演练分为几个部分,其中每个部分将重点介绍其中一个活动。
触控示例活动
打开项目 TouchWalkthrough_Start。 MainActivity 已准备就绪,我们需要实施活动中的触控行为。 如果运行应用程序并单击“触控示例”,则应启动以下活动:
现在,我们已确认活动启动,可以打开文件 TouchActivity.cs 并为
ImageView
的Touch
事件添加处理程序:_touchMeImageView.Touch += TouchMeImageViewOnTouch;
接下来,将以下方法添加到 TouchActivity.cs:
private void TouchMeImageViewOnTouch(object sender, View.TouchEventArgs touchEventArgs) { string message; switch (touchEventArgs.Event.Action & MotionEventActions.Mask) { case MotionEventActions.Down: case MotionEventActions.Move: message = "Touch Begins"; break; case MotionEventActions.Up: message = "Touch Ends"; break; default: message = string.Empty; break; } _touchInfoTextView.Text = message; }
请注意,在上面的代码中,我们将 Move
和 Down
视为相同的操作。 这是因为,即使用户可能未将手指抬离 ImageView
,它也可能会四处移动,或者用户施加的压力可能会发生改变。 这些类型的更改将生成 Move
操作。
每次用户触摸 ImageView
时,都会引发 Touch
事件,并且处理程序将在屏幕上显示“触控开始”消息,如以下屏幕截图所示:
只要用户触摸 ImageView
,就会在 TextView
中显示“触控开始”。 当用户不再触摸 ImageView
时,TextView
中就会显示消息“触控结束”,如以下屏幕截图所示:
手势识别器活动
现在,我们来实现手势识别器活动。 此活动将演示如何在屏幕上拖动视图,并演示一种实现捏合以缩放的方法。
将新活动添加到名为
GestureRecognizer
应用程序。 编辑此活动的代码,已使其类似于以下代码:public class GestureRecognizerActivity : Activity { protected override void OnCreate(Bundle bundle) { base.OnCreate(bundle); View v = new GestureRecognizerView(this); SetContentView(v); } }
向项目添加新的 Android 视图,并将其命名为
GestureRecognizerView
。 将以下变量添加到此类:private static readonly int InvalidPointerId = -1; private readonly Drawable _icon; private readonly ScaleGestureDetector _scaleDetector; private int _activePointerId = InvalidPointerId; private float _lastTouchX; private float _lastTouchY; private float _posX; private float _posY; private float _scaleFactor = 1.0f;
将下列构造函数添加到
GestureRecognizerView
。 此构造函数将向活动添加一个ImageView
。 此时,代码仍然不会编译 - 我们需要创建MyScaleListener
类,以在用户捏合它时帮助调整ImageView
的大小:public GestureRecognizerView(Context context): base(context, null, 0) { _icon = context.Resources.GetDrawable(Resource.Drawable.Icon); _icon.SetBounds(0, 0, _icon.IntrinsicWidth, _icon.IntrinsicHeight); _scaleDetector = new ScaleGestureDetector(context, new MyScaleListener(this)); }
要在活动上绘制图像,我们需要替代 View 类的
OnDraw
方法,如以下代码片段所示。 此代码会将ImageView
移动到_posX
和_posY
指定的位置,并根据缩放系数调整图像大小:protected override void OnDraw(Canvas canvas) { base.OnDraw(canvas); canvas.Save(); canvas.Translate(_posX, _posY); canvas.Scale(_scaleFactor, _scaleFactor); _icon.Draw(canvas); canvas.Restore(); }
接下来,我们需要在用户捏合
ImageView
时更新实例变量_scaleFactor
。 我们将添加一个名为MyScaleListener
的类。 当用户捏合ImageView
时,此类将会侦听由 Android 引发的缩放事件。 将以下内部类添加到GestureRecognizerView
。 此类是一个ScaleGesture.SimpleOnScaleGestureListener
。 此类是一个便利类,如果你对一部分手势感兴趣时,侦听器可以创建子类:private class MyScaleListener : ScaleGestureDetector.SimpleOnScaleGestureListener { private readonly GestureRecognizerView _view; public MyScaleListener(GestureRecognizerView view) { _view = view; } public override bool OnScale(ScaleGestureDetector detector) { _view._scaleFactor *= detector.ScaleFactor; // put a limit on how small or big the image can get. if (_view._scaleFactor > 5.0f) { _view._scaleFactor = 5.0f; } if (_view._scaleFactor < 0.1f) { _view._scaleFactor = 0.1f; } _view.Invalidate(); return true; } }
我们需要在
GestureRecognizerView
中替代的下一种方法是OnTouchEvent
。 以下代码列出了此方法的完整实现。 这里有很多代码,所以让我们花一分钟时间看看这里发生了什么。 此方法要执行的第一个操作是根据需要缩放图标 - 这是通过调用_scaleDetector.OnTouchEvent
来处理的。 接下来,我们尝试找出调用此方法的操作:如果用户触摸屏幕,我们将记录 X 和 Y 位置以及触摸屏幕的第一个指针的 ID。
如果用户在屏幕上移动了触摸点,则我们将确定用户移动指针的距离。
如果用户已将手指抬离屏幕,我们将停止跟踪手势。
public override bool OnTouchEvent(MotionEvent ev) { _scaleDetector.OnTouchEvent(ev); MotionEventActions action = ev.Action & MotionEventActions.Mask; int pointerIndex; switch (action) { case MotionEventActions.Down: _lastTouchX = ev.GetX(); _lastTouchY = ev.GetY(); _activePointerId = ev.GetPointerId(0); break; case MotionEventActions.Move: pointerIndex = ev.FindPointerIndex(_activePointerId); float x = ev.GetX(pointerIndex); float y = ev.GetY(pointerIndex); if (!_scaleDetector.IsInProgress) { // Only move the ScaleGestureDetector isn't already processing a gesture. float deltaX = x - _lastTouchX; float deltaY = y - _lastTouchY; _posX += deltaX; _posY += deltaY; Invalidate(); } _lastTouchX = x; _lastTouchY = y; break; case MotionEventActions.Up: case MotionEventActions.Cancel: // We no longer need to keep track of the active pointer. _activePointerId = InvalidPointerId; break; case MotionEventActions.PointerUp: // check to make sure that the pointer that went up is for the gesture we're tracking. pointerIndex = (int) (ev.Action & MotionEventActions.PointerIndexMask) >> (int) MotionEventActions.PointerIndexShift; int pointerId = ev.GetPointerId(pointerIndex); if (pointerId == _activePointerId) { // This was our active pointer going up. Choose a new // action pointer and adjust accordingly int newPointerIndex = pointerIndex == 0 ? 1 : 0; _lastTouchX = ev.GetX(newPointerIndex); _lastTouchY = ev.GetY(newPointerIndex); _activePointerId = ev.GetPointerId(newPointerIndex); } break; } return true; }
现在运行应用程序,并启动手势识别器活动。 当它启动时,屏幕外观应类似于下面的屏幕截图:
现在,触摸图标,并在屏幕上四处拖动它。 尝试捏合以缩放手势。 在某些时候,屏幕外观可能类似于以下屏幕截图:
此时,应该奖励一下自己:你刚刚在 Android 应用程序中实现了捏合以缩放设置! 休息片刻后,我们继续进行本演练中的第三个和最后一个活动 - 使用自定义手势。
自定义手势活动
本演练中的最后一个屏幕将使用自定义手势。
为了进行本次演练,已使用手势工具创建了手势库,并已将其添加到文件 Resources/raw/gestures 中的项目。 这些准备工作已经完成,我们开始完成演练的最后一个活动。
将名为 custom_gesture_layout.axml 的布局文件添加到包含以下内容的项目中。 项目已包含 Resources 文件夹中的所有图像:
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" android:layout_width="match_parent" android:layout_height="match_parent"> <LinearLayout android:layout_width="match_parent" android:layout_height="0dp" android:layout_weight="1" /> <ImageView android:src="@drawable/check_me" android:layout_width="match_parent" android:layout_height="0dp" android:layout_weight="3" android:id="@+id/imageView1" android:layout_gravity="center_vertical" /> <LinearLayout android:layout_width="match_parent" android:layout_height="0dp" android:layout_weight="1" /> </LinearLayout>
接下来,向项目添加新的活动并将其命名为
CustomGestureRecognizerActivity.cs
。 将两个实例变量添加到该类,如以下两行代码所示:private GestureLibrary _gestureLibrary; private ImageView _imageView;
编辑此活动的
OnCreate
方法,以将其修改为类似于以下代码。 我们来花一分钟时间解释以下此代码中发生的情况。 我们要做的第一件事是将GestureOverlayView
实例化并将其设置为活动的根视图。 我们还将事件处理程序分配给GestureOverlayView
的GesturePerformed
事件。 接下来,我们将扩充之前创建的布局文件,并将其添加为GestureOverlayView
的子视图。 最后一步是初始化变量_gestureLibrary
,并从应用程序资源加载手势文件。 如果由于某种原因无法加载手势文件,则此活动会无事可做,因此会被关闭:protected override void OnCreate(Bundle bundle) { base.OnCreate(bundle); GestureOverlayView gestureOverlayView = new GestureOverlayView(this); SetContentView(gestureOverlayView); gestureOverlayView.GesturePerformed += GestureOverlayViewOnGesturePerformed; View view = LayoutInflater.Inflate(Resource.Layout.custom_gesture_layout, null); _imageView = view.FindViewById<ImageView>(Resource.Id.imageView1); gestureOverlayView.AddView(view); _gestureLibrary = GestureLibraries.FromRawResource(this, Resource.Raw.gestures); if (!_gestureLibrary.Load()) { Log.Wtf(GetType().FullName, "There was a problem loading the gesture library."); Finish(); } }
我们需要执行最后一个操作来实现方法
GestureOverlayViewOnGesturePerformed
,如以下代码片段所示。 当GestureOverlayView
检测到手势时,它会回调此方法。 我们首先尝试通过调用_gestureLibrary.Recognize()
来获取与手势匹配的IList<Prediction>
对象。 使用一些 LINQ 获取具有该手势最高分数的Prediction
。如果没有具有足够高分数的匹配手势,则事件处理程序将退出而不执行任何操作。 否则,我们将检查预测的名称,并根据手势的名称更改所显示的图像:
private void GestureOverlayViewOnGesturePerformed(object sender, GestureOverlayView.GesturePerformedEventArgs gesturePerformedEventArgs) { IEnumerable<Prediction> predictions = from p in _gestureLibrary.Recognize(gesturePerformedEventArgs.Gesture) orderby p.Score descending where p.Score > 1.0 select p; Prediction prediction = predictions.FirstOrDefault(); if (prediction == null) { Log.Debug(GetType().FullName, "Nothing seemed to match the user's gesture, so don't do anything."); return; } Log.Debug(GetType().FullName, "Using the prediction named {0} with a score of {1}.", prediction.Name, prediction.Score); if (prediction.Name.StartsWith("checkmark")) { _imageView.SetImageResource(Resource.Drawable.checked_me); } else if (prediction.Name.StartsWith("erase", StringComparison.OrdinalIgnoreCase)) { // Match one of our "erase" gestures _imageView.SetImageResource(Resource.Drawable.check_me); } }
运行应用程序并启动自定义手势识别器活动。 它应类似于以下屏幕截图:
现在,在屏幕上绘制复选标记,所显示的位图应类似于下一屏幕截图所示:
最后,在屏幕上绘制一个涂鸦。 该复选框应更改回其原始图像,如以下屏幕截图所示:
现在,你已了解如何使用 Xamarin.Android 在 Android 应用程序中集成触控和手势。