2023-03-21 20:50:42 +00:00

197 lines
5.9 KiB
Java

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