如何在其中添加动态文字的图片,所有图片都是可绘制的,例如Google日历应用中的"今天"操作项?

and*_*per 13 android android-drawable android-toolbar

背景

Google日历应用有一个动作项,可根据当天("今天")动态更改:

在此输入图像描述

我需要做一个非常相似的事情,但周围的文字略有不同.

问题

通过创建一个包含文本和图像的Drawable(基于此处),我确实成功地使其工作.

但是,我认为我做得不够好:

  1. 文本字体可能因设备而异,因此可能不适合我写的内容.
  2. 不确定是不是因为VectorDrawable或文本,但我认为文本看起来并不那么集中.看起来有点左边.如果我使用2位数,则尤其如此:

在此输入图像描述

  1. 对于垂直居中,我认为我没有做正确的计算.我在那里尝试了更多合乎逻辑的东西,但它们没有集中.

我试过的

下面是完整的代码(也可在此项目中的):

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);
    }
}
Run Code Online (Sandbox Code Playgroud)

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)
    }
}
Run Code Online (Sandbox Code Playgroud)

ic_backtodate.xml

<vector xmlns:android="http://schemas.android.com/apk/res/android" android:width="25dp" android:height="25dp"
        android:viewportHeight="76.0" android:viewportWidth="76.0">
    <path
        android:fillColor="#ffffff" android:fillType="evenOdd"
        android:pathData="M47.294,60.997H28.704C21.148,60.997 15,54.755 15,47.083V28.905c0,-7.672 6.148,-13.913 13.704,-13.913h18.59C54.852,14.992 61,21.233 61,28.905v18.178c0,7.672 -6.148,13.914 -13.706,13.914zM57.592,28.905c0,-5.763 -4.62,-10.453 -10.298,-10.453h-18.59c-5.676,0 -10.296,4.69 -10.296,10.453v18.178c0,5.765 4.62,10.454 10.296,10.454h18.59c5.678,0 10.298,-4.689 10.298,-10.454z"/>
</vector>
Run Code Online (Sandbox Code Playgroud)

问题

  1. 如何克服不同的字体问题?我已经全局使用"Lato"字体(不是在示例应用程序中,而是在真实应用程序中,使用支持库的"下载字体" API,但将它们内置到应用程序中),但我不认为Paint对象可以用它吧?

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

  3. 我通过View-hierarchy工具查看了Google Calendar如何为此部分工作.对我来说,他们似乎只使用了TextView.他们是如何做到的呢?也许使用9补丁?但它对Toolbar项目有效吗?


编辑:

就目前而言,由于我的日程紧张,我不能使用可绘制的解决方案.如果能做得好,仍然会很高兴.

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


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

我已经设法很好地集中了文本,但字体现在是问题.似乎如果操作系统使用自己的字体,即使我将"Lato"设置为应用程序之一,它也不会用在我制作的drawable中:

在此输入图像描述

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

这是代码:

styles.xml

    <item name="android:fontFamily" tools:targetApi="jelly_bean">@font/lato</item>
    <item name="fontFamily">@font/lato</item>
Run Code Online (Sandbox Code Playgroud)

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)
    }
}
Run Code Online (Sandbox Code Playgroud)

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

}
Run Code Online (Sandbox Code Playgroud)

编辑:

我想我已经找到了如何使用文字的字体,使用:

mPaint.typeface=TypefaceCompat.createFromResourcesFamilyXml(...)
Run Code Online (Sandbox Code Playgroud)

不确定如何填写参数.还在调查......

and*_*per 5

好的,找到有关如何为我制作的Drawable类的TextPaint使用相同字体的答案:

mPaint.typeface = ResourcesCompat.getFont(context, R.font.lato)
Run Code Online (Sandbox Code Playgroud)

结果:

在此处输入图片说明

这是该类的完整实现:

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

}
Run Code Online (Sandbox Code Playgroud)

编辑:此代码现已完成,并且运行良好。它应该可以正常工作,并且部分基于Calendar应用程序本身,这是我建议查看的地方(此处和此处)。