| /* |
| * 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); |
| } |
| } |