/* * Copyright 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 * * https://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.google.android.material.internal; import static androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP; import android.content.Context; import android.graphics.Paint; import android.graphics.Typeface; import android.text.TextPaint; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.annotation.RestrictTo; import com.google.android.material.resources.TextAppearance; import com.google.android.material.resources.TextAppearanceFontCallback; import java.lang.ref.WeakReference; /** * Class that helps to support drawing text in drawables. It can be used by any drawable that draws * text. * * @hide */ @RestrictTo(LIBRARY_GROUP) public class TextDrawableHelper { private final TextPaint textPaint = new TextPaint(Paint.ANTI_ALIAS_FLAG); private final TextAppearanceFontCallback fontCallback = new TextAppearanceFontCallback() { @Override public void onFontRetrieved(@NonNull Typeface typeface, boolean fontResolvedSynchronously) { if (fontResolvedSynchronously) { return; } textSizeDirty = true; TextDrawableDelegate textDrawableDelegate = delegate.get(); if (textDrawableDelegate != null) { textDrawableDelegate.onTextSizeChange(); } } @Override public void onFontRetrievalFailed(int reason) { textSizeDirty = true; // Use fallback font. TextDrawableDelegate textDrawableDelegate = delegate.get(); if (textDrawableDelegate != null) { textDrawableDelegate.onTextSizeChange(); } } }; private float textWidth; private float textHeight; private boolean textSizeDirty = true; @Nullable private WeakReference delegate = new WeakReference<>(null); @Nullable private TextAppearance textAppearance; /** * Please provide a delegate if your text font may load asynchronously. */ public TextDrawableHelper(@Nullable TextDrawableDelegate delegate) { setDelegate(delegate); } /** Sets the delegate that owns this TextDrawableHelper. */ public void setDelegate(@Nullable TextDrawableDelegate delegate) { this.delegate = new WeakReference<>(delegate); } @NonNull public TextPaint getTextPaint() { return textPaint; } public void setTextWidthDirty(boolean dirty) { textSizeDirty = dirty; } public boolean isTextWidthDirty() { return textSizeDirty; } public void setTextSizeDirty(boolean dirty) { textSizeDirty = dirty; } private void refreshTextDimens(String text) { textWidth = calculateTextWidth(text); textHeight = calculateTextHeight(text); textSizeDirty = false; } /** Returns the visual width of the {@code text} based on its current text appearance. */ public float getTextWidth(String text) { if (!textSizeDirty) { return textWidth; } refreshTextDimens(text); return textWidth; } private float calculateTextWidth(@Nullable CharSequence charSequence) { if (charSequence == null) { return 0f; } return textPaint.measureText(charSequence, 0, charSequence.length()); } /** Returns the visual height of the {@code text} based on its current text appearance. */ public float getTextHeight(@Nullable String text) { if (!textSizeDirty) { return textHeight; } refreshTextDimens(text); return textHeight; } private float calculateTextHeight(@Nullable String str) { if (str == null) { return 0f; } return Math.abs(textPaint.getFontMetrics().ascent); } /** * Returns the text appearance. * * @see #setTextAppearance(TextAppearance, Context) */ @Nullable public TextAppearance getTextAppearance() { return textAppearance; } /** * Sets the delegate drawable's text appearance. If the {@code textAppearance} is {@code null}, * text appearance will be cleared. * * @param textAppearance The delegate drawable's text appearance or null to clear it. * @see #getTextAppearance() */ public void setTextAppearance(@Nullable TextAppearance textAppearance, Context context) { if (this.textAppearance != textAppearance) { this.textAppearance = textAppearance; if (textAppearance != null) { textAppearance.updateMeasureState(context, textPaint, fontCallback); TextDrawableDelegate textDrawableDelegate = delegate.get(); if (textDrawableDelegate != null) { textPaint.drawableState = textDrawableDelegate.getState(); } textAppearance.updateDrawState(context, textPaint, fontCallback); textSizeDirty = true; } TextDrawableDelegate textDrawableDelegate = delegate.get(); if (textDrawableDelegate != null) { textDrawableDelegate.onTextSizeChange(); textDrawableDelegate.onStateChange(textDrawableDelegate.getState()); } } } public void updateTextPaintDrawState(Context context) { textAppearance.updateDrawState(context, textPaint, fontCallback); } /** Delegate interface to be implemented by Drawables that own a TextDrawableHelper. */ public interface TextDrawableDelegate { // See Drawable#getState() @NonNull int[] getState(); /** Handles a change in the text's size. */ void onTextSizeChange(); // See Drawable#onStateChange(); boolean onStateChange(int[] state); } }