
Eu gosto de Dribbble . Existem muitos projetos de design interessantes e inspiradores. Mas se você é um desenvolvedor, geralmente o sentimento de beleza rapidamente se desanima quando você começa a pensar em como implementar esse design legal.
Neste artigo, mostrarei um exemplo desse design e de sua implementação, mas antes disso, vamos falar sobre a solução do problema como um todo.
A maneira mais fácil é usar algum tipo de biblioteca que cubra nossas necessidades. Agora, não me interpretem mal, sou um grande defensor da abordagem "não reinvente a roda". Existem ótimas bibliotecas de código aberto e, quando preciso fazer upload de imagens ou implementar a API REST, o Glide / Picasso e o Retrofit me ajudarão bastante.
Mas quando você precisa implementar um design incomum, essa nem sempre é a melhor opção. Você precisará gastar tempo procurando uma biblioteca boa e suportada que faça algo semelhante. Então você precisa procurar no código para garantir que algo adequado esteja escrito lá. Você precisará dedicar mais tempo para entender as configurações que você pode gerenciar para usar a biblioteca em suas tarefas. E, sejamos honestos, provavelmente a biblioteca não atenderá 100% às suas necessidades e você precisará fazer alguns compromissos com os designers.
Portanto, digo que geralmente é mais fácil e melhor criar seu próprio componente View
. Quando digo "componente nativo do View
", quero dizer a extensão da classe View
, substituindo o método onDraw()
e usando o Paint
and Canvas
para desenhar o componente View
. Isso pode parecer assustador se você não tiver feito isso antes, porque essas classes têm muitos métodos e propriedades, mas você pode se concentrar nos principais:
canvas.drawRect()
- especifique as coordenadas dos cantos e desenhe um retângulo;
canvas.drawRoundRect()
- opcionalmente, especifique o raio e os cantos do retângulo serão arredondados;
canvas.drawPath()
é uma maneira mais complexa, mas também mais poderosa, de criar sua própria forma usando linhas e curvas;
canvas.drawText()
- para desenhar texto na tela (usando o Paint
você pode controlar o tamanho, a cor e outras propriedades);
canvas.drawCircle()
- especifique o ponto central e o raio e obtenha um círculo;
canvas.drawArc()
- especifique o retângulo delimitador, bem como os ângulos de início e de rotação para desenhar o arco;
paint.style
- indica se a forma desenhada será preenchida, circulada ou ambas;
paint.color
- indica a cor (incluindo transparência);
paint.strokeWidth
- controla a largura das formas de traçado;
paint.pathEffect
- permite influenciar a geometria da figura desenhada;
paint.shader
- permite desenhar gradientes.
Lembre-se de que algumas vezes você pode precisar usar outras APIs, mas mesmo tendo dominado esses métodos, é possível desenhar formas muito complexas.
Exemplo prático
Aqui está um design que a Pepper nos oferece:

Há muitas coisas interessantes aqui, mas vamos levar tudo em pequenos pedaços.
Etapa 1. Calcular as posições dos marcadores
private fun calcPositions(markers: List<Marker>) { val max = markers.maxBy { it.value } val min = markers.minBy { it.value } pxPerUnit = chartHeight / (max - min) zeroY = max * pxPerUnit + paddingTop val step = (width - 2 * padding - scalesWidth) / (markers.size - 1) for ((i, marker) in markers.withIndex()) { val x = step * i + paddingLeft val y = zeroY - entry.value * pxPerUnit marker.currentPos.x = x marker.currentPos.y = y } }
Encontramos os valores mínimo e máximo, calculamos a proporção de pixels por unidade, o tamanho do passo horizontal entre os marcadores e as posições X e Y.
Etapa 2. Desenhe um gradiente

Criamos uma forma começando na borda esquerda, desenhando uma linha entre cada marcador e finalizando a forma no ponto inicial. Em seguida, desenhe essa forma usando tinta com um shader de gradiente.
Etapa 3. Desenhe uma grade

Colocamos a tinta para que ela desenhe com uma linha pontilhada. Em seguida, usamos o ciclo especial da linguagem Kotlin, que permite iterar sobre os marcadores nas etapas 7 (o número de dias em uma semana). Para cada marcador, pegamos a coordenada X e desenhamos uma linha pontilhada vertical da parte superior do gráfico até zeroY
.
Etapa 4. Desenhe um gráfico e marcadores

private fun drawLineAndMarkers(canvas: Canvas) { var previousMarker: Marker? = null for (marker in markers) { if (previousMarker != null) {
Examinamos os marcadores, desenhamos um círculo preenchido para cada um deles e uma linha simples do marcador anterior ao atual.
Etapa 5. Desenhe os botões da semana

private fun drawWeeks(canvas: Canvas) { for ((i, week) in weeks.withIndex()) { textPaint.getTextBounds(week, 0, week.length, rect) val x = middle(i) val y = zeroY + rect.height() val halfWidth = rect.width() / 2f val halfHeight = rect.height() / 2f val left = x - halfWidth - padding val top = y - halfHeight - padding val right = x + halfWidth + padding val bottom = y + halfHeight + padding rect.set(left, top, right, bottom) paint.color = bgColor paint.style = FILL canvas.drawRoundRect(rect, radius, radius, paint) paint.color = strokeColor paint.style = STROKE canvas.drawRoundRect(rect, radius, radius, paint) canvas.drawText(week, x, keyY, textPaint) } }
Examinamos as marcas da semana, localizamos a coordenada X do meio da semana e começamos a desenhar o botão em camadas: primeiro desenhamos um plano de fundo com cantos arredondados, depois uma borda e, finalmente, texto. Ajustamos a tinta antes de desenhar cada camada.
Etapa 6. Desenhe os marcadores numéricos à direita

private fun drawGraduations(canvas: Canvas) { val x = markers.last().currentPos.x + padding for (value in graduations) { val y = zeroY - scale * pxPerUnit val formatted = NumberFormat.getIntegerInstance().format(value) canvas.drawText(formatted, x, y, textPaint) } }
A coordenada X é a posição do último marcador mais algum recuo. A coordenada Y é calculada usando a proporção de pixels por unidade. Formaizamos o número em uma sequência (se necessário, adicione um separador de milhares) e desenhamos o texto.
Isso é tudo, agora o nosso onDraw()
ficará assim:
override fun onDraw(canvas: Canvas) { drawGradient(canvas) drawGuidelines(canvas) drawLineAndMarkers(canvas) drawWeeks(canvas) drawGraduations(canvas) }
E combinar as camadas nos dará o resultado desejado:

Sumário
- Não tenha medo de criar seus próprios componentes do
View
(se necessário). - Aprenda as APIs básicas do
Canvas
e do Paint
. - Divida seu design em pequenas camadas e desenhe cada uma de forma independente.
Quanto ao último ponto, para mim, essa é uma das melhores lições de programação em geral: quando confrontada com uma tarefa grande e complexa, divida-a em tarefas menores e mais simples.