Na vida de cada desenvolvedor, há um momento em que, quando você vê uma solução interessante em um aplicativo estrangeiro, deseja implementá-lo por conta própria. Isso é lógico e deve ser bastante simples. E certamente pessoas atenciosas da "corporação da bondade" escreveram algum tipo de guia sobre isso ou fizeram um vídeo de treinamento onde foi mostrado nos dedos como chamar alguns dos métodos necessários para alcançar o resultado desejado. Este é frequentemente o caso.
Mas isso acontece de uma maneira completamente diferente: você vê a implementação de algo em cada segundo aplicativo e, quando se trata da implementação do mesmo em casa - verifica-se que ainda não há soluções fáceis para isso ...
Isso aconteceu comigo quando se tornou necessário adicionar um ícone com um contador ao painel superior. Fiquei muito surpreso quando descobriu que não há uma solução simples para implementar um elemento de interface do usuário tão familiar e exigido. Mas acontece, infelizmente. E decidi voltar ao conhecimento da rede mundial. A questão de colocar um ícone com um contador na barra de ferramentas superior, preocupou-se bastante. Depois de passar algum tempo na Internet, encontrei muitas soluções diferentes. Em geral, todos são trabalhadores e têm direito à vida. Além disso, o resultado da minha pesquisa mostra claramente como abordar a solução de tarefas no Android de diferentes maneiras.
Neste artigo, falarei sobre várias implementações do ícone do contador. Aqui estão 4 exemplos. Se você pensar um pouco mais amplamente, falaremos sobre praticamente qualquer elemento personalizado que queremos colocar na barra de ferramentas superior. Então, vamos começar.
Decisão um
Conceito
Cada vez que você precisa desenhar ou atualizar o contador no ícone, é necessário criar
Drawable
base no arquivo de marcação e desenhá-lo na barra de ferramentas como um ícone.
Implementação
Crie um arquivo de marcação
badge_with_counter_icon
em
res/layouts
:
<?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="wrap_content" android:layout_height="@dimen/menu_item_icon_size" > <ImageView android:id="@+id/icon_badge" android:layout_width="@dimen/menu_item_icon_size" android:layout_height="@dimen/menu_item_icon_size" android:scaleType="fitXY" android:src="@drawable/icon" android:layout_alignParentStart="true"/> <TextView android:id="@+id/counter" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignStart="@id/icon_badge" android:layout_alignTop="@+id/icon_badge" android:layout_gravity="center" android:layout_marginStart="@dimen/counter_left_margin" android:background="@drawable/counter_background" android:gravity="center" android:paddingLeft="@dimen/counter_text_horizontal_padding" android:paddingRight="@dimen/counter_text_horizontal_padding" android:text="99" android:textAppearance="@style/CounterText" /> </RelativeLayout>
Aqui, anexamos o contador à borda esquerda do ícone e indicamos um recuo fixo: isso é necessário para que, quando o comprimento do texto aumentar, o ícone principal do contador não se sobreponha mais - seja feio.
Em
res/values/dimens
adicione:
<dimen name="menu_item_icon_size">24dp</dimen> <dimen name="counter_left_margin">14dp</dimen> <dimen name="counter_badge_radius">6dp</dimen> <dimen name="counter_text_size">9sp</dimen> <dimen name="counter_text_horizontal_padding">4dp</dimen>
Tamanho do ícone de acordo com o guia
Design de material .
Em
res/values/colors
adicione:
<color name="counter_background_color">@android:color/holo_red_light</color> <color name="counter_text_color">@android:color/white</color>
Em
res/values/styles
adicione:
<style name="CounterText"> <item name="android:fontFamily">sans-serif</item> <item name="android:textSize">@dimen/counter_text_size</item> <item name="android:textColor">@color/counter_text_color</item> <item name="android:textStyle">normal</item> </style>
Crie
res/drawable/
em
res/drawable/
resource:
<?xml version="1.0" encoding="utf-8"?> <shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="rectangle"> <solid android:color="@color/counter_background_color"/> <corners android:radius="@dimen/counter_badge_radius"/> </shape>
Como ícone, tire uma foto, chame-o de
icon
e coloque recursos.
Em
res/menu
crie o arquivo
menu_main.xml
:
<?xml version="1.0" encoding="utf-8"?> <menu xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto"> <item android:id="@+id/action_counter_1" android:icon="@drawable/icon" android:title="icon" app:showAsAction="ifRoom"/> </menu>
Crie uma classe que converte a marcação em
Drawable
:
LayoutToDrawableConverter.java
package com.example.counters.counters; import android.content.Context; import android.graphics.Bitmap; import android.graphics.drawable.BitmapDrawable; import android.graphics.drawable.Drawable; import android.view.LayoutInflater; import android.view.View; import android.widget.ImageView; import android.widget.TextView; public class LayoutToDrawableConverter { public static Drawable convertToImage(Context context, int count, int drawableId) { LayoutInflater inflater = LayoutInflater.from(context); View view = inflater.inflate(R.layout.badge_with_counter_icon, null); ((ImageView) view.findViewById(R.id.icon_badge)).setImageResource(drawableId); TextView textView = view.findViewById(R.id.counter); if (count == 0) { textView.setVisibility(View.GONE); } else { textView.setText(String.valueOf(count)); } view.measure(View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED), View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED)); view.layout(0, 0, view.getMeasuredWidth(), view.getMeasuredHeight()); view.setDrawingCacheEnabled(true); view.setDrawingCacheQuality(View.DRAWING_CACHE_QUALITY_HIGH); Bitmap bitmap = Bitmap.createBitmap(view.getDrawingCache()); view.setDrawingCacheEnabled(false); return new BitmapDrawable(context.getResources(), bitmap); } }
Em seguida, na
Activity
que precisamos, adicione:
private int mCounterValue1 = 0; @Override public boolean onCreateOptionsMenu(Menu menu) { getMenuInflater().inflate(R.menu.menu_main, menu); MenuItem menuItem = menu.findItem(R.id.action_with_counter_1); menuItem.setIcon(LayoutToDrawableConverter.convertToImage(this, mCounterValue1, R.drawable.icon)); return true; } @Override public boolean onOptionsItemSelected(final MenuItem item) { switch (item.getItemId()) { case R.id.action_counter_1: updateFirstCounter(mCounterValue1 + 1); return true; default: return super.onOptionsItemSelected(item); } } private void updateFirstCounter(int newCounterValue){ mCountrerValue1 = newCounterValue; invalidateOptionsMenu(); }
Agora, se for necessário atualizar o contador, chamamos o método
updateFirstCounter
, passando o valor real para ele. Aqui, publiquei um aumento no valor do contador quando clico no ícone. Com outras implementações, farei o mesmo.
É necessário prestar atenção ao seguinte: formamos uma imagem, que depois alimentamos no item de menu - todos os recuos necessários são gerados automaticamente, não precisamos levar em consideração.
Segunda decisão
Conceito
Nesta implementação,
LayerList
ícone baseado no elemento multicamada descrito em
LayerList
, no qual, no momento certo, desenhamos o próprio contador, deixando o ícone inalterado.
Implementação
A seguir, adicionarei gradualmente recursos e código para todas as implementações.
Em
res/drawable/
create
ic_layered_counter_icon.xml
:
<?xml version="1.0" encoding="utf-8"?> <layer-list xmlns:android="http://schemas.android.com/apk/res/android"> <item android:drawable="@drawable/icon" android:gravity="center" /> <item android:id="@+id/ic_counter" android:drawable="@android:color/transparent" /> </layer-list>
Em
res/menu/menu_main.xml
adicione:
<item android:id="@+id/action_counter_2" android:icon="@drawable/ic_layered_counter_icon" android:title="layered icon" app:showAsAction="ifRoom"/>
Em
res/values/dimens
adicione:
<dimen name="counter_text_vertical_padding">2dp</dimen>
Crie o arquivo
CounterDrawable.java
:
package com.example.counters.counters; import android.content.Context; import android.graphics.Canvas; import android.graphics.ColorFilter; import android.graphics.Paint; import android.graphics.PixelFormat; import android.graphics.Rect; import android.graphics.Typeface; import android.graphics.drawable.Drawable; import android.support.v4.content.ContextCompat; public class CounterDrawable extends Drawable { private Paint mBadgePaint; private Paint mTextPaint; private Rect mTxtRect = new Rect(); private String mCount = ""; private boolean mWillDraw; private Context mContext; public CounterDrawable(Context context) { mContext = context; float mTextSize = context.getResources() .getDimension(R.dimen.counter_text_size); mBadgePaint = new Paint(); mBadgePaint.setColor(ContextCompat.getColor(context.getApplicationContext(), R.color.counter_background_color)); mBadgePaint.setAntiAlias(true); mBadgePaint.setStyle(Paint.Style.FILL); mTextPaint = new Paint(); mTextPaint.setColor(ContextCompat.getColor(context.getApplicationContext(), R.color.counter_text_color)); mTextPaint.setTypeface(Typeface.DEFAULT); mTextPaint.setTextSize(mTextSize); mTextPaint.setAntiAlias(true); mTextPaint.setTextAlign(Paint.Align.CENTER); } @Override public void draw(Canvas canvas) { if (!mWillDraw) { return; } float radius = mContext.getResources() .getDimension(R.dimen.counter_badge_radius); float counterLeftMargin = mContext.getResources() .getDimension(R.dimen.counter_left_margin); float horizontalPadding = mContext.getResources() .getDimension(R.dimen.counter_text_horizontal_padding); float verticalPadding = mContext.getResources() .getDimension(R.dimen.counter_text_vertical_padding); mTextPaint.getTextBounds(mCount, 0, mCount.length(), mTxtRect); float textHeight = mTxtRect.bottom - mTxtRect.top; float textWidth = mTxtRect.right - mTxtRect.left; float badgeWidth = Math.max(textWidth + 2 * horizontalPadding, 2 * radius); float badgeHeight = Math.max(textHeight + 2 * verticalPadding, 2 * radius); canvas.drawCircle(counterLeftMargin + radius, radius, radius, mBadgePaint); canvas.drawCircle(counterLeftMargin + radius, badgeHeight - radius, radius, mBadgePaint); canvas.drawCircle(counterLeftMargin + badgeWidth - radius, badgeHeight - radius, radius, mBadgePaint); canvas.drawCircle(counterLeftMargin + badgeWidth - radius, radius, radius, mBadgePaint); canvas.drawRect(counterLeftMargin + radius, 0, counterLeftMargin + badgeWidth - radius, badgeHeight, mBadgePaint); canvas.drawRect(counterLeftMargin, radius, counterLeftMargin + badgeWidth, badgeHeight - radius, mBadgePaint);
Esta classe renderizará o contador no canto superior direito do nosso ícone. A maneira mais fácil de desenhar um plano de fundo do contador é simplesmente desenhar um retângulo arredondado chamando
canvas.drawRoundRect
, mas esse método é adequado para versões de API acima do dia 21. Embora para versões anteriores da API, isso não seja particularmente difícil.
Em seguida, em nossa
Activity
adicione:
private int mCounterValue2 = 0; private LayerDrawable mIcon2; private void initSecondCounter(Menu menu){ MenuItem menuItem = menu.findItem(R.id.action_counter_2); mIcon2 = (LayerDrawable) menuItem.getIcon(); updateSecondCounter(mCounterValue2); } private void updateSecondCounter(int newCounterValue) { CounterDrawable badge; Drawable reuse = mIcon2.findDrawableByLayerId(R.id.ic_counter); if (reuse != null && reuse instanceof CounterDrawable) { badge = (CounterDrawable) reuse; } else { badge = new CounterDrawable(this); } badge.setCount(String.valueOf(newCounterValue)); mIcon2.mutate(); mIcon2.setDrawableByLayerId(R.id.ic_counter, badge); }
Adicione o código ao
onOptionsItemSelected
. Com base no código da primeira implementação, este método será semelhante a este:
@Override public boolean onOptionsItemSelected(final MenuItem item) { switch (item.getItemId()) { case R.id.action_counter_1: updateFirstCounter(mCounterValue1 + 1); return true; case R.id.action_counter_2: updateSecondCounter(++mCounterValue2); return true; default: return super.onOptionsItemSelected(item); } }
Isso é tudo, a segunda implementação está pronta. Como na última vez, desliguei a atualização do contador clicando no ícone, mas ela pode ser inicializada de qualquer lugar chamando o método
updateSecondCounter
. Como você pode ver, desenhamos o balcão na tela com as mãos, mas você pode criar algo mais interessante - tudo depende da sua imaginação ou dos desejos do cliente.
Decisão três
Conceito
Para um item de menu, usamos não uma imagem, mas um elemento com marcação arbitrária. Em seguida, encontramos os componentes desse elemento e salvamos os links para eles.
Nesse caso, estamos interessados nos ícones
ImageView
e
TextView
contador
TextView
, mas, na realidade, pode ser algo mais personalizado. Aperte imediatamente o processamento de clicar neste elemento. Isso deve ser feito, pois o método
onOptionsItemSelected
não
onOptionsItemSelected
chamado para elementos personalizados na barra de ferramentas.
Implementação
Criamos um arquivo de marcação
badge_with_counter.xml
em
res/layouts
:
<?xml version="1.0" encoding="utf-8"?> <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="wrap_content" android:layout_height="wrap_content"> <RelativeLayout android:layout_width="@dimen/menu_item_size" android:layout_height="@dimen/menu_item_size"> <ImageView android:id="@+id/icon_badge" android:layout_width="@dimen/menu_item_icon_size" android:layout_height="@dimen/menu_item_icon_size" android:layout_centerInParent="true" android:scaleType="fitXY" android:src="@drawable/icon" /> <TextView android:id="@+id/counter" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignStart="@id/icon_badge" android:layout_alignTop="@+id/icon_badge" android:layout_gravity="center" android:layout_marginStart="@dimen/counter_left_margin" android:background="@drawable/counter_background" android:gravity="center" android:paddingLeft="@dimen/counter_text_horizontal_padding" android:paddingRight="@dimen/counter_text_horizontal_padding" android:text="99" android:textAppearance="@style/CounterText" /> </RelativeLayout> </FrameLayout>
Em
res/values/dimens
adicione:
<dimen name="menu_item_size">48dp</dimen>
Adicione a
res/menu/menu_main.xml
:
<item android:id="@+id/action_counter_3" app:actionLayout="@layout/badge_with_counter" android:title="existing action view" app:showAsAction="ifRoom"/>
Em seguida, em nossa
Activity
adicione:
private int mCounterValue3 = 0; private ImageView mIcon3; private TextView mCounterText3; private void initThirdCounter(Menu menu){ MenuItem counterItem = menu.findItem(R.id.action_counter_3); View counter = counterItem.getActionView(); mIcon3 = counter.findViewById(R.id.icon_badge); mCounterText3 = counter.findViewById(R.id.counter); counter.setOnClickListener(v -> onThirdCounterClick()); updateThirdCounter(mCounterValue3); } private void onThirdCounterClick(){ updateThirdCounter(++mCounterValue3); } private void updateThirdCounter(int newCounterValue) { if (mIcon3 == null || mCounterText3 == null) { return; } if (newCounterValue == 0) { mIcon3.setImageResource(R.drawable.icon); mCounterText3.setVisibility(View.GONE); } else { mIcon3.setImageResource(R.drawable.icon); mCounterText3.setVisibility(View.VISIBLE); mCounterText3.setText(String.valueOf(newCounterValue)); } }
No
onPrepareOptionsMenu
adicione:
initThirdCounter(menu);
Agora, levando em consideração as alterações anteriores, este método se parece com o seguinte:
@Override public boolean onPrepareOptionsMenu(final Menu menu) {
Feito! Observe que, para o nosso elemento, fizemos a marcação, na qual indicamos independentemente todos os tamanhos e recuos necessários - nesse caso, o sistema não fará isso por nós.
Decisão quatro
Conceito
O mesmo que na versão anterior, mas aqui criamos e adicionamos nosso elemento diretamente do código.
Implementação
Em
Activity
adicione:
private int mCounterValue4 = 0; private void addFourthCounter(Menu menu, Context context) { View counter = LayoutInflater.from(context) .inflate(R.layout.badge_with_counter, null); counter.setOnClickListener(v -> onFourthCounterClick()); mIcon4 = counter.findViewById(R.id.icon_badge); mCounterText4 = counter.findViewById(R.id.counter); MenuItem counterMenuItem = menu.add(context.getString(R.string.counter)); counterMenuItem.setActionView(counter); counterMenuItem.setShowAsActionFlags(MenuItem.SHOW_AS_ACTION_ALWAYS); updateFourthCounter(mCounterValue4); } private void onFourthCounterClick(){ updateFourthCounter(++mCounterValue4); } private void updateFourthCounter(int newCounterValue) { if (mIcon4 == null || mCounterText4 == null) { return; } if (newCounterValue == 0) { mIcon4.setImageResource(R.drawable.icon); mCounterText4.setVisibility(View.GONE); } else { mIcon4.setImageResource(R.drawable.icon); mCounterText4.setVisibility(View.VISIBLE); mCounterText4.setText(String.valueOf(newCounterValue)); } }
Nesta opção, adicionar nosso item ao menu já deve ser feito no
onCreateOptionsMenu
Com base nas alterações anteriores, esse método agora se parece com o seguinte:
@Override public boolean onCreateOptionsMenu(Menu menu) { getMenuInflater().inflate(R.menu.menu_main, menu); MenuItem menuItem = menu.findItem(R.id.action_counter_1);
Feito!
Na minha opinião, as duas últimas soluções são as mais simples e elegantes, e também as mais curtas: simplesmente selecionamos o layout do elemento necessário e o colocamos na barra de ferramentas, e atualizamos o conteúdo como se estivesse usando uma Visualização regular.
Parece, por que simplesmente não devo descrever essa abordagem e não insistir nela? Há duas razões para isso:
- primeiro, quero mostrar que um problema pode ter várias soluções;
- segundo, cada uma das opções consideradas tem direito à vida.
Lembre-se de que escrevi que você pode tratar essas soluções não apenas como implementar um ícone com um contador, mas usá-las em algum elemento personalizado muito complexo e interessante para uma barra de ferramentas, para qual das soluções propostas será a mais adequada? Eu darei um exemplo
De todos os métodos discutidos, o mais controverso é o primeiro, uma vez que carrega o sistema bastante. Seu uso pode ser justificado no caso de termos a necessidade de ocultar os detalhes da formação do ícone e transferir a imagem já formada para a barra de ferramentas. No entanto, deve-se ter em mente que, com a atualização frequente do ícone dessa maneira, podemos infligir um sério golpe no desempenho.
O segundo método é adequado para nós quando você mesmo precisa desenhar algo na tela. A terceira e quarta implementações são as mais universais para tarefas clássicas: alterar o valor do campo de texto em vez de formar uma imagem separada será uma boa solução.
Quando se torna necessário implementar algum tipo de recurso gráfico complicado, normalmente digo a mim mesmo: “Não há nada impossível - a única questão é quanto tempo e esforço precisam ser gastos na implementação”.
Agora você tem várias opções para realizar a tarefa e, como você pode ver, precisa de muito pouco tempo e esforço para implementar cada opção.