fundo
Antes de sair do trabalho naquele dia, o chefe encontrou Xiaozhuang: "Há uma página a ser otimizada, pequena demanda, você acompanha."
Xiaozhuang: "Bom chefe!" Ele olhou para o tempo, inconscientemente tirou o protótipo e viu isso Uma página:
Depois de pensar um pouco, Xiao Zhuang habilmente abriu um determinado mecanismo de busca, mas não encontrou a roda certa . Xiao Zhuang sabia que o primeiro passo no desenvolvimento de software deve ser conduzir primeiro a análise de requisitos e design, em vez de arregaçar as mangas. Então ele decidiu analisar as funções e organizar ideias.
Aviso: este artigo é muito prolixo e não há produtos secos (fear.jpg)
análise
Análise funcional
Função geral da página:
- Esta página é uma lista que mostra um determinado processo , e cada item da lista tem um status diferente (concluído, em andamento, não iniciado)
- Em um lado da lista, há uma visualização semelhante à linha do tempo. De acordo com o status de cada item, pontos e linhas verticais de cores diferentes são exibidos
Análise detalhada
Para a visualização da linha do tempo de um dos itens, quais são os detalhes?
- Em primeiro lugar, descobri que esta visualização da linha do tempo é composta por duas partes principais, a saber: círculo e linha
- Então podemos notar naturalmente que na linha do tempo de um item, duas cores aparecem novamente: a linha acima do círculo (doravante referida como a linha superior) é verde, e o próprio círculo e a linha abaixo do círculo (doravante referida como a linha inferior) são novamente É vermelho
- Em outras palavras, esta visão não só precisa saber sua própria cor, mas também a cor do item anterior
- Em outras palavras, o desenho desta vista deve ser dividido em três partes, a saber: linha superior, círculo e linha inferior
- Este é um item normal do meio. No entanto, para o primeiro e o último item, eles não estão online e offline, respectivamente
Ideia de plano
Vários pensamentos passaram rapidamente pela mente de Xiao Zhuang:
- A primeira ideia é definir a exibição de cor e linha no adaptador de acordo com o estado dos dados.
- Mas você ainda precisa de um designer para descobrir um círculo e uma linha tão simples? Não parece que ele é muito bom nisso? Isso é necessário
Drawable
? No entanto, será difícil alterar a cor no futuro e vários documentos devem ser escritos. Então essa ideia foi passada rapidamente
- Mas você ainda precisa de um designer para descobrir um círculo e uma linha tão simples? Não parece que ele é muito bom nisso? Isso é necessário
- A segunda ideia é usar uma visualização personalizada, desenhar círculos e linhas em cada item e, em seguida, definir a cor com atributos personalizados.
- Ele imediatamente escreveu uma demonstração e a experimentou.O resultado foi que sua visualização personalizada não era boa para aprender habilidades e encontrou um problema difícil [Nota], então ele desistiu de chorar.
- Talvez esteja destinado a abrir uma porta: amigos, talvez já tenham ouvido falar
RecyclerView.ItemDecoration
?
Nota: 2.000 anos depois, descobri que não conseguia reproduzir o problema de forma alguma, talvez seja o destino
Introdução ao RecyclerView.ItemDecoration
Este é um artefato poderoso, usado para adicionar divisores a listas é apenas sua habilidade mais comum. Aqui está uma breve introdução, não o conteúdo principal deste artigo. Porque pode ter muitos efeitos, não consigo aprender (ಥ_ಥ)
Para implementar um personalizado ItemDecoration
, você precisa herdá-lo e reescrever os dois métodos a seguir, conforme necessário:
onDraw
: Usado para conteúdo de desenho específico- O método possui um parâmetro
parent: RecyclerView
, a própria lista, então podemos pegar o conteúdo de cada subitem daqui - Deve-se notar que a dimensão do desenho neste método é a lista inteira , então precisamos percorrer a lista, calcular a posição e desenhar para cada filho
- O método possui um parâmetro
getItemOffsets
: Usado para controlar o deslocamento ao redor do item, o conteúdo desenhado no onDraw será desenhado nesses espaços- No entanto, a dimensão do desenho deste método é para cada itemView, então as margens superior, inferior, esquerda e direita de cada item são definidas
Comece a codificar
Xiao Zhuang agora tem ideias básicas e reservas de conhecimento e abre o IDE para começar a codificar. Porém, o desenvolvimento de software é um processo iterativo, mesmo para um requisito tão pequeno, ele pretende começar com uma versão simples.
Primeira edição
Na primeira versão, Xiaozhuang pretende desenhar apenas círculos e linhas sem estado ou cor. O objetivo principal é verificar se sua ideia é viável. A implementação específica precisa fazer o seguinte:
- Prepare-se para definir dois atributos importantes, que participarão do cálculo da posição e do conteúdo do desenho
radius
: Usado para determinar o raio do círculooffset
: Usado para indicaritem
a distância do ponto ao topo
- E
getItemOffsets
deixe espaço para desenhar toda a linhaitem
do tempo na margem esquerda - O conteúdo de trabalho mais importante é que calculamos e desenhamos círculos e linhas (veja o código para cálculos específicos)
class FirstVerTimeline : RecyclerView.ItemDecoration() {
private val paint = Paint(Paint.ANTI_ALIAS_FLAG)
var radius = 8f
var offset = 15
override fun onDraw(c: Canvas, parent: RecyclerView, state: RecyclerView.State) {
super.onDraw(c, parent, state)
val count = parent.childCount
for (i in 0 until count) {
// 获取当前的itemView
val itemView = parent.getChildAt(i)
// 整个轴线的x坐标都是相同的
val xPosition = radius
// 画上线。第一个item不画
if (i != 0) {
c.drawLine(xPosition, itemView.top.toFloat(),
xPosition, itemView.top.toFloat() + offset, paint)
}
// 画下线。最后一个item不画
if (i != count - 1) {
c.drawLine(xPosition, itemView.top + radius * 2 + offset,
xPosition, itemView.bottom.toFloat(),paint)
}
// 画圆
c.drawCircle(xPosition, itemView.top + offset + radius, radius, paint)
}
}
override fun getItemOffsets(outRect: Rect, view: View, : RecyclerView, state: RecyclerView.State) {
super.getItemOffsets(outRect, view, parent, state)
// 设置item在左边的偏移量
outRect.left = radius.toInt() * 2
}
}
Agora podemos definir uma fonte de dados virtual Record
e ItemDecoration
aplicar isso a um RecyclerView
efeito Shangkang:
rv_timeline1.adapter = RecordAdapter(ArrayList<Record>())// 省略构造假数据
rv_timeline1.addItemDecoration(FirstVerTimeline())
Começou a tomar forma! É que há um pouco de compressão entre a linha do tempo e o texto. Só precisamos adicionar um preenchimento adequado, alterar os dados de teste e parecerá real!
Para ir da Figura 1 à Figura 2, precisamos fazer:
- Definição
paddingLeft
epaddingRight
atributos, usados para indicar o preenchimento esquerdo e direito do eixo- Modificado
getItemOffsets
paraoutRect.left = paddingLeft + paddingRight + radius.toInt() * 2
deixar a posição de deslocamento - O
xPosition
valor inicial modificado éradius + paddingLeft
para mudar a coordenada x do eixo
- Modificado
Agora que a primeira versão está completa, quais novos recursos a segunda versão terá? ↓↓↓
segunda edição
Xiao Zhuang pretende implementar cores diferentes do estado na segunda edição. Para realizar essa demanda, ele caiu em profunda contemplação:
- É definitivamente impossível acoplar a implementação da IU da cor na classe de dados, então uma maneira de obter a cor do estado é necessária
- Devido a desenhar uma
item
necessidade de conhecer em umaitem
cor, simplesmente direcionar toda a lista de fontes de dadosdata
aferentesItemDecoration
boas - Combinando os dois pontos acima, podemos definir um atributo de um tipo de
var color: (item: T) -> Int
função.Ao implementar este atributo, o usuário pode definir a cor desejada através do estado dos dados.
O tipo de função é uma das características do Kotlin (ou programação funcional). Se for Java, você pode considerar o uso do modo de modelo, ou seja, definir um método abstrato para as subclasses reescrever
class SecondVerTimeline<T> : RecyclerView.ItemDecoration() {
// 其他属性...
var data: List<T> = ArrayList() //-->这里有更新,定义了数据源
var color: (item: T) -> Int = { _ -> Color.GRAY } //-->这里有更新,通过这个属性设置颜色选择策略
override fun onDraw(c: Canvas, parent: RecyclerView, state: RecyclerView.State) {
super.onDraw(c, parent, state)
val count = parent.childCount
for (i in 0 until count) {
// ...
val adapterPosition = parent.getChildAdapterPosition(itemView) //-->这里有更新,获取当前项的真正位置
val item = data[adapterPosition] //-->这里有更新,获取当前项的数据源
// 画上线。第一个item不画
if (adapterPosition != 0) {
paint.color = color(data[adapterPosition - 1]) //-->这里有更新,设置上线的颜色
c.drawLine(...)
}
paint.color = color(item) //-->这里有更新,设置圆和下线的颜色
// 画下线。最后一个item不画
if (adapterPosition != data.size - 1) {//-->这里有更新,改用数据源的大小判断是否为最后一个item
c.drawLine(...)
}
// 画圆...
}
}
// getItemOffsets...
}
Pontos que podem precisar de atenção no código:
- Antes de desenhar a linha, você precisa
data数据源
pegá-la上一个item
e usá-la paracolor属性
obter a cor correspondente ao seu estado - A mesma
这一个item
cor que precisa ser alterada antes de desenhar o círculo e a linha inferior - O
parent.childCount
número de subitens obtidos refere-se à parte visível da tela,parent.getChildAdapterPosition
e a posição real do item na lista deve ser obtida para determinar se desenha o offline. Caso contrário, haverá uma cena embaraçosa de [o último item visível na tela atual não é o último item real, mas não está offline, mas depois de deslizar para baixo, está offline novamente] - Observe que
item
o método usado para determinar se é o último neste momentocount - 1
mudou de paradata.size - 1
, usando o tamanho da fonte de dados para determinar a proporção écount
mais preciso (o motivo é o mesmo que o anterior)
Existem também algumas mudanças ao usar:
- O
data
conjunto para oItemDecoration
color
Definir estratégia de cores por meio de atributos
val secondVerTimeline = SecondVerTimeline<Record>()
secondVerTimeline.data = records
secondVerTimeline.color = { item ->
when (item.status) {
1 -> color1
2 -> color2
...
}
}
rv_timeline2.addItemDecoration(secondVerTimeline)
Então você pode correr para ver o efeito:
Nossa, gansa, então você percebeu a função de mudar as cores de acordo com o estado! As funções da segunda edição também foram implementadas com sucesso!
Posfácio
Mais tarde, Xiao Zhuang fez modificações com base na IU e rapidamente completou esse requisito ~ Mas Xiao Zhuang é um programador persistente e começou a pensar na escalabilidade e versatilidade desse código. Não importa se você não quiser, apenas pense em descobrir que não há pato nenhum! E se o produto quiser transformar pontos em imagens? Ou o produto deseja voar mais livremente com o vento?
No entanto, nenhum design pode ser feito de uma vez por todas. A única constante no trabalho de software é a mudança. Ao mesmo tempo, não devemos projetar em excesso em resposta às chamadas "mudanças que podem ocorrer no futuro".