Dibujando un SeekBar personalizado en Android con Canvas

18-07-2017 - Andrés Cruz

In english

Descarga

En esta entrada veremos cómo crear un sencillo SeekBar mediante la API de Canvas en Android; nuevamente haremos uso del proyecto de Telegram que puedes encontrar en la sección de recursos en esta misma web, y ya con este vendría siendo la segunda entrada que le dedicamos a analizar un poco ciertas secciones de la aplicación de Telegram:

Cómo realizar una actividad de presentación animada en Android con ViewPager

El SeekBar a realizar es realmente sencillo, solo consiste de una barra lateral (un rectángulo achatado) y una circunferencia que hará la vez de control y es la que manipularemos mediante un clic o "gesto" para desplazarla de derecha a izquierda y viceversa.

Definiendo las bases: la estructura de la actividad

Primero debemos definir la estructura de nuestra actividad que es la que dará soporte al SeekBar dibujado mediante Canvas en una otra clase; en el layout de nuestra actividad hacemos realmente poco; simplemente definimos un layout vacío como el siguiente:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
   xmlns:tools="http://schemas.android.com/tools"
   android:id="@+id/cons"
   android:layout_width="match_parent"
   android:layout_height="match_parent"
   android:orientation="vertical"
   tools:context="com.presentacion.desarrollolibre.audioplayer.MainActivity">
</LinearLayout>

Ya nos encargamos desde la actividad de crear un FrameLayout con unas dimensiones fijas de 300 x 300 empleando el siguiente código Java:

SeekBarView seekBarView = new SeekBarView(MainActivity.this);
FrameLayout.LayoutParams myFrameLayoutParams = new FrameLayout.LayoutParams(300,300);
seekBarView.setLayoutParams(myFrameLayoutParams);
setContentView(seekBarView);

La clase SeekBarView para dibujar el SeekBar mediante Canvas

Como veremos en el experimento, la clase SeekBarView extiende de la clase FrameLayout y por lo tanto debe sobrescribir ciertos métodos de esta clase; en el método constructor de la clase nos encargamos de crear un objeto de tipo Paint que nos permite dibujar formas geométricas (nuestro rectangulo y circulo) y dibujarlo dentro de un Canvas; ademas definimos dos variables con un tamaño fijo que nos serviran para otro propósito:

thumbWidth = dp(24);
thumbHeight = dp(24);

Estas variables se encargan de definir la posición del rectángulo que dibujamos con el método onDraw() de esta clase SeekBarView; el método onDraw() que sobreescribimos, se encarga de crear el lienzo/canvas y dibujar las figuras geométricas (rectángulo y círculo) en nuestro lienzo/canvas:

canvas.drawRect(thumbWidth / 2, getMeasuredHeight() / 2 - dp(1), thumbWidth / 2 + thumbX, getMeasuredHeight() / 2 + dp(1), outerPaint1);

El método getMeasuredHeight() retorna el largo del contenedor que definimos en la actividad, que para este experimento es de 300.

El método getMeasuredWidth() retorna el ancho del contenedor que definimos en la actividad, que para este experimento es de 300.

Además de una barra que dibujamos con la función anterior de drawRect, dibujamos un círculo con ayuda de la primitiva drawCircle.

canvas.drawCircle(thumbX + thumbWidth / 2, y + thumbHeight / 2, dp(pressed ? 8 : 6), outerPaint1);

Por último se crea un método onTouch que se ejecuta para mover el círculo según el toque del usuario sobre el Canvas (una especie de evento onClick mantenido). El método onTouch es bastante interesante ya que se realizan los cálculos para reubicar la bola según la actualización de la variable thumbX la cual se actualiza según la posición del clic del usuario en la SeekBar; el método onTouch se encarga de llamar de manera automática el método onDraw y de esta forma se redibuja la bola según la posición actualizada:

boolean onTouch(MotionEvent ev) {
    if (ev.getAction() == MotionEvent.ACTION_DOWN) {
        getParent().requestDisallowInterceptTouchEvent(true);

        int additionWidth = (getMeasuredHeight() - thumbWidth) / 2;
        if (thumbX - additionWidth <= ev.getX() && ev.getX() <= thumbX + thumbWidth + additionWidth && ev.getY() >= 0 && ev.getY() <= getMeasuredHeight()) {
            pressed = true;
            thumbDX = (int) (ev.getX() - thumbX);
            invalidate();
            return true;
        }
    } else if (ev.getAction() == MotionEvent.ACTION_UP || ev.getAction() == MotionEvent.ACTION_CANCEL) {
        if (pressed) {
            if (ev.getAction() == MotionEvent.ACTION_UP) {
//                        onSeekBarDrag((float) thumbX / (float) (getMeasuredWidth() - thumbWidth));
            }
            pressed = false;
            invalidate();
            return true;
        }
    } else if (ev.getAction() == MotionEvent.ACTION_MOVE) {
        if (pressed) {
            thumbX = (int) (ev.getX() - thumbDX);
            if (thumbX < 0) {
                thumbX = 0;
            } else if (thumbX > getMeasuredWidth() - thumbWidth) {
                thumbX = getMeasuredWidth() - thumbWidth;
            }
            invalidate();
            return true;
        }
    }
    return false;
}

En el método anterior onTouch existen varios eventos para; tenemos la constante ACTION_DOWN que se ejecuta al momento de iniciar el "gesto" o el clic de la persona en el Canvas y la constante ACTION_UP que se ejecuta al momento de terminar el "gesto" o el clic de la persona en el Canvas.

A raíz de estos estados, se actualiza la variable thumbX que es la que "mueve" o permite redibujar la bola mediante el método onDraw().

De igual manera puedes encontrar el código fuente en github al inicio y final de esta entrada:

Descarga


Andrés Cruz
Desarrollo con Laravel, Django, Flask, CodeIgniter, HTML5, CSS3, MySQL, JavaScript, Vue, Android, iOS, Flutter

Andrés Cruz en Udemy

Acepto recibir anuncios de interes sobre este Blog.

!Cursos a!

10$

En Udemy

Quedan 5 días!

Ver los cursos
¡Hazte afiliado en Gumroad!

!Cursos desde!

4$

En Academia

Ver los cursos

!Libros desde!

1$

Ver los libros
!Web Alojada en Hostinger!