在每个开发人员的生活中,都有一段时间,当您在外部应用程序中看到有趣的解决方案时,您想自己实现它。 这是合乎逻辑的,应该非常简单。 一定要从“善良的融合”中关心人们,为此写了一些指南或制作了培训视频,该视频在手指上展示了如何调用两种必要的方法来达到预期的效果。 通常是这种情况。
但这是以完全不同的方式发生的:您在第二个应用程序中看到某事物的实现,而在家中实现同一事物的事实-事实证明,仍然没有简单的解决方案...
当需要在顶部面板上添加带有计数器的图标时,这发生在我身上。 事实证明,没有简单的解决方案可实现如此熟悉且要求很高的UI元素,这让我感到非常惊讶。 但是不幸的是,它发生了。 我决定转向全球网络的知识。 事实证明,在上部工具栏中放置带有计数器的图标的问题令很多人担忧。 花了一些时间在Internet上之后,我发现了很多不同的解决方案。 一般来说,他们都是工人,并享有生命权。 而且,我的研究结果清楚地表明了如何以不同的方式解决Android中的任务解决方案。
在本文中,我将讨论计数器图标的几种实现。 这里有4个例子。 如果您考虑的范围更广一些,那么我们将讨论几乎要放置在上方工具栏中的所有自定义元素。 因此,让我们开始吧。
决定一
概念图
每次需要在图标上绘制或更新计数器时,都需要基于标记文件创建
Drawable
并将其作为图标在工具栏上绘制。
实作
在
res/layouts
创建一个
badge_with_counter_icon
标记文件:
<?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/
resource中创建
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
方法,并将当前值传递给该方法。 单击此图标后,我在此处发布了计数器值的增加。 对于其他实现,我将做同样的事情。
有必要注意以下几点:我们形成一个图像,然后将其馈送到菜单项-所有必需的缩进都是自动生成的,我们不需要考虑它们。
第二个决定
概念图
在此实现中,我们基于
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
:
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
简单地绘制一个圆角矩形,但是此方法适用于21st以上的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
计数器感兴趣,但实际上,它可能更自定义。 立即加快单击此元素的过程。 必须这样做,因为不会为工具栏中的自定义元素调用
onOptionsItemSelected
方法。
实作
我们在
res/layouts
创建一个
badge_with_counter.xml
标记文件:
<?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);
做完了!
我认为,最后两个解决方案是最简单,最优雅的解决方案,也是最短的解决方案:我们只需选择所需的元素布局并将其放到工具栏中,然后像使用常规View一样更新内容。
看来,为什么我不干脆不描述这种方法而又不去研究它呢? 这有两个原因:
- 首先,我想表明一个问题可以有多种解决方案;
- 其次,所考虑的每一种选择都有生命权。
记住,我写道,您不仅可以将这些解决方案视为实现带有计数器的图标,而且可以将它们用于工具栏的一些非常复杂且有趣的自定义元素中,所建议的解决方案中哪一种最适合? 我举一个例子。
在所有讨论的方法中,最有争议的是第一种,因为它给系统带来了沉重的负担。 当我们需要隐藏图标形成的细节并将已形成的图像传输到工具栏时,可以合理地使用它。 但是,应该牢记,以这种方式频繁更新图标,可能会对性能造成严重的打击。
当您需要自己在画布上绘制某些东西时,第二种方法适用于我们。 第三和第四种实现是经典任务中最通用的实现:更改文本字段的值而不是形成单独的图像将是一个很好的解决方案。
当有必要实施某种复杂的图形功能时,我通常对自己说:“没有什么不可能的,唯一的问题是在实施上需要花费多少时间和精力”。
现在,您有几个选项可以完成任务,并且可以看到,您只需很少的时间和精力即可实现每个选项。