-
Notifications
You must be signed in to change notification settings - Fork 3.2k
Expand file tree
/
Copy pathTextAppearance.java
More file actions
333 lines (300 loc) · 11.7 KB
/
TextAppearance.java
File metadata and controls
333 lines (300 loc) · 11.7 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
/*
* Copyright 2017 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.resources;
import com.google.android.material.R;
import android.content.Context;
import android.content.res.ColorStateList;
import android.content.res.Resources;
import android.content.res.TypedArray;
import android.graphics.Color;
import android.graphics.Typeface;
import androidx.annotation.FontRes;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.RestrictTo;
import androidx.annotation.RestrictTo.Scope;
import androidx.annotation.StyleRes;
import androidx.annotation.VisibleForTesting;
import androidx.core.content.res.ResourcesCompat;
import androidx.core.content.res.ResourcesCompat.FontCallback;
import androidx.core.provider.FontsContractCompat.FontRequestCallback;
import android.text.TextPaint;
import android.util.Log;
/**
* Utility class that contains the data from parsing a TextAppearance style resource.
*
* @hide
*/
@RestrictTo(Scope.LIBRARY_GROUP)
public class TextAppearance {
private static final String TAG = "TextAppearance";
// Enums from AppCompatTextHelper.
private static final int TYPEFACE_SANS = 1;
private static final int TYPEFACE_SERIF = 2;
private static final int TYPEFACE_MONOSPACE = 3;
public final float textSize;
@Nullable public final ColorStateList textColor;
@Nullable public final ColorStateList textColorHint;
@Nullable public final ColorStateList textColorLink;
public final int textStyle;
public final int typeface;
@Nullable public final String fontFamily;
public final boolean textAllCaps;
@Nullable public final ColorStateList shadowColor;
public final float shadowDx;
public final float shadowDy;
public final float shadowRadius;
@FontRes private final int fontFamilyResourceId;
private boolean fontResolved = false;
private Typeface font;
/** Parses the given TextAppearance style resource. */
public TextAppearance(@NonNull Context context, @StyleRes int id) {
TypedArray a = context.obtainStyledAttributes(id, R.styleable.TextAppearance);
textSize = a.getDimension(R.styleable.TextAppearance_android_textSize, 0f);
textColor =
MaterialResources.getColorStateList(
context, a, R.styleable.TextAppearance_android_textColor);
textColorHint =
MaterialResources.getColorStateList(
context, a, R.styleable.TextAppearance_android_textColorHint);
textColorLink =
MaterialResources.getColorStateList(
context, a, R.styleable.TextAppearance_android_textColorLink);
textStyle = a.getInt(R.styleable.TextAppearance_android_textStyle, Typeface.NORMAL);
typeface = a.getInt(R.styleable.TextAppearance_android_typeface, TYPEFACE_SANS);
int fontFamilyIndex =
MaterialResources.getIndexWithValue(
a,
R.styleable.TextAppearance_fontFamily,
R.styleable.TextAppearance_android_fontFamily);
fontFamilyResourceId = a.getResourceId(fontFamilyIndex, 0);
fontFamily = a.getString(fontFamilyIndex);
textAllCaps = a.getBoolean(R.styleable.TextAppearance_textAllCaps, false);
shadowColor =
MaterialResources.getColorStateList(
context, a, R.styleable.TextAppearance_android_shadowColor);
shadowDx = a.getFloat(R.styleable.TextAppearance_android_shadowDx, 0);
shadowDy = a.getFloat(R.styleable.TextAppearance_android_shadowDy, 0);
shadowRadius = a.getFloat(R.styleable.TextAppearance_android_shadowRadius, 0);
a.recycle();
}
/**
* Synchronously resolves the font Typeface using the fontFamily, style, and typeface.
*
* @see androidx.appcompat.widget.AppCompatTextHelper
*/
@VisibleForTesting
@NonNull
public Typeface getFont(@NonNull Context context) {
if (fontResolved) {
return font;
}
// Try resolving fontFamily as a font resource.
if (!context.isRestricted()) {
try {
font = ResourcesCompat.getFont(context, fontFamilyResourceId);
if (font != null) {
font = Typeface.create(font, textStyle);
}
} catch (UnsupportedOperationException | Resources.NotFoundException e) {
// Expected if it is not a font resource.
} catch (Exception e) {
Log.d(TAG, "Error loading font " + fontFamily, e);
}
}
// If not resolved create fallback and resolve.
createFallbackFont();
fontResolved = true;
return font;
}
/**
* Resolves the requested font using the fontFamily, style, and typeface. Immediately (and
* synchronously) calls {@link TextAppearanceFontCallback#onFontRetrieved(Typeface, boolean)} with
* the requested font, if it has been resolved already, or {@link
* TextAppearanceFontCallback#onFontRetrievalFailed(int)} if requested fontFamily is invalid.
* Otherwise callback is invoked asynchronously when the font is loaded (or async loading fails).
* While font is being fetched asynchronously, {@link #getFallbackFont()} can be used as a
* temporary font.
*
* @param context the {@link Context}.
* @param callback callback to notify when font is loaded.
* @see androidx.appcompat.widget.AppCompatTextHelper
*/
public void getFontAsync(
@NonNull Context context, @NonNull final TextAppearanceFontCallback callback) {
if (TextAppearanceConfig.shouldLoadFontSynchronously()) {
getFont(context);
} else {
// No-op if font already resolved.
createFallbackFont();
}
if (fontFamilyResourceId == 0) {
// Only fontFamily id requires async fetch, if undefined the fallback font is the actual font.
fontResolved = true;
}
if (fontResolved) {
callback.onFontRetrieved(font, true);
return;
}
// Try to resolve fontFamily asynchronously. If failed fallback font is used instead.
try {
ResourcesCompat.getFont(
context,
fontFamilyResourceId,
new FontCallback() {
@Override
public void onFontRetrieved(@NonNull Typeface typeface) {
font = Typeface.create(typeface, textStyle);
fontResolved = true;
callback.onFontRetrieved(font, false);
}
@Override
public void onFontRetrievalFailed(int reason) {
fontResolved = true;
callback.onFontRetrievalFailed(reason);
}
},
/* handler */ null);
} catch (Resources.NotFoundException e) {
// Expected if it is not a font resource.
fontResolved = true;
callback.onFontRetrievalFailed(FontRequestCallback.FAIL_REASON_FONT_NOT_FOUND);
} catch (Exception e) {
Log.d(TAG, "Error loading font " + fontFamily, e);
fontResolved = true;
callback.onFontRetrievalFailed(FontRequestCallback.FAIL_REASON_FONT_LOAD_ERROR);
}
}
/**
* Asynchronously resolves the requested font Typeface using the fontFamily, style, and typeface,
* and automatically updates given {@code textPaint} using {@link #updateTextPaintMeasureState} on
* successful load.
*
* @param context The {@link Context}.
* @param textPaint {@link TextPaint} to be updated.
* @param callback Callback to notify when font is available.
* @see #getFontAsync(Context, TextAppearanceFontCallback)
*/
public void getFontAsync(
@NonNull Context context,
@NonNull final TextPaint textPaint,
@NonNull final TextAppearanceFontCallback callback) {
// Updates text paint using fallback font while waiting for font to be requested.
updateTextPaintMeasureState(textPaint, getFallbackFont());
getFontAsync(
context,
new TextAppearanceFontCallback() {
@Override
public void onFontRetrieved(
@NonNull Typeface typeface, boolean fontResolvedSynchronously) {
updateTextPaintMeasureState(textPaint, typeface);
callback.onFontRetrieved(typeface, fontResolvedSynchronously);
}
@Override
public void onFontRetrievalFailed(int i) {
callback.onFontRetrievalFailed(i);
}
});
}
/**
* Returns a fallback {@link Typeface} that is retrieved synchronously, in case the actual font is
* not yet resolved or pending async fetch or an actual {@link Typeface} if resolved already.
*
* <p>Fallback font is a font that can be resolved using typeface attributes not requiring any
* async operations, i.e. android:typeface, android:textStyle and android:fontFamily defined as
* string rather than resource id.
*/
public Typeface getFallbackFont() {
createFallbackFont();
return font;
}
private void createFallbackFont() {
// Try resolving fontFamily as a string name if specified.
if (font == null && fontFamily != null) {
font = Typeface.create(fontFamily, textStyle);
}
// Try resolving typeface if specified otherwise fallback to Typeface.DEFAULT.
if (font == null) {
switch (typeface) {
case TYPEFACE_SANS:
font = Typeface.SANS_SERIF;
break;
case TYPEFACE_SERIF:
font = Typeface.SERIF;
break;
case TYPEFACE_MONOSPACE:
font = Typeface.MONOSPACE;
break;
default:
font = Typeface.DEFAULT;
break;
}
font = Typeface.create(font, textStyle);
}
}
/**
* Applies the attributes that affect drawing from TextAppearance to the given TextPaint. Note
* that not all attributes can be applied to the TextPaint.
*
* @see android.text.style.TextAppearanceSpan#updateDrawState(TextPaint)
*/
public void updateDrawState(
@NonNull Context context,
@NonNull TextPaint textPaint,
@NonNull TextAppearanceFontCallback callback) {
updateMeasureState(context, textPaint, callback);
textPaint.setColor(
textColor != null
? textColor.getColorForState(textPaint.drawableState, textColor.getDefaultColor())
: Color.BLACK);
textPaint.setShadowLayer(
shadowRadius,
shadowDx,
shadowDy,
shadowColor != null
? shadowColor.getColorForState(textPaint.drawableState, shadowColor.getDefaultColor())
: Color.TRANSPARENT);
}
/**
* Applies the attributes that affect measurement from TextAppearance to the given TextPaint. Note
* that not all attributes can be applied to the TextPaint.
*
* @see android.text.style.TextAppearanceSpan#updateMeasureState(TextPaint)
*/
public void updateMeasureState(
@NonNull Context context,
@NonNull TextPaint textPaint,
@NonNull TextAppearanceFontCallback callback) {
if (TextAppearanceConfig.shouldLoadFontSynchronously()) {
updateTextPaintMeasureState(textPaint, getFont(context));
} else {
getFontAsync(context, textPaint, callback);
}
}
/**
* Applies the attributes that affect measurement from Typeface to the given TextPaint.
*
* @see android.text.style.TextAppearanceSpan#updateMeasureState(TextPaint)
*/
public void updateTextPaintMeasureState(
@NonNull TextPaint textPaint, @NonNull Typeface typeface) {
textPaint.setTypeface(typeface);
int fake = textStyle & ~typeface.getStyle();
textPaint.setFakeBoldText((fake & Typeface.BOLD) != 0);
textPaint.setTextSkewX((fake & Typeface.ITALIC) != 0 ? -0.25f : 0f);
textPaint.setTextSize(textSize);
}
}