Фон
В приложении Google Calendar есть элемент действия, который динамически изменяется в соответствии с текущим днем ( "сегодня" ):
Мне нужно делать очень похожие вещи, но с немного другим изображением, которое окружает текст.
Проблема
Мне удалось сделать это, создав Drawable, в котором есть текст и изображение (на основе здесь).
Однако, я не думаю, что я сделал это достаточно хорошо:
- Текстовый шрифт может быть разным на разных устройствах, поэтому может не соответствовать тому, что я написал.
- Не уверен, что это из-за VectorDrawable или текста, но я думаю, что текст не кажется таким центрированным. Кажется немного слева. Это особенно верно, если я использую 2 цифры:
- Для центрирования по вертикали, я не думаю, что сделал правильный расчет. Я попробовал гораздо более логичные вещи, но они не были сосредоточены.
Что я пробовал
Здесь полный код (также доступен здесь в проекте):
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
<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>
Вопросы
-
Как я могу преодолеть различные проблемы с шрифтами? Я уже использую шрифт "Lato" глобально (не в примере приложения, а в реальном приложении, используя "загруженные шрифты" API библиотеки поддержки, но вместо этого они встроены в приложение), но я не думаю, что
Paint
Object может использовать его, правильно? -
Как я могу централизовать текст?
-
Я посмотрел, используя инструмент View-hierarchy, как работает Календарь Google для этой части. Мне кажется, они просто использовали TextView. Как они это делают? Возможно, используя 9-патч? Но хорошо ли это работает для элементов панели инструментов?
EDIT:
В настоящее время, поскольку я напряжен по графику, я не могу использовать выталкиваемое решение. Было бы неплохо знать, как это сделать хорошо.
Мое текущее решение не связано с этим. Я просто использую специальное представление, которое имитирует обычный элемент действия. Это не идеально (не полностью имитирует реальный элемент действия), но этого пока достаточно. Поскольку он не идеален, я написал об этом в новом потоке, здесь.
EDIT: так как это действительно может работать хорошо и по-прежнему оставаться как обычный элемент действия, я решил попробовать еще раз.
Мне удалось красиво центрировать текст, но теперь этот шрифт является проблемой. Похоже, что если ОС использует собственный шрифт, даже если я установил "Lato" в качестве одного из приложений, он не будет использоваться в drawable, который я сделал:
Я думаю, что это последняя проблема, которую мне нужно исправить здесь.
Здесь код:
styles.xml
<item name="android:fontFamily" tools:targetApi="jelly_bean">@font/lato</item>
<item name="fontFamily">@font/lato</item>
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()
}
}
EDIT:
Я думаю, что нашел способ шрифта для текста, используя:
mPaint.typeface=TypefaceCompat.createFromResourcesFamilyXml(...)
Не уверен, хотя как заполнить параметры. Продолжая расследование...