في حياة كل مطور ، هناك لحظة ، عندما ترى فيها حلًا مثيرًا للاهتمام في تطبيق أجنبي ، ترغب في تنفيذه بنفسك. هذا أمر منطقي ويجب أن يكون بسيطًا جدًا. ومن المؤكد أن أشخاصًا مهتمين من "شركة الخير" كتبوا نوعًا من الدليل حول هذا أو قدموا فيديو تدريبيًا حيث تم عرضه على الأصابع كيفية استدعاء بعض الطرق الضرورية لتحقيق النتيجة المرجوة. هذا هو الحال في كثير من الأحيان.
لكن هذا يحدث بطريقة مختلفة تمامًا: ترى تنفيذ شيء ما في كل تطبيق ثاني ، وعندما يتعلق الأمر بتنفيذ نفس الشيء في المنزل - اتضح أنه لا توجد حتى الآن حلول سهلة لهذا ...
حدث هذا لي عندما أصبح من الضروري إضافة رمز مع عداد إلى اللوحة العلوية. لقد فوجئت للغاية عندما اتضح أنه لا يوجد حل بسيط لتطبيق عنصر واجهة المستخدم المألوف والمطلوب هذا. لكن هذا يحدث للأسف. وقررت أن أنتقل إلى معرفة الشبكة العالمية. كانت مسألة وضع رمز مع عداد في شريط الأدوات العلوي ، كما اتضح ، قلقة للغاية. بعد قضاء بعض الوقت على الإنترنت ، وجدت الكثير من الحلول المختلفة. بشكل عام ، جميعهم عمال ولهم الحق في الحياة. علاوة على ذلك ، تظهر نتيجة بحثي بوضوح كيفية التعامل مع حل المهام في Android بطرق مختلفة.
في هذه المقالة سوف أتحدث عن عدة تطبيقات لرمز العداد. هنا 4 أمثلة. إذا كنت تفكر على نطاق أوسع ، فسنتحدث عن أي عنصر مخصص تقريبًا نريد وضعه في شريط الأدوات العلوي. لذلك دعونا نبدأ.
قرار واحد
المفهوم
في كل مرة تحتاج إلى رسم العداد أو تحديثه على الرمز ، تحتاج إلى إنشاء
Drawable
بناءً على ملف الترميز ورسمه على شريط الأدوات كرمز.
التنفيذ
إنشاء ملف ترميزية
badge_with_counter_icon
في
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>
هنا نعلق العداد نفسه على الحافة اليسرى للرمز ونشير إلى مسافة بادئة ثابتة: هذا ضروري حتى عندما يزيد طول نص العداد ، لا يتداخل الرمز الرئيسي بقوة أكبر - هذا قبيح.
في
res/values/dimens
تضاف:
<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>
حجم الرمز وفقًا لدليل
التصميم متعدد الأبعاد.
في
res/values/colors
أضف:
<color name="counter_background_color">@android:color/holo_red_light</color> <color name="counter_text_color">@android:color/white</color>
في
res/values/styles
أضف:
<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>
إنشاء
res/drawable/
في
res/drawable/
:
<?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>
كرمز ، التقط صورتك ، واسمها
icon
وأدخل الموارد.
في
res/menu
بإنشاء ملف
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>
قم بإنشاء فصل يحول الترميز إلى
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); } }
بعد ذلك ، في
Activity
الذي نحتاجه ، أضف:
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(); }
الآن ، إذا كان من الضروري تحديث العداد ، فإننا نسمي طريقة
updateFirstCounter
،
updateFirstCounter
القيمة الفعلية إليه. لقد نشرت هنا زيادة في قيمة العداد عندما أنقر فوق الرمز. مع التطبيقات الأخرى سأفعل نفس الشيء.
من الضروري الانتباه إلى ما يلي: نقوم بتشكيل صورة ، ثم نطعمها إلى عنصر القائمة - يتم إنشاء جميع المسافات البادئة اللازمة تلقائيًا ، ولا نحتاج إلى أخذها في الاعتبار.
القرار الثاني
المفهوم
في هذا التطبيق ،
LayerList
أيقونة استنادًا إلى عنصر متعدد الطبقات الموضح في
LayerList
، حيث نقوم في الوقت المناسب برسم العداد نفسه ، تاركًا الرمز دون تغيير.
التنفيذ
بعد ذلك ، سأضيف الموارد والرمز تدريجيًا لجميع عمليات التنفيذ.
في
res/drawable/
إنشاء
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>
في
res/menu/menu_main.xml
أضف:
<item android:id="@+id/action_counter_2" android:icon="@drawable/ic_layered_counter_icon" android:title="layered icon" app:showAsAction="ifRoom"/>
في
res/values/dimens
تضاف:
<dimen name="counter_text_vertical_padding">2dp</dimen>
قم
CounterDrawable.java
الملف
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);
ستقدم هذه الفئة العداد في الزاوية العلوية اليمنى من الرمز الخاص بنا. أسهل طريقة لرسم خلفية مضادة هي ببساطة رسم مستطيل مستدير الزوايا عن طريق استدعاء
canvas.drawRoundRect
، لكن هذه الطريقة مناسبة لإصدارات API فوق الحادي والعشرين. على الرغم من الإصدارات السابقة من API ، إلا أن ذلك ليس بالأمر الصعب.
بعد ذلك ، في نشاطنا
Activity
أضف:
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); }
أضف الكود إلى
onOptionsItemSelected
. استنادًا إلى رمز التنفيذ الأول ، ستبدو هذه الطريقة كما يلي:
@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); } }
هذا كل شيء ، التنفيذ الثاني جاهز. مثل آخر مرة ، قمت بتعليق تحديث العداد بالنقر فوق الرمز ، ولكن يمكن تهيئته من أي مكان عن طريق استدعاء طريقة
updateSecondCounter
. كما ترون ، نرسم العداد على القماش بأيدينا ، ولكن يمكنك التوصل إلى شيء أكثر إثارة للاهتمام - كل هذا يعتمد على خيالك أو رغبات العميل.
القرار الثالث
المفهوم
بالنسبة لعنصر قائمة ، لا نستخدم صورة ، ولكن عنصرًا مع ترميز تعسفي. ثم نجد مكونات هذا العنصر ونحفظ الروابط إليه.
في هذه الحالة ، نحن مهتمون برموز
ImageView
TextView
، ولكن في الواقع قد يكون الأمر أكثر
TextView
. اربط على الفور معالجة النقر على هذا العنصر. يجب أن يتم ذلك ، حيث أن أسلوب
onOptionsItemSelected
مطلوب للعناصر المخصصة في شريط الأدوات.
التنفيذ
نحن
badge_with_counter.xml
ملف
badge_with_counter.xml
في
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>
في
res/values/dimens
تضاف:
<dimen name="menu_item_size">48dp</dimen>
أضف إلى
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"/>
بعد ذلك ، في نشاطنا
Activity
أضف:
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)); } }
في
onPrepareOptionsMenu
أضف:
initThirdCounter(menu);
الآن ، مع مراعاة التغييرات السابقة ، تبدو هذه الطريقة كما يلي:
@Override public boolean onPrepareOptionsMenu(final Menu menu) {
انتهى! يرجى ملاحظة أنه بالنسبة لعنصرنا ، فقد قمنا بترميز العلامة ، حيث حددنا بشكل مستقل جميع الأحجام والأزرار اللازمة - في هذه الحالة ، لن يقوم النظام بذلك نيابةً عنا.
القرار الرابع
المفهوم
كما هو الحال في الإصدار السابق ، ولكن هنا نقوم بإنشاء العنصر وإضافته مباشرة من التعليمات البرمجية.
التنفيذ
في
Activity
أضف:
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)); } }
في هذا الخيار ، يجب إضافة العنصر الخاص بنا إلى القائمة بالفعل في
onCreateOptionsMenu
بناءً على التغييرات السابقة ، تبدو هذه الطريقة الآن كما يلي:
@Override public boolean onCreateOptionsMenu(Menu menu) { getMenuInflater().inflate(R.menu.menu_main, menu); MenuItem menuItem = menu.findItem(R.id.action_counter_1);
انتهى!
في رأيي ، فإن الحلين الأخيرين هما الأبسط والأكثر أناقة ، والأقصر أيضًا: نختار ببساطة تخطيط العنصر الذي نحتاجه ونضعه في شريط الأدوات ، ونحدث المحتويات كما لو كنت تستخدم طريقة عرض عادية.
يبدو ، لماذا لا يمكنني ببساطة وصف هذا النهج وعدم الخوض فيه؟ هناك سببان لذلك:
- أولاً ، أريد أن أوضح أن مشكلة واحدة يمكن أن يكون لها العديد من الحلول ؛
- ثانياً ، لكل من الخيارات التي تم النظر فيها الحق في الحياة.
تذكر ، لقد كتبت أنه يمكنك التعامل مع هذه الحلول ليس فقط على أنها تنفيذ رمز مع عداد ، ولكن استخدامها في بعض العناصر المخصصة المعقدة والمثيرة للاهتمام لشريط الأدوات ، أي الحلول المقترحة ستكون الأكثر ملاءمة؟ سأعطي مثالا.
من بين جميع الطرق التي تمت مناقشتها ، فإن الأكثر إثارة للجدل هو الأول ، لأنه يحمل النظام بشكل كبير. يمكن تبرير استخدامه في الحالة عندما يكون لدينا مطلب لإخفاء تفاصيل تكوين الرمز ونقل الصورة المشكلة بالفعل إلى شريط الأدوات. ومع ذلك ، يجب أن يوضع في الاعتبار أنه مع التحديث المتكرر للرمز بهذه الطريقة ، يمكننا أن نتسبب في ضربة خطيرة للأداء.
الطريقة الثانية مناسبة لنا عندما تحتاج إلى رسم شيء ما على القماش بنفسك. التنفيذان الثالث والرابع هما الأكثر شمولية للمهام الكلاسيكية: تغيير قيمة حقل النص بدلاً من تكوين صورة منفصلة سيكون حلاً جيدًا.
عندما يصبح من الضروري تنفيذ نوع من الميزات الرسومية المعقدة ، عادة ما أقول لنفسي: "لا يوجد شيء مستحيل - السؤال الوحيد هو كم من الوقت والجهد يحتاجان إلى إنفاقه على التنفيذ".
لديك الآن العديد من الخيارات لإنجاز المهمة ، وكما ترى ، أنت بحاجة إلى القليل من الوقت والجهد لتنفيذ كل خيار.