Compartir a través de


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:

Captura de pantalla de ejemplo con el botón Tócame

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:

    Captura de pantalla de la actividad en la que se muestra Inicio de la entrada táctil

  • Ahora que hemos confirmado que la actividad se inicia, abra el archivo TouchActivity.cs y agregue un controlador para el evento Touch de ImageView:

    _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:

Captura de pantalla de la actividad con Inicio de la entrada táctil

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:

Captura de pantalla de la actividad con Final de la entrada táctil

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 elemento ImageView a nuestra actividad. En este momento, el código todavía no se compilará: es necesario crear la clase MyScaleListener que ayudará a cambiar el tamaño de ImageView 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 reduce ImageView. Agregaremos una clase denominada MyScaleListener. Esta clase escuchará los eventos de escalado que generará Android cuando el usuario reduzca ImageView. Agregue la siguiente clase interna a GestureRecognizerView. Esta clase es ScaleGesture.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 es OnTouchEvent. 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:

    Pantalla de inicio del Reconocedor de gestos con el icono de Android

  • 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:

    Los gestos mueven el icono alrededor de la 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 de GestureOverlayView y establecerla como vista raíz de la actividad. También se asigna un controlador de eventos al evento GesturePerformed de GestureOverlayView. A continuación, se infla el archivo de diseño que se creó anteriormente y se agrega como una vista secundaria de GestureOverlayView. 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. Cuando GestureOverlayView detecta un gesto, vuelve a llamar a este método. Lo primero que intentamos obtener es un objeto IList<Prediction> que coincida con el gesto mediante una llamada a _gestureLibrary.Recognize(). Vamos a usar LINQ para obtener el elemento Prediction 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:

    Captura de pantalla con la imagen Compruébame

    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:

    Marca de verificación dibujada, se reconoce la marca de verificación

    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:

    Scribble en la pantalla, se muestra la imagen original

Ahora tiene conocimientos sobre cómo integrar la entrada táctil y los gestos en una aplicación Android mediante Xamarin.Android.