Tutorial: Uso de la entrada táctil en Android
Veamos cómo usar los conceptos de la sección anterior en una aplicación en funcionamiento. Crearemos una aplicación con cuatro actividades. La primera actividad será un menú o un panel de control que iniciará las demás actividades para mostrar las distintas API. En la captura de pantalla siguiente se muestra la actividad principal:
En la primera actividad, Ejemplo de entrada táctil, se mostrará cómo usar controladores de eventos para tocar las vistas. La actividad Reconocedor de gestos mostrará cómo subclasificar Android.View.Views
, controlar eventos y controlar los gestos de reducción. La tercera y última actividad, Gestos personalizados, mostrará cómo usar gestos personalizados. Para facilitar la comprensión y el aprendizaje de estos temas, dividiremos este tutorial en secciones, con cada sección centrada en una de las actividades.
Actividad Ejemplo de entrada táctil
Abra el proyecto TouchWalkthrough_Start. MainActivity ya está preparada: solo nos falta decidir si implementamos el comportamiento de entrada táctil en la actividad. Si ejecuta la aplicación y hace clic en Ejemplo de entrada táctil, debe iniciarse la siguiente actividad:
Ahora que hemos confirmado que la actividad se inicia, abra el archivo TouchActivity.cs y agregue un controlador para el evento
Touch
deImageView
:_touchMeImageView.Touch += TouchMeImageViewOnTouch;
A continuación, agregue el método siguiente a 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; }
Observe en el código anterior que hemos tratado la acción Move
y Down
como la misma. Esto se debe a que aunque el usuario no levante el dedo de ImageView
, esta puede moverse o la presión que ejerce el usuario puede cambiar. Estos tipos de cambios generarán una acción Move
.
Cada vez que el usuario toque ImageView
, se generará el evento Touch
y nuestro controlador mostrará el mensaje Empieza la entrada táctil en la pantalla, como se muestra en la captura de pantalla siguiente:
Siempre que el usuario toque ImageView
, aparecerá el mensaje Empieza la entrada táctil en TextView
. Cuando el usuario deje de tocar ImageView
, aparecerá el mensaje Finaliza la entrada táctil en TextView
, como se muestra en la captura de pantalla:
Actividad Reconocedor de gestos
Ahora vamos a implementar la actividad Reconocedor de gestos. Esta actividad mostrará cómo arrastrar una vista alrededor de la pantalla y una manera de implementar el gesto de reducir o ampliar.
Agregue una nueva actividad a la aplicación llamada
GestureRecognizer
. Edite el código de esta actividad para que se parezca al código siguiente:public class GestureRecognizerActivity : Activity { protected override void OnCreate(Bundle bundle) { base.OnCreate(bundle); View v = new GestureRecognizerView(this); SetContentView(v); } }
Agregue una nueva vista de Android al proyecto y asígnele el nombre
GestureRecognizerView
. Agregue las siguientes variables a esta clase: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;
Agregue el constructor siguiente a
GestureRecognizerView
. Este constructor agregará un elementoImageView
a nuestra actividad. En este momento, el código todavía no se compilará: es necesario crear la claseMyScaleListener
que ayudará a cambiar el tamaño deImageView
cuando el usuario haga el gesto de reducir: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)); }
Para dibujar la imagen en nuestra actividad, es necesario invalidar el método
OnDraw
de la clase View como se muestra en el siguiente fragmento de código. Este código moveráImageView
a la posición especificada por_posX
y_posY
, y cambiará el tamaño de la imagen según el factor de escalado:protected override void OnDraw(Canvas canvas) { base.OnDraw(canvas); canvas.Save(); canvas.Translate(_posX, _posY); canvas.Scale(_scaleFactor, _scaleFactor); _icon.Draw(canvas); canvas.Restore(); }
A continuación, es necesario actualizar la variable de instancia
_scaleFactor
a medida que el usuario reduceImageView
. Agregaremos una clase denominadaMyScaleListener
. Esta clase escuchará los eventos de escalado que generará Android cuando el usuario reduzcaImageView
. Agregue la siguiente clase interna aGestureRecognizerView
. Esta clase esScaleGesture.SimpleOnScaleGestureListener
. Esta clase es una clase de conveniencia que los clientes de escucha pueden subclasificar cuando esté interesado en un subconjunto de gestos: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; } }
El siguiente método que tenemos que invalidar en
GestureRecognizerView
esOnTouchEvent
. En el código siguiente se muestra la implementación completa de este método. Hay mucho código aquí, así que dediquemos un momento a analizar lo que se está haciendo. Lo primero que hace este método es escalar el icono si es necesario; esto se controla llamando a_scaleDetector.OnTouchEvent
. A continuación, intentaremos averiguar qué acción llamó a este método:Si el usuario tocó la pantalla, registraremos las posiciones X e Y y el identificador del primer puntero que tocó la pantalla.
Si el usuario movió su entrada táctil en la pantalla, averiguaremos hasta qué punto el usuario movió el puntero.
Si el usuario ha levantado el dedo de la pantalla, detendremos el seguimiento de los gestos.
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; }
Ahora, ejecute la aplicación e inicie la actividad Reconocedor de gestos. Cuando se inicia la pantalla, esta debería tener un aspecto similar al de la captura de pantalla siguiente:
Ahora toque el icono y arrástrelo alrededor de la pantalla. Pruebe el gesto de reducir o ampliar. En algún momento, la pantalla puede parecerse a la siguiente captura de pantalla:
En este momento deberías darte una palmadita en la espalda: ¡acabas de implementar el gesto de reducir o ampliar en una aplicación Android! Descanse unos instantes y pasemos a la tercera y última actividad de este tutorial: el uso de gestos personalizados.
Actividad Gestos personalizados
La pantalla final de este tutorial usará gestos personalizados.
Para los fines de este tutorial, la biblioteca de gestos ya se ha creado mediante la herramienta de gestos y se ha agregado al proyecto en el archivo Resources/raw/gestures. Una vez aclarado esto, vamos a empezar con la actividad final del tutorial.
Agregue un archivo de diseño denominado custom_gesture_layout.axml al proyecto con el siguiente contenido. El proyecto ya tiene todas las imágenes en la carpeta 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>
Agregue una nueva actividad a la aplicación y asígnele el nombre
CustomGestureRecognizerActivity.cs
. Agregue dos variables de instancia a la clase, como se muestra en las dos líneas de código siguientes:private GestureLibrary _gestureLibrary; private ImageView _imageView;
Edite el método
OnCreate
de esta actividad para que se parezca al código siguiente. Dediquemos un momento a explicar lo que sucede en este código. Lo primero que se hace es crear una instancia deGestureOverlayView
y establecerla como vista raíz de la actividad. También se asigna un controlador de eventos al eventoGesturePerformed
deGestureOverlayView
. A continuación, se infla el archivo de diseño que se creó anteriormente y se agrega como una vista secundaria deGestureOverlayView
. El último paso consiste en inicializar la variable_gestureLibrary
y cargar el archivo de gestos de los recursos de la aplicación. Si el archivo de gestos no se puede cargar por algún motivo, no hay mucho que esta actividad pueda hacer, por lo que se apagará: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(); } }
Por último, se implementa el método
GestureOverlayViewOnGesturePerformed
, como se muestra en el siguiente fragmento de código. CuandoGestureOverlayView
detecta un gesto, vuelve a llamar a este método. Lo primero que intentamos obtener es un objetoIList<Prediction>
que coincida con el gesto mediante una llamada a_gestureLibrary.Recognize()
. Vamos a usar LINQ para obtener el elementoPrediction
que tiene la puntuación más alta para el gesto.Si no hay ningún gesto coincidente con una puntuación lo suficientemente alta, el controlador de eventos saldrá sin hacer nada. En caso contrario, comprobaremos el nombre de la predicción y cambiaremos la imagen que se muestra en función del nombre del gesto:
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); } }
Ejecute la aplicación e inicie la actividad Reconocedor de gestos personalizados. Debería tener un aspecto parecido al de la siguiente captura de pantalla:
Ahora, dibuje una marca de verificación en la pantalla y el mapa de bits que aparece tendrá un aspecto similar al que se muestra en las capturas de pantalla siguientes:
Por último, dibuje un garabato en la pantalla. La marca de verificación volverá a cambiar a su imagen original, como se muestra en estas capturas de pantalla:
Ahora tiene conocimientos sobre cómo integrar la entrada táctil y los gestos en una aplicación Android mediante Xamarin.Android.