Cómo dibujar caras de reloj

Una vez que hayas configurado tu proyecto y agregado una clase que implementa el servicio de cara de reloj, puedes comenzar a escribir código para inicializar y dibujar tu cara de reloj personalizada.

Esta lección incluye ejemplos de la cara de reloj de muestra para demostrar cómo el sistema usa este servicio. Muchos aspectos de las implementaciones de servicio que se describen aquí (como la inicialización y detección de características del dispositivo) se aplican a cualquier cara de reloj, por lo que puedes reutilizar parte del código en tus propias caras.

Figura 1: Las caras de reloj analógico y digital en la cara de reloj de ejemplo.

Inicializa tu cara de reloj

Cuando el sistema carga tu servicio, deberías asignar e inicializar la mayoría de los recursos que necesita la cara de reloj, incluida la carga de recursos de mapa de bits, la creación de objetos de temporizadores para ejecutar animaciones personalizadas, la configuración de estilos de pintura y la realización de otros procesamientos. Por lo general, puedes realizar estas operaciones una sola vez y reutilizar sus resultados. Esta práctica mejora el rendimiento de la cara de reloj y facilita el mantenimiento del código.

Para inicializar la cara de reloj, sigue estos pasos:

  1. Declara variables para un temporizador personalizado, objetos gráficos y otros elementos.
  2. Inicializa los elementos de la cara de reloj en el método Engine.onCreate().
  3. Inicializa el temporizador personalizado en el método Engine.onVisibilityChanged().

En las siguientes secciones, se describen esos pasos en detalle.

Declara variables

Los recursos que inicializas cuando el sistema carga tu servicio deben ser accesibles en diferentes puntos a lo largo de tu implementación para que puedas volver a usarlos. Para lograrlo, declara las variables de miembro para esos recursos en la implementación WatchFaceService.Engine.

Declara variables para los siguientes elementos:

Objetos gráficos
La mayoría de las caras de reloj contienen al menos una imagen de mapa de bits que se utiliza como fondo de la cara de reloj, lo que se describe en Cómo crear una estrategia de implementación. Puedes usar imágenes de mapa de bits adicionales que representan agujas del reloj u otros elementos de diseño de la cara de reloj.
Temporizador periódico
El sistema notifica a la cara de reloj una vez por minuto cuando cambia la hora, pero algunas caras de reloj ejecutan animaciones en intervalos de tiempo personalizados. En esos casos, debes proporcionar un temporizador personalizado que marque con la frecuencia requerida para actualizar la cara de reloj.
Receptor de cambio de zona horaria
Los usuarios pueden modificar su zona horaria cuando viajan, y el sistema transmite este evento. La implementación del servicio debe registrar un receptor de emisión que reciba una notificación cuando cambia la zona horaria y actualizar la hora según corresponda.

El siguiente fragmento muestra cómo definir esas variables:

Kotlin

    private const val MSG_UPDATE_TIME = 0

    class Service : CanvasWatchFaceService() {
        ...
        inner class Engine : CanvasWatchFaceService.Engine() {

            private lateinit var calendar: Calendar

            // device features
            private var lowBitAmbient: Boolean = false

            // graphic objects
            private lateinit var backgroundBitmap: Bitmap
            private var backgroundScaledBitmap: Bitmap? = null
            private lateinit var hourPaint: Paint
            private lateinit var minutePaint: Paint

            // handler to update the time once a second in interactive mode
            private val updateTimeHandler: Handler = UpdateTimeHandler(WeakReference(this))

            // receiver to update the time zone
            private val timeZoneReceiver: BroadcastReceiver = object : BroadcastReceiver() {
                override fun onReceive(context: Context, intent: Intent) {
                    calendar.timeZone = TimeZone.getDefault()
                    invalidate()
                }
            }

            // service methods (see other sections)
            ...
        }
        ...
        private class UpdateTimeHandler(val engineReference: WeakReference<Engine>) : Handler() {
            override fun handleMessage(message: Message) {
                engineReference.get()?.apply {
                    when (message.what) {
                        MSG_UPDATE_TIME -> {
                            invalidate()
                            if (shouldTimerBeRunning()) {
                                val timeMs: Long = System.currentTimeMillis()
                                val delayMs: Long =
                                        INTERACTIVE_UPDATE_RATE_MS - timeMs % INTERACTIVE_UPDATE_RATE_MS
                                sendEmptyMessageDelayed(MSG_UPDATE_TIME, delayMs)
                            }
                        }
                    }
                }
            }
        }
        ...
    }
    

Java

    public class CanvasWatchFaceJava extends CanvasWatchFaceService {
        static final int MSG_UPDATE_TIME = 0;
        ...
        class Engine extends CanvasWatchFaceService.Engine {

            Calendar calendar;

            // device features
            boolean lowBitAmbient;

            // graphic objects
            Bitmap backgroundBitmap;
            Bitmap backgroundScaledBitmap;
            Paint hourPaint;
            Paint minutePaint;
            ...

            // handler to update the time once a second in interactive mode
            final Handler updateTimeHandler = new UpdateTimeHandler(new WeakReference<>(this));

            // receiver to update the time zone
            final BroadcastReceiver timeZoneReceiver = new BroadcastReceiver() {
                @Override
                public void onReceive(Context context, Intent intent) {
                    calendar.setTimeZone(TimeZone.getDefault());
                    invalidate();
                }
            };

            // service methods (see other sections)
            ...
        }
        ...
        private static class UpdateTimeHandler extends Handler {
            private WeakReference<Engine> engineReference;

            UpdateTimeHandler(WeakReference<Engine> engine) {
                this.engineReference = engine;
            }

            @Override
            public void handleMessage(Message message) {
                Engine engine = engineReference.get();
                if (engine != null) {
                    switch (message.what) {
                        case MSG_UPDATE_TIME:
                            engine.invalidate();
                            if (engine.shouldTimerBeRunning()) {
                                long timeMs = System.currentTimeMillis();
                                long delayMs = INTERACTIVE_UPDATE_RATE_MS
                                        - (timeMs % INTERACTIVE_UPDATE_RATE_MS);
                                sendEmptyMessageDelayed(MSG_UPDATE_TIME, delayMs);
                            }
                            break;
                    }
                }
            }
        }
        ...
    }
    

En el ejemplo anterior, el temporizador personalizado se implementa como una instancia de Handler que envía y procesa los mensajes demorados usando la cola de mensajes del subproceso. Para esta cara de reloj en particular, el temporizador personalizado marca una vez por segundo. Cuando el temporizador marca, el controlador llama al método invalidate() y, luego, el sistema llama al método onDraw() para volver a dibujar la cara de reloj.

Inicializa los elementos de la cara de reloj

Después de declarar las variables de miembro para los recursos de mapa de bits, estilos de pintura y otros elementos que volverás a usar cada vez que vuelvas a dibujar la cara de reloj, inicialízalos cuando el sistema cargue tu servicio. Al inicializar esos elementos solo una vez y reutilizarlos, se mejora el rendimiento y la duración de la batería.

En el método Engine.onCreate(), inicializa los siguientes elementos:

  • Carga la imagen de fondo.
  • Crea estilos y colores para dibujar objetos gráficos.
  • Asigna un objeto para calcular la hora.
  • Configura la IU del sistema.

En el siguiente fragmento, se muestra cómo inicializar esos elementos:

Kotlin

    override fun onCreate(holder: SurfaceHolder?) {
        super.onCreate(holder)

        // configure the system UI (see next section)
        ...

        // load the background image
        backgroundBitmap = (resources.getDrawable(R.drawable.bg, null) as BitmapDrawable).bitmap

        // create graphic styles
        hourPaint = Paint().apply {
            setARGB(255, 200, 200, 200)
            strokeWidth = 5.0f
            isAntiAlias = true
            strokeCap = Paint.Cap.ROUND
        }
        ...

        // allocate a Calendar to calculate local time using the UTC time and time zone
        calendar = Calendar.getInstance()
    }
    

Java

    @Override
    public void onCreate(SurfaceHolder holder) {
        super.onCreate(holder);

        // configure the system UI (see next section)
        ...

        // load the background image
        Resources resources = AnalogWatchFaceService.this.getResources();
        Drawable backgroundDrawable = resources.getDrawable(R.drawable.bg, null);
        backgroundBitmap = ((BitmapDrawable) backgroundDrawable).getBitmap();

        // create graphic styles
        hourPaint = new Paint();
        hourPaint.setARGB(255, 200, 200, 200);
        hourPaint.setStrokeWidth(5.0f);
        hourPaint.setAntiAlias(true);
        hourPaint.setStrokeCap(Paint.Cap.ROUND);
        ...

        // allocate a Calendar to calculate local time using the UTC time and time zone
        calendar = Calendar.getInstance();
    }
    

El mapa de bits del fondo se carga solo una vez cuando el sistema inicializa la cara de reloj. Los estilos gráficos son instancias de la clase Paint. Usa estos estilos para dibujar los elementos de la cara de reloj dentro del método Engine.onDraw(), como se describe en Cómo dibujar la cara de reloj.

Inicializa el temporizador personalizado

Como desarrollador de caras de reloj, decides la frecuencia con la que deseas actualizar la cara de reloj cuando proporcionas un temporizador personalizado que marca con la frecuencia requerida mientras el dispositivo está en modo interactivo. De esa manera, puedes crear animaciones personalizadas y otros efectos visuales.

Nota: En el modo ambiente, el sistema no llama de manera confiable al temporizador personalizado. Consulta Actualiza la cara de reloj en modo ambiente para obtener información sobre cómo hacerlo.

Se muestra una definición de temporizador de ejemplo de la clase AnalogWatchFaceService que marca una vez por segundo en la sección Declara variables. En el método Engine.onVisibilityChanged(), inicia el temporizador personalizado si se cumplen estas dos condiciones:

  • La cara de reloj es visible.
  • El dispositivo está en modo interactivo.

La clase AnalogWatchFaceService programa la siguiente marca del temporizador si es necesario de la siguiente manera:

Kotlin

    private fun updateTimer() {
        updateTimeHandler.removeMessages(MSG_UPDATE_TIME)
        if (shouldTimerBeRunning()) {
            updateTimeHandler.sendEmptyMessage(MSG_UPDATE_TIME)
        }
    }

    fun shouldTimerBeRunning(): Boolean = isVisible && !isInAmbientMode
    

Java

    private void updateTimer() {
        updateTimeHandler.removeMessages(MSG_UPDATE_TIME);
        if (shouldTimerBeRunning()) {
            updateTimeHandler.sendEmptyMessage(MSG_UPDATE_TIME);
        }
    }

    boolean shouldTimerBeRunning() {
        return isVisible() && !isInAmbientMode();
    }
    

Este temporizador personalizado marca una vez por segundo, como se describe en Declara variables.

En el método onVisibilityChanged(), inicia el temporizador si es necesario y registra el receptor para los cambios de zona horaria de la siguiente manera:

Kotlin

    override fun onVisibilityChanged(visible: Boolean) {
        super.onVisibilityChanged(visible)

        if (visible) {
            registerReceiver()

            // Update time zone in case it changed while we weren't visible.
            calendar.timeZone = TimeZone.getDefault()
        } else {
            unregisterReceiver()
        }

        // Whether the timer should be running depends on whether we're visible and
        // whether we're in ambient mode, so we may need to start or stop the timer
        updateTimer()
    }
    

Java

    @Override
    public void onVisibilityChanged(boolean visible) {
        super.onVisibilityChanged(visible);

        if (visible) {
            registerReceiver();

            // Update time zone in case it changed while we weren't visible.
            calendar.setTimeZone(TimeZone.getDefault());
        } else {
            unregisterReceiver();
        }

        // Whether the timer should be running depends on whether we're visible and
        // whether we're in ambient mode, so we may need to start or stop the timer
        updateTimer();
    }
    

Cuando la cara de reloj está visible, el método onVisibilityChanged() registra el receptor para los cambios de zona horaria. Si el dispositivo está en modo interactivo, este método también inicia el temporizador personalizado. Cuando la cara de reloj no está visible, este método detiene el temporizador personalizado y cancela el registro del receptor para los cambios de zona horaria. Los métodos registerReceiver() y unregisterReceiver() se implementan de la siguiente manera:

Kotlin

    private fun registerReceiver() {
        if (registeredTimeZoneReceiver) return
        registeredTimeZoneReceiver = true
        IntentFilter(Intent.ACTION_TIMEZONE_CHANGED).also { filter ->
            this@AnalogWatchFaceService.registerReceiver(timeZoneReceiver, filter)
        }
    }

    private fun unregisterReceiver() {
        if (!registeredTimeZoneReceiver) return
        registeredTimeZoneReceiver = false
        this@AnalogWatchFaceService.unregisterReceiver(timeZoneReceiver)
    }
    

Java

    private void registerReceiver() {
        if (registeredTimeZoneReceiver) {
            return;
        }
        registeredTimeZoneReceiver = true;
        IntentFilter filter = new IntentFilter(Intent.ACTION_TIMEZONE_CHANGED);
        AnalogWatchFaceService.this.registerReceiver(timeZoneReceiver, filter);
    }

    private void unregisterReceiver() {
        if (!registeredTimeZoneReceiver) {
            return;
        }
        registeredTimeZoneReceiver = false;
        AnalogWatchFaceService.this.unregisterReceiver(timeZoneReceiver);
    }
    

Actualiza la cara de reloj en modo ambiente

En modo ambiente, el sistema llama al método Engine.onTimeTick() cada un minuto. Por lo general, en este modo, es suficiente actualizar la cara de reloj una vez por minuto. Para actualizar la cara de reloj en modo interactivo, debes proporcionar un temporizador personalizado como se describe en Inicia el temporizador personalizado.

En el modo ambiente, la mayoría de las implementaciones de cara de reloj simplemente invalidan el lienzo para volver a dibujar la cara de reloj con el método Engine.onTimeTick():

Kotlin

    override fun onTimeTick() {
        super.onTimeTick()

        invalidate()
    }
    

Java

    @Override
    public void onTimeTick() {
        super.onTimeTick();

        invalidate();
    }
    

Configura la IU del sistema

Las caras de reloj no deben interferir con los elementos de la IU del sistema, como se describe en Cómo incorporar los elementos de la IU del sistema. Si la cara de reloj tiene un fondo claro o muestra información cerca de la parte inferior de la pantalla, es posible que debas configurar el tamaño de las tarjetas de notificación o habilitar la protección de fondo.

Wear OS by Google te permite configurar los siguientes aspectos de la IU del sistema cuando la cara de reloj está activa:

  • Especificar si el sistema dibuja la hora en la cara de reloj.
  • Proteger los indicadores del sistema con un fondo sólido alrededor de ellos.
  • Especificar el posicionamiento de los indicadores del sistema.

Para configurar estos aspectos de la IU del sistema, crea una instancia de WatchFaceStyle y pásala al método Engine.setWatchFaceStyle().

La clase AnalogWatchFaceService configura la IU del sistema de la siguiente manera:

Kotlin

    override fun onCreate(holder: SurfaceHolder?) {
        super.onCreate(holder)

        // configure the system UI
        setWatchFaceStyle(WatchFaceStyle.Builder(this@AnalogWatchFaceService)
                .setBackgroundVisibility(WatchFaceStyle.BACKGROUND_VISIBILITY_INTERRUPTIVE)
                .setShowSystemUiTime(false)
                .build())
        ...
    }
    

Java

    @Override
    public void onCreate(SurfaceHolder holder) {
        super.onCreate(holder);

        // configure the system UI
        setWatchFaceStyle(new WatchFaceStyle.Builder(AnalogWatchFaceService.this)
                .setBackgroundVisibility(WatchFaceStyle
                                        .BACKGROUND_VISIBILITY_INTERRUPTIVE)
                .setShowSystemUiTime(false)
                .build());
        ...
    }
    

El fragmento de código anterior configura la hora del sistema para que no se muestre (porque esta cara de reloj dibuja su propia representación de hora).

Puedes configurar el estilo de la IU del sistema en cualquier momento de la implementación de la cara de reloj. Por ejemplo, si el usuario selecciona un fondo blanco, puedes agregar protección de fondo para los indicadores del sistema.

Para obtener más detalles sobre la configuración de la IU del sistema, consulta la documentación de referencia de la API de Wear.

Administra el indicador de notificaciones no leídas

Los usuarios a menudo desean tener una indicación clara de que hay notificaciones no leídas. Por lo tanto, se proporciona un indicador de notificaciones no leídas. Este indicador tiene el aspecto de un punto rodeado por un círculo en la parte inferior de la pantalla. Aparece si hay más de una notificación no leída en la transmisión.

indicador de notificaciones no leídas

Figura 2: Indicador de notificaciones no leídas.

Nota: El indicador de notificaciones no leídas no se encuentra habilitado en la versión de producción de Wear 2.8.0. Se recomienda a los desarrolladores que prueben su implementación usando, como alternativa, el último emulador de Wear. Esta característica se mostrará de forma predeterminada a partir de la próxima versión para el consumidor de Wear (versión 2.9.0).

De forma predeterminada, se agregará el indicador a la cara de reloj. Te recomendamos que dejes el indicador disponible para tus usuarios. Sin embargo, si la cara de reloj ya proporciona una indicación de notificaciones no leídas o si la posición del indicador nuevo choca con un elemento de la cara de reloj, puedes rechazar que se muestre el indicador del sistema. Usa uno de los siguientes métodos para desarrollar el estilo de reloj:

proporciona un recuento personalizado de notificaciones no leídas proporciona un recuento de notificaciones no leídas en la barra de estado

Figura 3: Recuento de notificaciones personalizadas o recuento de notificaciones de la barra de estado.

Si decides permitir que se muestre el indicador de notificaciones no leídas en la cara de reloj, puedes personalizar el color del círculo exterior. Llama a WatchFaceStyle.Builder.setAccentColor y especifica el color deseado. De forma predeterminada, el círculo exterior es blanco.

Obtén información sobre la pantalla del dispositivo

El sistema llama al método Engine.onPropertiesChanged() cuando determina las propiedades de la pantalla del dispositivo, por ejemplo, si el dispositivo utiliza el modo de ambiente de pocos bits y si se requiere protección de pantalla.

En el siguiente fragmento de código, se muestra cómo obtener estas propiedades:

Kotlin

    override fun onPropertiesChanged(properties: Bundle?) {
        super.onPropertiesChanged(properties)
        properties?.apply {
            lowBitAmbient = getBoolean(PROPERTY_LOW_BIT_AMBIENT, false)
            burnInProtection = getBoolean(PROPERTY_BURN_IN_PROTECTION, false)
        }
    }
    

Java

    @Override
    public void onPropertiesChanged(Bundle properties) {
        super.onPropertiesChanged(properties);
        lowBitAmbient = properties.getBoolean(PROPERTY_LOW_BIT_AMBIENT, false);
        burnInProtection = properties.getBoolean(PROPERTY_BURN_IN_PROTECTION,
                false);
    }
    

Debes tener en cuenta estas propiedades del dispositivo cuando dibujes la cara de reloj:

  • Para los dispositivos que utilizan el modo ambiente de pocos bits, la pantalla admite menos bits para cada color en el modo ambiente, por lo que debes desactivar el suavizado de contorno y el filtro de mapas de bits cuando el dispositivo pasa al modo ambiente.
  • Para los dispositivos que requieren protección de pantalla, evita el uso de grandes bloques de píxeles blancos en el modo ambiente y no coloques el contenido dentro de los 10 píxeles del borde de la pantalla, ya que el sistema cambia el contenido de manera periódica para evitar la protección de píxeles.

Para obtener más información sobre el modo ambiente de pocos bits y la protección de pantalla, consulta Cómo optimizar las pantallas especiales. Para obtener más información sobre cómo inhabilitar el filtro de mapas de bits, consulta Filtro de mapas de bits.

Responde a los cambios entre modos

Cuando el dispositivo cambia entre los modos ambiente e interactivo, el sistema llama al método Engine.onAmbientModeChanged(). La implementación del servicio debe realizar las modificaciones necesarias para cambiar entre modos y luego llamar al método invalidate() para que el sistema vuelva a dibujar la cara de reloj.

En el siguiente fragmento de código, se muestra cómo implementar este método:

Kotlin

    override fun onAmbientModeChanged(inAmbientMode: Boolean) {

        super.onAmbientModeChanged(inAmbientMode)

        if (lowBitAmbient) {
            !inAmbientMode.also { antiAlias ->
                hourPaint.isAntiAlias = antiAlias
                minutePaint.isAntiAlias = antiAlias
                secondPaint.isAntiAlias = antiAlias
                tickPaint.isAntiAlias = antiAlias
            }
        }
        invalidate()
        updateTimer()
    }
    

Java

    @Override
    public void onAmbientModeChanged(boolean inAmbientMode) {

        super.onAmbientModeChanged(inAmbientMode);

        if (lowBitAmbient) {
            boolean antiAlias = !inAmbientMode;
            hourPaint.setAntiAlias(antiAlias);
            minutePaint.setAntiAlias(antiAlias);
            secondPaint.setAntiAlias(antiAlias);
            tickPaint.setAntiAlias(antiAlias);
        }
        invalidate();
        updateTimer();
    }
    

En este ejemplo, se realizan modificaciones en algunos estilos gráficos y se invalida el lienzo para que el sistema pueda volver a dibujar la cara de reloj.

Dibuja la cara de reloj

Para dibujar una cara de reloj personalizada, el sistema llama al método Engine.onDraw() con una instancia de Canvas y los límites en los que debes dibujar la cara de reloj. Los límites tienen en cuenta las áreas dentro del recuadro, como el "mentón" en la parte inferior de algunos dispositivos redondos. Puedes usar este lienzo para dibujar la cara de reloj directamente de la siguiente manera:

  1. Reemplaza el método onSurfaceChanged() para adaptar tu fondo a fin de que se ajuste al dispositivo cada vez que cambie la vista.

    Kotlin

        override fun onSurfaceChanged(holder: SurfaceHolder, format: Int, width: Int, height: Int) {
            if (backgroundScaledBitmap?.width != width || backgroundScaledBitmap?.height != height) {
                backgroundScaledBitmap = Bitmap.createScaledBitmap(backgroundBitmap,
                        width, height, true /* filter */)
            }
            super.onSurfaceChanged(holder, format, width, height)
        }
        

    Java

        @Override
        public void onSurfaceChanged(
                SurfaceHolder holder, int format, int width, int height) {
            if (backgroundScaledBitmap == null
                    || backgroundScaledBitmap.getWidth() != width
                    || backgroundScaledBitmap.getHeight() != height) {
                backgroundScaledBitmap = Bitmap.createScaledBitmap(backgroundBitmap,
                        width, height, true /* filter */);
            }
            super.onSurfaceChanged(holder, format, width, height);
        }
        
  2. Comprueba si el dispositivo está en modo ambiente o modo interactivo.
  3. Realiza cualquier cálculo gráfico requerido.
  4. Dibuja tu mapa de bits de fondo en el lienzo.
  5. Usa los métodos en la clase Canvas para dibujar la cara de reloj.

En el siguiente fragmento de código, se muestra cómo implementar el método onDraw():

Kotlin

    override fun onDraw(canvas: Canvas, bounds: Rect) {
        val frameStartTimeMs: Long = SystemClock.elapsedRealtime()

        // Drawing code here

        if (shouldTimerBeRunning()) {
            var delayMs: Long = SystemClock.elapsedRealtime() - frameStartTimeMs
            delayMs = if (delayMs > INTERACTIVE_UPDATE_RATE_MS) {
                // This scenario occurs when drawing all of the components takes longer than an actual
                // frame. It may be helpful to log how many times this happens, so you can
                // fix it when it occurs.
                // In general, you don't want to redraw immediately, but on the next
                // appropriate frame (else block below).
                0
            } else {
                // Sets the delay as close as possible to the intended framerate.
                // Note that the recommended interactive update rate is 1 frame per second.
                // However, if you want to include the sweeping hand gesture, set the
                // interactive update rate up to 30 frames per second.
                INTERACTIVE_UPDATE_RATE_MS - delayMs
            }
            updateTimeHandler.sendEmptyMessageDelayed(MSG_CODE_UPDATE_TIME, delayMs)
        }
    }
    

Java

    @Override
    public void onDraw(Canvas canvas, Rect bounds) {
        long frameStartTimeMs = SystemClock.elapsedRealtime();

        // Drawing code here

        if (shouldTimerBeRunning()) {
            long delayMs = SystemClock.elapsedRealtime() - frameStartTimeMs;
            if (delayMs > INTERACTIVE_UPDATE_RATE_MS) {
                // This scenario occurs when drawing all of the components takes longer than an actual
                // frame. It may be helpful to log how many times this happens, so you can
                // fix it when it occurs.
                // In general, you don't want to redraw immediately, but on the next
                // appropriate frame (else block below).
                delayMs = 0;
            } else {
                // Sets the delay as close as possible to the intended framerate.
                // Note that the recommended interactive update rate is 1 frame per second.
                // However, if you want to include the sweeping hand gesture, set the
                // interactive update rate up to 30 frames per second.
                delayMs = INTERACTIVE_UPDATE_RATE_MS - delayMs;
            }
            updateTimeHandler.sendEmptyMessageDelayed(MSG_CODE_UPDATE_TIME, delayMs);
        }
    }
    

Para obtener más información sobre cómo dibujar en una instancia de lienzo, consulta Lienzos y elementos de diseño.

La cara de reloj de ejemplo incluye caras de reloj adicionales que puedes consultar como ejemplos de cómo implementar el método onDraw().