by Chiu-Ki Chan
This tag and its children can be replaced by one <TextView/> and a compound drawable
A LinearLayout which contains an ImageView and a TextView can be more efficiently handled as a compound drawable
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/animation"
android:drawableLeft=
"@drawable/rotating_loading"
android:drawableRight=
"@drawable/animated_wifi"
android:drawableBottom=
"@drawable/animated_clock"/>
<!-- res/drawable/rotating_loading.xml -->
<animated-rotate
android:pivotX="50%"
android:pivotY="50%"
android:drawable="@drawable/ic_loading"
android:duration="500" />
AnimatedRotateDrawable implements Animatable
<!-- res/drawable/animated_wifi.xml -->
<animation-list>
<item android:drawable="@drawable/ic_wifi_0"
android:duration="250" />
<item android:drawable="@drawable/ic_wifi_1"
android:duration="250" />
<item android:drawable="@drawable/ic_wifi_2"
android:duration="250" />
<item android:drawable="@drawable/ic_wifi_3"
android:duration="250" />
</animation-list>
AnimationDrawable implements Animatable
<!-- res/drawable/animated_clock.xml -->
<animated-vector android:drawable="@drawable/clock">
<target android:name="hours"
android:animation="@anim/hours_rotation" />
<target android:name="minutes"
android:animation="@anim/minutes_rotation" />
</animated-vector>
AnimatedVectorDrawable implements Animatable
Introduced in Lollipop
private void startAnimation(
TextView textView) {
Drawable[] drawables
= textView.getCompoundDrawables();
for (Drawable drawable : drawables) {
if (drawable != null &&
drawable instanceof Animatable) {
((Animatable) drawable).start();
}
}
}
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:padding="12dp"
android:text="@string/shadow"
android:textSize="80sp"
android:textStyle="bold"
android:shadowColor="#7000"
android:shadowDx="12"
android:shadowDy="12"
android:shadowRadius="8"/>
shadowColor
,
shadowDx
,
shadowDy
,
shadowRadius
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:padding="12dp"
android:text="@string/blocky"
android:textColor="@color/purple"
android:textSize="80sp"
android:textStyle="bold"
android:shadowColor="@color/green"
android:shadowDx="4"
android:shadowDy="-4"
android:shadowRadius="1"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:padding="12dp"
android:text="@string/glow"
android:textSize="80sp"
android:textStyle="bold"
android:textColor="@android:color/white"
android:background="@android:color/black"
android:shadowColor="@color/yellow"
android:shadowDx="0"
android:shadowDy="0"
android:shadowRadius="24"/>
Typeface typeface
= Typeface.createFromAsset(
getAssets(), "Ruthie.ttf");
textView.setTypeface(typeface);
Shader shader = new LinearGradient(
0, 0, 0, textView.getTextSize(),
Color.RED, Color.BLUE,
Shader.TileMode.CLAMP);
textView.getPaint().setShader(shader);
Bitmap bitmap
= BitmapFactory.decodeResource(
getResources(),
R.drawable.cheetah_tile);
Shader shader = new BitmapShader(
bitmap,
Shader.TileMode.REPEAT,
Shader.TileMode.REPEAT);
textView.getPaint().setShader(shader);
<h1>Hello World</h1>
Here is an
<img src="octopus"><i>octopus</i>.<br>
And here is a
<a href="http://d.android.com">
link</a>.
<string name="from_html_text">
<![CDATA[
<h1>Hello World</h1>
Here is an
<img src="octopus"><i>octopus</i>.<br>
And here is a
<a href="http://d.android.com">
link</a>.
]]>
</string>
String html = getString(R.string.from_html_text);
textView.setMovementMethod(
LinkMovementMethod.getInstance());
textView.setText(Html.fromHtml(
html, new ResourceImageGetter(this), null));
private static class ResourceImageGetter
implements Html.ImageGetter {
// Constructor takes a Context
public Drawable getDrawable(String source) {
int path = context.getResources().getIdentifier(
source, "drawable", context.getPackageName());
Drawable drawable = ContextCompat.getDrawable(context, path);
drawable.setBounds(0, 0,
drawable.getIntrinsicWidth(),
drawable.getIntrinsicHeight());
return drawable;
}
}
TagHandler
One <u>two</u> three
↓
"One two three"
+ Underline from position 4 to 6
↓
spannableString.setSpan(new UnderlineSpan(), 4, 6, flags);
ForegroundColorSpan
,
BackgroundColorSpan
,
UnderlineSpan
BulletSpan
,
IconMarginSpan
,
QuoteSpan
by Flavien Laurent
TextPaint#setFontFeatureSettings
@TargetApi(LOLLIPOP)
<string name="fraction_text">
<![CDATA[
1<afrc>1/2</afrc> + <afrc>11/16</afrc>
= 2<afrc>3/16</afrc>
]]>
</string>
Typeface typeface
= Typeface.createFromAsset(
getAssets(), "Nutso2.otf");
textView.setTypeface(typeface);
String html = getString(
R.string.fraction_text);
textView.setText(Html.fromHtml(
html, null,
new FractionTagHandler()));
private static class FractionTagHandler implements Html.TagHandler {
public void handleTag(boolean opening,
String tag, Editable output, XMLReader xmlReader) {
if (!"afrc".equalsIgnoreCase(tag)) return;
int len = output.length();
if (opening) {
output.setSpan(new FractionSpan(), len, len,
Spannable.SPAN_MARK_MARK);
} else {
Object obj = getLast(output, FractionSpan.class);
int where = output.getSpanStart(obj);
output.removeSpan(obj);
if (where != len) {
output.setSpan(new FractionSpan(), where, len, 0);
}
}
}
private Object getLast(Editable text, Class kind) {
Object[] objs = text.getSpans(0, text.length(), kind);
if (objs.length == 0) return null;
for (int i = objs.length - 1; i >= 0; --i) {
if(text.getSpanFlags(objs[i]) == Spannable.SPAN_MARK_MARK) {
return objs[i];
}
}
return null;
}
private static class FractionSpan extends MetricAffectingSpan {
public void updateMeasureState(TextPaint textPaint) {
textPaint.setFontFeatureSettings("afrc");
}
public void updateDrawState(TextPaint textPaint) {
textPaint.setFontFeatureSettings("afrc");
}
}
static SpannableString formatString(
Context context,
int textId, int styleId) {
String text = context.getString(textId);
SpannableString spannableString
= new SpannableString(text);
spannableString.setSpan(
new TextAppearanceSpan(
context, styleId),
0, text.length(), 0);
return spannableString;
}
SpannableStringBuilder builder
= new SpannableStringBuilder()
.append(formatString(
this, R.string.big_red,
R.style.BigRedTextAppearance))
.append("\n")
.append(formatString(
this, R.string.medium_green,
R.style.MediumGreenTextAppearance))
.append("\n")
.append(formatString(
this, R.string.small_blue,
R.style.SmallBlueTextAppearance));
textView.setText(
builder.subSequence(
0, builder.length()));
<style name="BigRedTextAppearance"
parent="@android:style/TextAppearance">
<item name="android:textSize">
56sp</item>
<item name="android:textColor">
#c00</item>
</style>
public void click(View button) {
String text
= editText.getText().toString();
Layout.Alignment align =
button.getId() ==
R.id.add_to_right_button ?
Layout.Alignment.ALIGN_OPPOSITE :
Layout.Alignment.ALIGN_NORMAL;
appendText(text, align);
editText.setText(null);
}
private void appendText(
CharSequence text,
Layout.Alignment align) {
AlignmentSpan span
= new AlignmentSpan.Standard(align);
SpannableString spannableString
= new SpannableString(text);
spannableString.setSpan(
span, 0, text.length(), 0);
if (textView.length() > 0) {
textView.append("\n\n");
}
textView.append(spannableString);
}
String text
= textView.getText().toString();
SpannableString spannableString
= new SpannableString(text);
int start
= text.toLowerCase().indexOf(substring);
int end
= start + substring.length();
spannableString.setSpan(
new RainbowSpan(this), start, end, 0);
private static class RainbowSpan
extends CharacterStyle
implements UpdateAppearance {
private final int[] colors;
public RainbowSpan(Context context) {
colors = context.getResources()
.getIntArray(
R.array.rainbow);
}
public void updateDrawState(
TextPaint paint) {
paint.setStyle(Paint.Style.FILL);
Shader shader = new LinearGradient(
0, 0, 0,
paint.getTextSize() * colors.length,
colors, null,
Shader.TileMode.MIRROR);
Matrix matrix = new Matrix();
matrix.setRotate(90);
shader.setLocalMatrix(matrix);
paint.setShader(shader);
}
ObjectAnimator objectAnimator
= ObjectAnimator.ofFloat(
span,
ANIMATED_COLOR_SPAN_FLOAT_PROPERTY,
0, 100);
objectAnimator.setEvaluator(
new FloatEvaluator());
objectAnimator.addUpdateListener(
new ValueAnimator.AnimatorUpdateListener() {
public void onAnimationUpdate(
ValueAnimator animation) {
textView.setText(spannableString);
}
});
objectAnimator.setInterpolator(
new LinearInterpolator());
objectAnimator.setDuration(
DateUtils.MINUTE_IN_MILLIS * 3);
objectAnimator.setRepeatCount(
ValueAnimator.INFINITE);
objectAnimator.start();
private static final
Property<AnimatedColorSpan, Float>
ANIMATED_COLOR_SPAN_FLOAT_PROPERTY
= new Property<AnimatedColorSpan, Float>(
Float.class,
"ANIMATED_COLOR_SPAN_FLOAT_PROPERTY") {
public void set(
AnimatedColorSpan span, Float value) {
span.setTranslateXPercentage(value);
}
public Float get(AnimatedColorSpan span) {
return span.getTranslateXPercentage();
}
};
public void setTranslateXPercentage(
float value) {
translateXPercentage = value;
}
public float getTranslateXPercentage() {
return translateXPercentage;
}
public void updateDrawState(
TextPaint paint) {
paint.setStyle(Paint.Style.FILL);
float width
= paint.getTextSize() * colors.length;
if (this.shader == null) {
this.shader = new LinearGradient(
0, 0, 0, width, colors, null,
Shader.TileMode.MIRROR);
}
this.matrix.reset();
this.matrix.setRotate(90);
this.matrix.postTranslate(
width * translateXPercentage, 0);
this.shader.setLocalMatrix(this.matrix);
paint.setShader(this.shader);
}
String text = textView.getText().toString();
String goToSettings = getString(R.string.go_to_settings);
int start = text.indexOf(goToSettings);
int end = start + goToSettings.length();
SpannableString spannableString = new SpannableString(text);
spannableString.setSpan(new GoToSettingsSpan(), start, end, 0);
textView.setText(spannableString);
textView.setMovementMethod(new LinkMovementMethod());
private static class GoToSettingsSpan extends ClickableSpan {
public void onClick(View view) {
view.getContext().startActivity(
new Intent(android.provider.Settings.ACTION_SETTINGS));
}
}
<TextView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:text="@string/clickable_span_text"
android:textColorLink="@color/go_to_settings"
android:textColorHighlight="@color/light_green"/>
public class LinedEditText
extends EditText {
private void init() {
this.paint = new Paint();
paint.setStyle(Paint.Style.STROKE);
paint.setColor(
getResources().getColor(
R.color.paper_line));
paint.setStrokeWidth(
getLineHeight() / 10);
paint.setStrokeCap(Paint.Cap.ROUND);
}
}
protected void onDraw(Canvas canvas) {
float startX = getPaddingLeft();
float stopX
= getWidth() - getPaddingRight();
float offsetY = getPaddingTop()
- getPaint().getFontMetrics().top
+ paint.getStrokeWidth() / 2;
for (int i = 0; i < getLineCount(); ++i) {
float y = offsetY + getLineHeight() * i;
canvas.drawLine(
startX, y, stopX, y, paint);
}
super.onDraw(canvas);
}
fontMetrics.top
String text
= textView.getText().toString();
SpannableString spannableString
= new SpannableString(text);
IconFontSpan iconFontSpan
= new IconFontSpan(
textView.getContext());
Pattern pattern
= Pattern.compile("\u26F7"); // skier
Matcher matcher = pattern.matcher(text);
while (matcher.find()) {
spannableString.setSpan(iconFontSpan,
matcher.start(), matcher.end(), 0);
}
private static class IconFontSpan
extends MetricAffectingSpan {
private static Typeface typeface = null;
public IconFontSpan(Context context) {
if (typeface == null) {
typeface = Typeface.createFromAsset(
context.getAssets(), "icomoon.ttf");
}
}
public void updateMeasureState(
TextPaint textPaint) {
textPaint.setTypeface(typeface);
}
public void updateDrawState(
TextPaint textPaint) {
textPaint.setTypeface(typeface);
}
}
Pattern pattern = Pattern.compile(":octopus:");
Matcher matcher = pattern.matcher(text);
Bitmap octopus = null;
int size = (int) (-textView.getPaint().ascent());
while (matcher.find()) {
if (octopus == null) {
Bitmap bitmap = BitmapFactory.decodeResource(
getResources(), R.drawable.octopus);
octopus = Bitmap.createScaledBitmap(
bitmap, size, size, true);
bitmap.recycle();
}
ImageSpan span = new ImageSpan(
this, octopus, ImageSpan.ALIGN_BASELINE);
spannableString.setSpan(
span, matcher.start(), matcher.end(), 0);
}
// :speed_50: :speed_110:
Pattern pattern
= Pattern.compile(":speed_(\\d\\d\\d?):");
Pattern matcher = pattern.matcher(text);
while (matcher.find()) {
SpeedSignDrawable drawable
= new SpeedSignDrawable(
textView, matcher.group(1));
ImageSpan span = new ImageSpan(
drawable, ImageSpan.ALIGN_BASELINE);
spannableString.setSpan(
span, matcher.start(), matcher.end(), 0);
}
private static class SpeedSignDrawable
extends Drawable {
public SpeedSignDrawable(
TextView textView, String number) {
this.ascent
= textView.getPaint().ascent();
this.descent
= textView.getPaint().descent();
this.textSize = textView.getTextSize();
this.strokeWidth
= textView.getPaint().getStrokeWidth();
this.number = number;
int size = (int) -ascent;
this.setBounds(0, 0, size, size);
}
public void draw(Canvas canvas) {
drawYellowCircle(canvas);
drawRedRing(canvas);
drawBlackNumber(canvas);
}
private void drawYellowCircle(Canvas canvas) {
int size = (int) -ascent;
Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);
paint.setStyle(Paint.Style.FILL);
paint.setColor(Color.YELLOW);
canvas.drawCircle(
size/2, size/2, size/2, paint);
}
private void drawRedRing(Canvas canvas) {
Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);
paint.setStyle(Paint.Style.STROKE);
paint.setColor(Color.RED);
float ringWidth = size / 10;
paint.setStrokeWidth(ringWidth);
canvas.drawCircle(
size/2, size/2, size/2 - ringWidth/2, paint);
}
private void drawBlackNumber(Canvas canvas) {
float ratio = 0.4f;
Paint paint = new Paint(
Paint.ANTI_ALIAS_FLAG);
paint.setStyle(Paint.Style.FILL);
paint.setColor(Color.BLACK);
paint.setTextAlign(Paint.Align.CENTER);
paint.setTextSize(textSize * ratio);
paint.setStrokeWidth(strokeWidth);
paint.setTypeface(
Typeface.defaultFromStyle(Typeface.BOLD));
int x = size / 2;
int y = (int) (size/2
- ((descent + ascent)/2) * ratio);
canvas.drawText(number, x, y, paint);
}
CompoundDrawable
HTML.fromHtml()
setFontFeatureSettings()
AlignmentSpan
ClickableSpan