blob: 3487d24c015c7903d1a3dc6ec6f4260b5acc6705 [file] [log] [blame]
/*
* Copyright (C) 2019 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.car.ui.drawable;
import android.content.res.Resources;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Color;
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.text.TextPaint;
import android.util.AttributeSet;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.android.car.ui.R;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
import java.io.IOException;
/**
* A drawable to renders a given text. It can be used as part of a compound
* drawable as follows:
*
* <pre>
* {@code
* <layer-list xmlns:android="http://schemas.android.com/apk/res/android">
* ...
* <item
* <drawable
* class="com.android.car.ui.drawable.CarUiTextDrawable"
* app:text="Some text"
* app:textSize="30sp">
* </drawable>
* </item>
* ...
* </layer-list>
* }
* </pre>
*
* <b>Important: This class is referenced by reflection from overlays. DO NOT
* RENAME, REMOVE OR MOVE TO ANOTHER PACKAGE, otherwise the overlays would
* cause the target application to crash.</b>
*/
public class CarUiTextDrawable extends Drawable {
private final TextPaint mPaint = new TextPaint();
private final Rect mTextBounds = new Rect(); // Minimum bounds of the text only.
// Attributes initialized during inflation
@Nullable
private String mText;
private Typeface mTypeface = Typeface.DEFAULT;
private float mTextSize = 10;
private int mTextColor = Color.WHITE;
/** Text sample used to measure font height */
private static final String TEXT_HEIGHT_SAMPLE = "Ag";
/** Constructor available to include this drawable by code */
public CarUiTextDrawable(@Nullable String text,
Typeface typeface,
float textSize,
int textColor) {
mText = text;
mTypeface = typeface;
mTextSize = textSize;
mTextColor = textColor;
refreshTextBoundsAndInvalidate();
}
/** Constructor invoked by XML inflator */
public CarUiTextDrawable() {
// To be used during inflation.
}
/** Updates the text rendered by this drawable */
public void setText(@Nullable String text) {
this.mText = text;
refreshTextBoundsAndInvalidate();
}
@Override
public int getIntrinsicHeight() {
return mTextBounds.height();
}
@Override
public int getIntrinsicWidth() {
return mTextBounds.width();
}
@Override
public void draw(Canvas canvas) {
Rect bounds = getBounds();
if (bounds.isEmpty() || mPaint.getAlpha() == 0) {
return;
}
// Draw text.
if (mText != null) {
canvas.drawText(mText,
bounds.centerX(),
bounds.centerY() + (mTextBounds.height() / 2.0f),
mPaint);
}
}
@Override
public void setAlpha(int alpha) {
final int old = mPaint.getAlpha();
if (alpha != old) {
mPaint.setAlpha(alpha);
invalidateSelf();
}
}
@Override
public void setColorFilter(ColorFilter cf) {
mPaint.setColorFilter(cf);
invalidateSelf();
}
@Override
public int getOpacity() {
return mPaint.bgColor != 0 ? PixelFormat.OPAQUE : PixelFormat.TRANSPARENT;
}
/**
* Call this to re-measure the text when properties that may affect the
* textBounds are changed.
*/
private void refreshTextBoundsAndInvalidate() {
mPaint.setTypeface(mTypeface);
mPaint.setTextSize(mTextSize);
mPaint.setColor(mTextColor);
mPaint.setTextAlign(Paint.Align.CENTER);
mPaint.setAntiAlias(true);
mPaint.setDither(true);
calculateTextBounds(mPaint, mText, mTextBounds);
invalidateSelf();
}
/**
* Calculate the bounds of the given text.
*
* The textBounds are different from the drawable bounds: the textBounds is
* is strictly a subset, and measures the minimum bounding box necessary to
* draw the text. The left and right of the textBounds are used to calculate
* the intrinsic width and intrinsic height.
*
* Text height is calculated based on a fixed sample text (to avoid height
* changing every time a different text is rendered)
*/
private static void calculateTextBounds(TextPaint paint, @Nullable String text,
Rect outBounds) {
outBounds.setEmpty();
paint.getTextBounds(TEXT_HEIGHT_SAMPLE, 0, TEXT_HEIGHT_SAMPLE.length(), outBounds);
if (text != null) {
// Save the top and bottom bounds of the sample text.
int top = outBounds.top;
int bottom = outBounds.bottom;
// Get the actual text bounds.
paint.getTextBounds(text, 0, text.length(), outBounds);
// Replace top and bottom with sample text bounds. We only care about left
// and right.
outBounds.top = top;
outBounds.bottom = bottom;
} else {
outBounds.left = 0;
outBounds.right = 0;
}
}
@NonNull
private static TypedArray themedObtainAttributes(@NonNull Resources res,
@Nullable Resources.Theme theme,
@NonNull AttributeSet set,
@NonNull int[] attrs) {
if (theme == null) {
return res.obtainAttributes(set, attrs);
}
return theme.obtainStyledAttributes(set, attrs, 0, 0);
}
@Override
public void inflate(@NonNull Resources r, @NonNull XmlPullParser parser,
@NonNull AttributeSet attrs, @Nullable Resources.Theme theme)
throws XmlPullParserException, IOException {
final TypedArray a = themedObtainAttributes(r, theme, attrs, R.styleable.CarUiTextDrawable);
mText = a.getString(R.styleable.CarUiTextDrawable_text);
mTypeface = Typeface.create(a.getString(R.styleable.CarUiTextDrawable_typeface),
Typeface.NORMAL);
mTextSize = a.getDimension(R.styleable.CarUiTextDrawable_textSize, 10);
mTextColor = a.getColor(R.styleable.CarUiTextDrawable_textColor,
r.getColor(R.color.car_ui_primary_text_color, theme));
a.recycle();
refreshTextBoundsAndInvalidate();
super.inflate(r, parser, attrs, theme);
}
}