Ikon dengan penghitung di bilah alat atas: contoh berbagai pendekatan untuk satu tugas


Dalam kehidupan setiap pengembang, ada saat ketika, ketika Anda melihat solusi yang menarik dalam aplikasi asing, Anda ingin mengimplementasikannya dalam aplikasi Anda sendiri. Ini logis dan harus cukup sederhana. Dan tentunya orang-orang yang peduli dari “korporasi kebaikan” menulis semacam panduan tentang hal ini atau membuat video pelatihan, yang memperlihatkan dengan jari bagaimana memanggil beberapa metode yang diperlukan untuk mencapai hasil yang diinginkan. Ini sering terjadi.

Tapi itu terjadi dengan cara yang sama sekali berbeda: Anda melihat implementasi sesuatu di setiap aplikasi kedua, dan ketika menyangkut implementasi hal yang sama di rumah - ternyata masih belum ada solusi mudah untuk ini ...

Ini terjadi pada saya ketika menjadi perlu untuk menambahkan ikon dengan penghitung ke panel atas. Saya sangat terkejut ketika ternyata tidak ada solusi sederhana untuk mengimplementasikan elemen UI yang akrab dan dituntut seperti itu. Tetapi itu terjadi, sayangnya. Dan saya memutuskan untuk beralih ke pengetahuan tentang jaringan di seluruh dunia. Masalah menempatkan ikon dengan penghitung di bilah alat atas, ternyata, cukup khawatir. Setelah menghabiskan waktu di Internet, saya menemukan banyak solusi berbeda. Secara umum, mereka semua adalah pekerja dan memiliki hak untuk hidup. Selain itu, hasil penelitian saya dengan jelas menunjukkan cara mendekati solusi tugas di Android dengan berbagai cara.

Pada artikel ini saya akan berbicara tentang beberapa implementasi ikon penghitung. Berikut adalah 4 contoh. Jika Anda berpikir sedikit lebih luas, maka kita akan berbicara tentang hampir semua elemen khusus yang ingin kita tempatkan di bilah alat atas. Jadi mari kita mulai.

Keputusan satu


Konsep


Setiap kali Anda perlu menggambar atau memperbarui penghitung pada ikon, Anda harus membuat Drawable berdasarkan file markup dan menggambarnya di bilah alat sebagai ikon.

Implementasi


Buat file markup badge_with_counter_icon di 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> 

Di sini kita melampirkan penghitung itu sendiri ke tepi kiri ikon dan menunjukkan indentasi tetap: ini diperlukan agar ketika panjang teks penghitung meningkat, ikon utama tidak tumpang tindih lebih kuat - ini jelek.

Dalam res/values/dimens tambahkan:

 <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> 

Ukuran ikon sesuai dengan panduan Desain Bahan .

Dalam res/values/colors tambahkan:

 <color name="counter_background_color">@android:color/holo_red_light</color> <color name="counter_text_color">@android:color/white</color> 

Dalam res/values/styles tambahkan:

 <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> 

Buat res/drawable/ di 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> 

Sebagai ikon, ambil gambar Anda, sebut icon itu dan masukkan sumber daya.

Di res/menu buat file 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> 

Buat kelas yang mengonversi markup ke 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); } } 

Selanjutnya, dalam Activity kita butuhkan, tambahkan:

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

Sekarang, jika perlu memperbarui penghitung, kami memanggil metode updateFirstCounter , meneruskan nilai aktualnya. Di sini saya memposting peningkatan nilai penghitung ketika saya mengklik ikon. Dengan implementasi lain saya akan melakukan hal yang sama.

Kita perlu memperhatikan hal-hal berikut: kita membentuk gambar, yang kemudian kita masukkan ke item menu - semua indentasi yang diperlukan dihasilkan secara otomatis, kita tidak perlu memperhitungkannya.

Keputusan kedua


Konsep


Dalam implementasi ini, kami LayerList ikon berdasarkan elemen multilayer yang dijelaskan dalam LayerList , di mana pada saat yang tepat kami menggambar penghitung itu sendiri, meninggalkan ikon tidak berubah.

Implementasi


Selanjutnya, saya akan secara bertahap menambahkan sumber daya dan kode untuk semua implementasi.

Dalam res/drawable/ ic_layered_counter_icon.xml 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> 

Dalam res/menu/menu_main.xml tambahkan:

 <item android:id="@+id/action_counter_2" android:icon="@drawable/ic_layered_counter_icon" android:title="layered icon" app:showAsAction="ifRoom"/> 

Dalam res/values/dimens tambahkan:

 <dimen name="counter_text_vertical_padding">2dp</dimen> 

Buat file 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); // for API 21 and more: //canvas.drawRoundRect(counterLeftMargin, 0, counterLeftMargin + badgeWidth, badgeHeight, radius, radius, mBadgePaint); canvas.drawText(mCount, counterLeftMargin + badgeWidth / 2, verticalPadding + textHeight, mTextPaint); } public void setCount(String count) { mCount = count; mWillDraw = !count.equalsIgnoreCase("0"); invalidateSelf(); } @Override public void setAlpha(int alpha) { // do nothing } @Override public void setColorFilter(ColorFilter cf) { // do nothing } @Override public int getOpacity() { return PixelFormat.UNKNOWN; } } 

Kelas ini akan membuat penghitung di sudut kanan atas ikon kita. Cara termudah untuk menggambar latar belakang penghitung adalah dengan hanya menggambar persegi panjang bulat dengan memanggil canvas.drawRoundRect , tetapi metode ini cocok untuk versi API di atas tanggal 21. Meskipun untuk versi API sebelumnya, ini tidak terlalu sulit.

Selanjutnya, di Activity kami Activity tambahkan:

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

Tambahkan kode ke onOptionsItemSelected . Berdasarkan kode untuk implementasi pertama, metode ini akan terlihat seperti ini:

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

Itu saja, implementasi kedua sudah siap. Seperti terakhir kali, saya menutup pembaruan counter dengan mengklik ikon, tetapi dapat diinisialisasi dari mana saja dengan memanggil metode updateSecondCounter . Seperti yang Anda lihat, kami menggambar konter di atas kanvas dengan tangan kami, tetapi Anda dapat membuat sesuatu yang lebih menarik - semuanya tergantung pada imajinasi Anda atau pada keinginan pelanggan.

Keputusan tiga


Konsep


Untuk item menu, kami tidak menggunakan gambar, tetapi elemen dengan markup sewenang-wenang. Lalu kami menemukan komponen elemen ini dan menyimpan tautan ke sana.

Dalam hal ini, kami tertarik pada ikon ImageView dan penghitung TextView , tetapi pada kenyataannya itu mungkin sesuatu yang lebih khusus. Segera kencangkan pemrosesan klik pada elemen ini. Ini harus dilakukan, karena metode onOptionsItemSelected tidak dipanggil untuk elemen kustom di toolbar.

Implementasi


Kami membuat file markup badge_with_counter.xml di 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> 

Dalam res/values/dimens tambahkan:

 <dimen name="menu_item_size">48dp</dimen> 

Tambahkan ke 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"/> 

Selanjutnya, di Activity kami Activity tambahkan:

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

Di onPrepareOptionsMenu tambahkan:

 initThirdCounter(menu); 

Sekarang, dengan mempertimbangkan perubahan sebelumnya, metode ini terlihat seperti ini:

 @Override public boolean onPrepareOptionsMenu(final Menu menu) { // the second counter initSecondCounter(menu); // the third counter initThirdCounter(menu); return super.onPrepareOptionsMenu(menu); } 

Selesai! Harap perhatikan bahwa untuk elemen kami, kami mengambil markup, di mana kami secara independen menunjukkan semua ukuran dan inden yang diperlukan - dalam hal ini, sistem tidak akan melakukan ini untuk kami.

Keputusan empat


Konsep


Sama seperti di versi sebelumnya, tetapi di sini kita membuat dan menambahkan elemen kita langsung dari kode.

Implementasi


Dalam Activity tambahkan:

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

Dalam opsi ini, menambahkan item kami ke menu perlu dilakukan di onCreateOptionsMenu

Berdasarkan perubahan sebelumnya, metode ini sekarang terlihat seperti ini:

 @Override public boolean onCreateOptionsMenu(Menu menu) { getMenuInflater().inflate(R.menu.menu_main, menu); MenuItem menuItem = menu.findItem(R.id.action_counter_1); // the first counter menuItem.setIcon(LayoutToDrawableConverter.convertToImage(this, mCounterValue1, R.drawable.icon)); // the third counter addFourthCounter(menu, this); return true; } 

Selesai!

Menurut pendapat saya, dua solusi terakhir adalah yang paling sederhana dan paling elegan, dan juga yang terpendek: kita cukup memilih tata letak elemen yang kita butuhkan dan memasukkannya ke dalam bilah alat, dan memperbarui konten seolah-olah menggunakan Tampilan biasa.

Kelihatannya, mengapa saya tidak menggambarkan pendekatan ini dan tidak memikirkannya? Ada dua alasan untuk ini:

  • pertama, saya ingin menunjukkan bahwa satu masalah dapat memiliki beberapa solusi;
  • kedua, setiap opsi yang dipertimbangkan memiliki hak untuk hidup.

Ingat, saya menulis bahwa Anda dapat memperlakukan solusi ini tidak hanya sebagai penerapan ikon dengan penghitung, tetapi menggunakannya dalam beberapa elemen khusus yang sangat kompleks dan menarik untuk bilah alat, yang salah satu solusi yang diusulkan akan menjadi yang paling cocok? Saya akan memberi contoh.

Dari semua metode yang dibahas, yang paling kontroversial adalah yang pertama, karena memuat sistem dengan cukup berat. Penggunaannya dapat dibenarkan jika kami memiliki persyaratan untuk menyembunyikan detail pembentukan ikon dan mentransfer gambar yang sudah terbentuk ke bilah alat. Namun, harus diingat bahwa dengan memperbarui ikon secara berkala dengan cara ini kita dapat memberikan pukulan serius pada kinerja.

Metode kedua cocok untuk kami ketika Anda perlu menggambar sesuatu di kanvas sendiri. Implementasi ketiga dan keempat adalah yang paling universal untuk tugas-tugas klasik: mengubah nilai bidang teks daripada membentuk gambar yang terpisah akan menjadi solusi yang baik.

Ketika menjadi perlu untuk mengimplementasikan beberapa fitur grafis yang rumit, saya biasanya berkata kepada diri saya sendiri: "Tidak ada yang mustahil - satu-satunya pertanyaan adalah berapa banyak waktu dan upaya yang perlu dihabiskan untuk implementasi"

Sekarang Anda memiliki beberapa opsi untuk mencapai tugas dan, seperti yang Anda lihat, Anda membutuhkan sedikit waktu dan upaya untuk mengimplementasikan setiap opsi.

Source: https://habr.com/ru/post/id420459/


All Articles