Desenhar mostradores de relógio

Depois de configurar o projeto e adicionar uma classe que implementa o serviço de mostrador do relógio, você pode começar a escrever o código para inicializar e desenhar seu mostrador personalizado.

Esta lição inclui exemplos da amostra WatchFace para mostrar como o sistema usa o serviço de mostrador do relógio. Muitos aspectos das implementações de serviço descritas aqui (como inicialização e detecção de recursos do dispositivo) se aplicam a qualquer mostrador do relógio. Sendo assim, você pode reutilizar parte do código nos próprios mostradores.

Figura 1. Mostradores de relógio analógico e digital na amostra WatchFace.

Inicializar o mostrador do relógio

Quando o sistema carrega o serviço, você deve alocar e inicializar a maioria dos recursos necessários para o mostrador do relógio. Isso inclui carregar recursos de bitmap, criar objetos de timer para executar animações personalizadas, configurar estilos de pintura e realizar outros cálculos. Geralmente, é possível concluir essas operações apenas uma vez e reutilizar os resultados. Essa prática melhora o desempenho do mostrador do relógio e facilita a manutenção do seu código.

Para inicializar o mostrador do relógio, siga estas etapas:

  1. Declare variáveis para um timer personalizado, objetos gráficos e outros elementos.
  2. Inicialize os elementos do mostrador do relógio no método Engine.onCreate().
  3. Inicialize o timer personalizado no método Engine.onVisibilityChanged().

As seções a seguir descrevem essas etapas em detalhes.

Declarar variáveis

Os recursos que você inicializa quando o sistema carrega seu serviço precisam estar acessíveis em pontos diferentes em toda a implementação para que você possa reutilizá-los. Isso é possível por meio da declaração de variáveis de membro para esses recursos na implementação WatchFaceService.Engine.

Declare variáveis para os seguintes elementos:

Objetos gráficos
A maioria dos mostradores de relógio contém pelo menos uma imagem de bitmap usada como plano de fundo do mostrador, conforme descrito em Criar uma estratégia de implementação. Você pode usar outras imagens de bitmap que representam ponteiros do relógio ou outros elementos de design do seu mostrador do relógio.
Timer periódico
O sistema notifica o mostrador do relógio uma vez por minuto quando a hora muda, mas alguns mostradores executam animações em intervalos de tempo personalizados. Nesses casos, é preciso providenciar um timer personalizado que funcione com a frequência necessária para atualizar o mostrador do relógio.
Receptor de alteração de fuso horário
O usuário pode ajustar o fuso horário quando viaja, e o sistema transmite esse evento. A implementação do serviço precisa registrar um broadcast receiver que é notificado quando o fuso horário é alterado e atualizar a hora corretamente.

O snippet a seguir mostra como definir essas variáveis:

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;
                    }
                }
            }
        }
        ...
    }
    

No exemplo acima, o timer personalizado é implementado como uma instância de Handler que envia e processa mensagens atrasadas usando a fila de mensagens do thread. No caso desse mostrador de relógio específico, o timer personalizado é acionado uma vez por segundo. Quando o timer é acionado, o gerenciador chama o método invalidate() e o sistema chama o método onDraw() para redesenhar o mostrador do relógio.

Inicializar elementos do mostrador do relógio

Depois de declarar variáveis de membro para recursos de bitmap, estilos de pintura e outros elementos que você reutiliza toda vez que redesenha o mostrador do relógio, inicialize-as quando o sistema carregar seu serviço. Inicializar esses elementos apenas uma vez e reutilizá-los melhora o desempenho e a duração da bateria.

No método Engine.onCreate(), inicialize os seguintes elementos:

  • Carregue a imagem de plano de fundo.
  • Crie estilos e cores para desenhar objetos gráficos.
  • Aloque um objeto para calcular o tempo.
  • Configure a IU do sistema.

O snippet a seguir mostra como inicializar esses 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();
    }
    

O bitmap de plano de fundo é carregado apenas uma vez quando o sistema inicializa o mostrador do relógio. Os estilos gráficos são instâncias da classe Paint. Use esses estilos para desenhar os elementos do mostrador do relógio dentro do método Engine.onDraw(), conforme descrito em Desenhar o mostrador do relógio.

Inicializar o timer personalizado

Como desenvolvedor de mostradores de relógio, você define com que frequência quer atualizar o mostrador por meio de um timer personalizado que funciona com a frequência necessária enquanto o dispositivo está no modo interativo. Isso permite que você crie animações personalizadas e outros efeitos visuais.

Observação: no modo ambiente, o sistema não chama o timer personalizado corretamente. Para atualizar o mostrador do relógio no modo ambiente, consulte Atualizar o mostrador do relógio no modo ambiente.

Um exemplo de definição de timer da classe AnalogWatchFaceService que é acionado uma vez por segundo pode ser visto em Declarar variáveis. No método Engine.onVisibilityChanged(), inicie o timer personalizado se estas duas condições forem aplicáveis:

  • O mostrador do relógio está visível.
  • O dispositivo está no modo interativo.

A classe AnalogWatchFaceService programa o acionamento seguinte do timer, se necessário, da forma abaixo:

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();
    }
    

Esse timer personalizado é acionado uma vez por segundo, conforme descrito em Declarar variáveis.

No método onVisibilityChanged(), inicie o timer se necessário e registre o receptor para alterações de fuso horário da seguinte maneira:

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();
    }
    

Quando o mostrador do relógio está visível, o método onVisibilityChanged() registra o receptor para alterações de fuso horário. Se o dispositivo estiver no modo interativo, esse método também iniciará o timer personalizado. Quando o mostrador do relógio não está visível, esse método interrompe o timer personalizado e cancela o registro do receptor para alterações de fuso horário. Os métodos registerReceiver() e unregisterReceiver() são implementados da seguinte forma:

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);
    }
    

Atualizar o mostrador do relógio no modo ambiente

No modo ambiente, o sistema chama o método Engine.onTimeTick() a cada minuto. Em geral, é suficiente atualizar o mostrador do relógio uma vez por minuto nesse modo. Para atualizar o mostrador do relógio enquanto estiver no modo interativo, providencie um timer personalizado, conforme descrito em Inicializar o timer personalizado.

No modo ambiente, a maioria das implementações de mostradores de relógio simplesmente invalida o canvas para redesenhar o mostrador do relógio no método Engine.onTimeTick():

Kotlin

    override fun onTimeTick() {
        super.onTimeTick()

        invalidate()
    }
    

Java

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

        invalidate();
    }
    

Configurar a IU do sistema

Os mostradores de relógio não devem interferir nos elementos da IU do sistema, conforme descrito em Acomodar elementos da IU do sistema. Se o mostrador do relógio tiver um plano de fundo claro ou mostrar informações perto da parte inferior da tela, talvez seja necessário configurar o tamanho dos cartões de notificação ou ativar a proteção em segundo plano.

O Wear OS by Google permite que você configure os seguintes aspectos da IU do sistema quando o mostrador do relógio está ativo:

  • Especificar se o sistema desenha a hora no mostrador do relógio
  • Proteger os indicadores do sistema com um plano de fundo sólido em torno deles
  • Especificar o posicionamento dos indicadores do sistema

Para configurar esses aspectos da IU do sistema, crie uma instância de WatchFaceStyle e passe-a para o método Engine.setWatchFaceStyle().

A classe AnalogWatchFaceService configura a IU do sistema da seguinte maneira:

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());
        ...
    }
    

Com o snippet de código acima, a hora do sistema é configurada como oculta, uma vez que esse mostrador do relógio desenha a própria representação de hora.

É possível configurar o estilo da IU do sistema a qualquer momento na implementação do mostrador do relógio. Por exemplo, se o usuário selecionar um plano de fundo branco, você poderá adicionar proteção em segundo plano aos indicadores do sistema.

Para mais detalhes sobre como configurar a IU do sistema, consulte a documentação de referência da API Wear.

Gerenciar o indicador de notificação não lida

Normalmente, o usuário quer ter uma indicação clara de que existem notificações não lidas. Portanto, um indicador de notificação não lida é oferecido. Esse indicador aparece como um ponto circulado na parte inferior da tela. Ele será exibido se houver mais de uma notificação não lida no fluxo.

indicador de notificações não lidas

Figura 2. Indicador de notificação não lida.

Observação: o indicador de notificações não lidas não está ativado na versão de produção do Wear 2.8.0. Os desenvolvedores são aconselhados a testar a implementação usando o emulador do Wear mais recente. Esse recurso será exibido por padrão a partir da próxima versão para consumidor do Wear (versão 2.9.0).

Por padrão, o indicador será adicionado ao mostrador do relógio. É altamente recomendável que você deixe o indicador disponível para seus usuários. No entanto, se seu mostrador do relógio já oferece uma indicação de notificações não lidas ou se a posição do novo indicador entra em conflito com um elemento do mostrador do relógio, você pode desativar a exibição do indicador do sistema. Use um dos seguintes métodos ao criar o estilo do relógio:

fornecer uma contagem personalizada de notificações não lidas fornecer uma contagem de notificações não lidas na barra de status

Figura 3. Contagem de notificações personalizada ou contagem de notificações da barra de status.

Se optar por permitir a exibição do indicador de notificações não lidas no mostrador do relógio, você poderá personalizar a cor do anel externo dele. Chame WatchFaceStyle.Builder.setAccentColor e especifique a color desejada. Por padrão, o anel externo é branco.

Ver informações sobre a tela do dispositivo

O sistema chama o método Engine.onPropertiesChanged() quando determina as propriedades da tela do dispositivo, por exemplo, se o dispositivo usa o modo ambiente de poucos bits e se a tela requer a "Proteção de pixels".

O snippet de código a seguir mostra como acessar essas propriedades:

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);
    }
    

É preciso considerar essas propriedades do dispositivo ao desenhar o mostrador do relógio:

  • Para dispositivos que usam o modo ambiente de poucos bits, a tela aceita menos bits para cada cor no modo ambiente. Portanto, desative a suavização e a filtragem de bitmap quando o dispositivo alternar para o modo ambiente.
  • Para dispositivos que exigem a "Proteção de pixels", evite o uso de grandes blocos de pixels brancos no modo ambiente e não coloque conteúdo dentro de 10 pixels da borda da tela, uma vez que o sistema alterna o conteúdo periodicamente para evitar a queima de pixels.

Para mais informações sobre o modo ambiente de poucos bits e a "Proteção de pixels", consulte Otimizar para telas especiais. Para mais informações sobre como desativar a filtragem de bitmap, consulte Filtragem de bitmap.

Responder a alterações entre os modos

Quando o dispositivo alterna entre os modos ambiente e interativo, o sistema chama o método Engine.onAmbientModeChanged(). A implementação do serviço deve fazer os ajustes necessários para alternar entre os modos e, em seguida, chamar o método invalidate() para o sistema redesenhar o mostrador do relógio.

O snippet a seguir mostra como implementar esse 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();
    }
    

Esse exemplo faz ajustes em alguns estilos gráficos e invalida o canvas para que o sistema possa redesenhar o mostrador do relógio.

Desenhar o mostrador do relógio

Para desenhar um mostrador personalizado, o sistema chama o método Engine.onDraw() com uma instância de Canvas e os limites nos quais você deve desenhar o mostrador do relógio. Os limites levam em conta quaisquer áreas inseridas, como o "queixo" na parte inferior de alguns dispositivos redondos. Você pode usar esse canvas para desenhar o mostrador do relógio diretamente da seguinte maneira:

  1. Modifique o método onSurfaceChanged() para dimensionar seu plano de fundo de acordo com o dispositivo sempre que a visualização for alterada.

    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. Verifique se o dispositivo está no modo ambiente ou no modo interativo.
  3. Realize todos os cálculos gráficos necessários.
  4. Desenhe seu bitmap de plano de fundo no canvas.
  5. Use os métodos na classe Canvas para desenhar o mostrador do relógio.

O snippet a seguir mostra como implementar o 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 mais informações sobre como desenhar em uma instância de Canvas, consulte Canvas e drawables.

O exemplo de WatchFace inclui outros mostradores que você pode consultar como exemplo de como implementar o método onDraw().