如何在图片中添加一个动态文本,全部都是可绘制的,就像Google日历应用中的“今天”操作项一样?

背景

Google日历应用有一个根据当天(“今天”)动态更改的操作项目:

在这里输入图像说明

我被要求做一个非常相似的事情,但在文字周围有一个稍微不同的图像。

问题

通过创建一个具有文本和图像的Drawable(基于这里 ),我成功地实现了它的工作。

不过,我觉得我做得不够好:

  1. 文字字体在不同设备上可能会有所不同,因此可能不适合我写的内容。
  2. 不知道是由于VectorDrawable还是文本,但我认为文字看起来不那么居中。 似乎在左边。 如果我使用两位数字,情况尤其如此:

在这里输入图像说明

  1. 为了垂直居中,我不认为我做了正确的计算。 我在那里尝试了更多合乎逻辑的事情,但是他们没有集中精力。

我试过了

下面是完整的代码(也可以在这里find一个项目):

TextDrawable.java

public class TextDrawable extends Drawable { private static final int DEFAULT_COLOR = Color.WHITE; private static final int DRAWABLE_SIZE = 24; private static final int DEFAULT_TEXT_SIZE = 8; private Paint mPaint; private CharSequence mText; private final int mIntrinstSize; private final Drawable mDrawable; public TextDrawable(Context context, CharSequence text) { mText = text; mPaint = new Paint(Paint.ANTI_ALIAS_FLAG); mPaint.setColor(DEFAULT_COLOR); mPaint.setTextAlign(Align.CENTER); float textSize = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, DEFAULT_TEXT_SIZE, context.getResources().getDisplayMetrics()); mPaint.setTextSize(textSize); mIntrinstSize = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, DRAWABLE_SIZE, context.getResources().getDisplayMetrics()); mDrawable = AppCompatResources.getDrawable(context, R.drawable.ic_backtodate); mDrawable.setBounds(0, 0, mIntrinstSize, mIntrinstSize); } @Override public void draw(Canvas canvas) { Rect bounds = getBounds(); mDrawable.draw(canvas); canvas.drawText(mText, 0, mText.length(), bounds.centerX(), bounds.centerY() + mPaint.getFontMetricsInt(null) / 3, mPaint); // this seems very wrong } @Override public int getOpacity() { return mPaint.getAlpha(); } @Override public int getIntrinsicWidth() { return mIntrinstSize; } @Override public int getIntrinsicHeight() { return mIntrinstSize; } @Override public void setAlpha(int alpha) { mPaint.setAlpha(alpha); } @Override public void setColorFilter(ColorFilter filter) { mPaint.setColorFilter(filter); } } 

MainActivity.kt

 class MainActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) val drawable = TextDrawable(this, "1") imageView.setImageDrawable(drawable) } } 

ic_backtodate.xml

    

问题

  1. 我怎样才能克服不同的字体问题? 我已经在全球范围内使用“Lato”字体(不是在示例应用程序中,而是在真实应用程序中,使用支持库的“下载的字体” API,但将它们内置到应用程序中),但是我不认为Paint Object可以使用它,对不对?

  2. 我怎样才能把文本居中?

  3. 我已经通过视图层次结构工具查看了Google日历如何工作。 对我来说,他们似乎只是使用TextView。 他们是如何做到的呢? 也许使用9补丁? 但是它对于工具栏项目是否正常工作?


编辑:

就目前而言,由于时间紧迫,我无法使用可绘制的解决方案。 如果知道如何做好,还是很好的。

我目前的解决方案不涉及它。 我只是使用模仿正常操作项目的特殊视图。 这并不完美(不完全模仿一个真实的行动项目),但现在已经足够了。 因为这不完美,我在这里写了一个新的主题。


编辑:因为这实际上可以工作,并仍然作为一个正常的行动项目,我决定再试一次。

我已经设法很好地将文本居中,但字体现在是问题。 看起来,如果操作系统使用自己的字体,即使我已经将“Lato”设置为应用程序的一个,但是它并没有用于我制作的可绘制对象:

在这里输入图像说明

我认为这是我需要解决的最后一个问题。

代码如下:

styles.xml

  @font/lato @font/lato 

MainActivity.kt

 class MainActivity : AppCompatActivity() { lateinit var textDrawable: TextDrawable override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) textDrawable = TextDrawable(this, "1") setSupportActionBar(toolbar) val handler = Handler() val runnable = object : Runnable { var i = 1 override fun run() { if (isFinishing||isDestroyed) return textDrawable.text = (i + 1).toString() i = (i + 1) % 31 handler.postDelayed(this, 1000) } } runnable.run() } override fun onCreateOptionsMenu(menu: Menu): Boolean { menu.add("goToToday").setIcon(textDrawable).setShowAsAction(MenuItem.SHOW_AS_ACTION_ALWAYS) menu.add("asd").setIcon(R.drawable.abc_ic_menu_copy_mtrl_am_alpha).setShowAsAction(MenuItem.SHOW_AS_ACTION_ALWAYS) return super.onCreateOptionsMenu(menu) } } 

TextDrawable.kt

 class TextDrawable(context: Context, text: CharSequence) : Drawable() { companion object { private val DEFAULT_COLOR = Color.WHITE private val DEFAULT_TEXT_SIZE = 12 } var text: CharSequence = text set (value) { field = value invalidateSelf() } private val mPaint: TextPaint = TextPaint(Paint.ANTI_ALIAS_FLAG) private val mDrawable: Drawable? init { mPaint.color = DEFAULT_COLOR mPaint.textAlign = Align.CENTER val textSize = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, DEFAULT_TEXT_SIZE.toFloat(), context.resources.displayMetrics) mPaint.textSize = textSize mDrawable = AppCompatResources.getDrawable(context, R.drawable.ic_backtodate) mDrawable!!.setBounds(0, 0, mDrawable.intrinsicWidth, mDrawable.intrinsicHeight) } override fun draw(canvas: Canvas) { val bounds = bounds mDrawable!!.draw(canvas) canvas.drawText(text, 0, text.length, bounds.centerX().toFloat(), (bounds.centerY() + mPaint.getFontMetricsInt(null) / 3).toFloat(), mPaint) // this seems very wrong, but seems to work fine } override fun getOpacity(): Int = mPaint.alpha override fun getIntrinsicWidth(): Int = mDrawable!!.intrinsicWidth override fun getIntrinsicHeight(): Int = mDrawable!!.intrinsicHeight override fun setAlpha(alpha: Int) { mPaint.alpha = alpha invalidateSelf() } override fun setColorFilter(filter: ColorFilter?) { mPaint.colorFilter = filter invalidateSelf() } } 

编辑:

我想我已经find了如何使用文字的字体,

 mPaint.typeface=TypefaceCompat.createFromResourcesFamilyXml(...) 

不知道如何填写参数。 仍在调查…

好的,find了关于如何为我所创建的Drawable类的TextPaint使用相同字体的答案:

 mPaint.typeface = ResourcesCompat.getFont(context, R.font.lato) 

结果:

在这里输入图像说明

以下是这个类的完整实现:

 class TextDrawable(context: Context, text: CharSequence) : Drawable() { companion object { private val DEFAULT_COLOR = Color.WHITE private val DEFAULT_TEXT_SIZE_IN_DP = 12 } private val mTextBounds = Rect() private val mPaint: TextPaint = TextPaint(Paint.ANTI_ALIAS_FLAG) private val mDrawable: Drawable? var text: CharSequence = text set (value) { field = value invalidateSelf() } init { mPaint.typeface = ResourcesCompat.getFont(context, R.font.lato) mPaint.color = DEFAULT_COLOR mPaint.textAlign = Align.CENTER val textSize = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, DEFAULT_TEXT_SIZE_IN_DP.toFloat(), context.resources.displayMetrics) mPaint.textSize = textSize mDrawable = AppCompatResources.getDrawable(context, R.drawable.ic_backtodate) mDrawable!!.setBounds(0, 0, mDrawable.intrinsicWidth, mDrawable.intrinsicHeight) } override fun draw(canvas: Canvas) { val bounds = bounds mDrawable!!.draw(canvas) mPaint.getTextBounds(text.toString(), 0, text.length, mTextBounds); val textHeight = mTextBounds.bottom - mTextBounds.top canvas.drawText(text as String?, (bounds.right / 2).toFloat(), (bounds.bottom.toFloat() + textHeight + 1) / 2, mPaint) } override fun getOpacity(): Int = mPaint.alpha override fun getIntrinsicWidth(): Int = mDrawable!!.intrinsicWidth override fun getIntrinsicHeight(): Int = mDrawable!!.intrinsicHeight override fun setAlpha(alpha: Int) { mPaint.alpha = alpha invalidateSelf() } override fun setColorFilter(filter: ColorFilter?) { mPaint.colorFilter = filter invalidateSelf() } } 

编辑:这个代码现在完成,运作良好。 它应该正常工作,部分基于日历应用程序本身,正如我建议看看( 这里这里 )。

请参考您的另一个问题: “如何在工具栏中完全模拟操作项目视图,对于自定义项目?”

我已将上述引用问题的答案中的方法合并到您对此问题的回答中的自定义绘图的实现中。 下面是一个新的TextDrawable.java版本, TextDrawable.java可以动态构建一个盒装的TextView作为菜单项的图标。 它避免了绘制缓存,并简单地在内部管理TextView来显示。

TextDrawable.java

 public class TextDrawable extends Drawable { private final int mIntrinsicSize; private final TextView mTextView; public TextDrawable(Context context, CharSequence text) { mIntrinsicSize = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, DRAWABLE_SIZE, context.getResources().getDisplayMetrics()); mTextView = createTextView(context, text); mTextView.setWidth(mIntrinsicSize); mTextView.setHeight(mIntrinsicSize); mTextView.measure(mIntrinsicSize, mIntrinsicSize); mTextView.layout(0, 0, mIntrinsicSize, mIntrinsicSize); } private TextView createTextView(Context context, CharSequence text) { TextView textView = new TextView(context); // textView.setId(View.generateViewId()); // API 17+ LinearLayout.LayoutParams lp = new LinearLayout.LayoutParams( LinearLayout.LayoutParams.WRAP_CONTENT, LinearLayout.LayoutParams.WRAP_CONTENT); lp.gravity = Gravity.CENTER; textView.setLayoutParams(lp); textView.setGravity(Gravity.CENTER); textView.setBackgroundResource(R.drawable.ic_backtodate); textView.setTextColor(Color.WHITE); textView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, DEFAULT_TEXT_SIZE); textView.setText(text); return textView; } public void setText(CharSequence text) { mTextView.setText(text); invalidateSelf(); } @Override public void draw(@NonNull Canvas canvas) { mTextView.draw(canvas); } @Override public int getOpacity() { return PixelFormat.OPAQUE; } @Override public int getIntrinsicWidth() { return mIntrinsicSize; } @Override public int getIntrinsicHeight() { return mIntrinsicSize; } @Override public void setAlpha(int alpha) { } @Override public void setColorFilter(ColorFilter filter) { } private static final int DRAWABLE_SIZE = 32; // device-independent pixels (DP) private static final int DEFAULT_TEXT_SIZE = 12; // device-independent pixels (DP) } 

调用此自定义Drawable (Kotlin):

 mTextDrawable = TextDrawable(this, "1") menu.add("goToToday").setIcon(mTextDrawable).setShowAsAction(MenuItem.SHOW_AS_ACTION_ALWAYS) 

要更改显示的日期(Kotlin):

 mTextDrawable?.setText(i.toString())