Android Advanced TextView

# 自己紹介

こんにちは [@wasabeef_jp](https://twitter.com/wasabeef_jp) です
[去年](http://qiita.com/wasabeef_jp/items/f9436e1c9b74536caaff)に引き続き、今年も12/1を担当させて頂きます。

今回は、TextViewをもっとHackしようということで、Advanced TextViewを紹介します。


# Advanced TextView
## Compound Drawable

![animated_compound_drawable.png](https://qiita-image-store.s3.amazonaws.com/0/12099/6035b269-4f44-0a84-cbd6-f55b9f0aa1b3.png)

これは、とてもポピュラーな使い方で
TextViewのテキスト本文の上下左右にアイコンを表示したい場合などに有効です

```xml
<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"/>

<!--
  android:drawableLeft
  android:drawableRight
  android:drawableTop
  android:drawableBottom
-->

```

## Drawable XML (Animatable)
![animated_compound_drawable.gif](https://qiita-image-store.s3.amazonaws.com/0/12099/26d57f55-893d-a6fa-d187-d5eb2e584354.gif)

上記で説明した android:drawable... にはアイコンは勿論ですが、Animation XMLなども定義が出来ます

* AnimatedRotateDrawableで、1つの画像を回転させる

```xml
<!-- res/drawable/rotating_loading.xml -->
<animated-rotate
  android:pivotX="50%"
  android:pivotY="50%"
  android:drawable="@drawable/ic_loading"
  android:duration="500" />
```

* AnimationDrawableで、画像をGifアニメーションように動かす

```xml
<!-- 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>
```

* AnimatedVectorDrawableで、細かいアニメーションを設定する
  (**Introduced in Lollipop**)

```xml
<!-- 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>
```

```xml
<!-- anim-v21/hours_rotation.xml -->
<objectAnimator
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:duration="12000"
    android:propertyName="rotation"
    android:valueFrom="0"
    android:valueTo="360"
    android:repeatCount="-1"
    android:interpolator="@android:anim/linear_interpolator"/>
```

```xml
<!-- anim-v21/minutes_rotation.xml -->
<objectAnimator
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:duration="1000"
    android:propertyName="rotation"
    android:valueFrom="0"
    android:valueTo="360"
    android:repeatCount="-1"
    android:interpolator="@android:anim/linear_interpolator"/>
```

先ほど、説明したAnimatable XMLを利用し、アニメーションの開始をします

```xml
<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"/>
```
```java
private void startAnimation(TextView textView) {
  Drawable[] drawables = textView.getCompoundDrawables();
  for (Drawable drawable : drawables) {
    if (drawable != null && drawable instanceof Animatable) {
      ((Animatable) drawable).start();
    }
  }
}
```

## Shadow
![shadow.png](https://qiita-image-store.s3.amazonaws.com/0/12099/75151bef-8bc9-e197-0469-0decb097016c.png)

TextViewは、Canvasや9patchなどを使わずに影を簡単に描くことが出来ます

* 設定するプロパティは、shadowColor, shadowDx, shadowDy, shadowRadius
* dpで設定するのではなく、pxの単位になります
* また、paddingを入れてあげましょう


```xml
<TextView
  xmlns:android="http://schemas.android.com/apk/res/android"
  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"/>
```

## Shadow, abused
![shadow_blocky_glow.png](https://qiita-image-store.s3.amazonaws.com/0/12099/5c9a253b-2daa-7731-0abe-edd966d93330.png)

単純な影だけではなく、応用することでこのように幅が広がります

```xml
<TextView
  xmlns:android="http://schemas.android.com/apk/res/android"
  android:layout_width="wrap_content"
  android:layout_height="wrap_content"
  android:padding="12dp"
  android:text="@string/blocky"
  android:textColor="@color/purple"
  android:textSize="@dimen/80sp"
  android:textStyle="bold"
  android:shadowColor="@color/green"
  android:shadowDx="4"
  android:shadowDy="-4"
  android:shadowRadius="1"/>
```

```xml
<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"/>
```

## Custom font
![custom_font.png](https://qiita-image-store.s3.amazonaws.com/0/12099/583fa008-a0fb-a79f-3599-16307cd0e20d.png)

* カスタムフォント(日本語もOK)も簡単に適用させることが出来ます
* アプリ全体にカスタムフォントを適用させたい場合などは
[Calligraphy](https://github.com/chrisjenx/Calligraphy)などのFont Libraryを使いましょう

```java
Typeface typeface = Typeface.createFromAsset(getAssets(), "Ruthie.ttf");
textView.setTypeface(typeface);
```

## Gradient
![gradient.png](https://qiita-image-store.s3.amazonaws.com/0/12099/fa0505ea-bbd8-992b-8993-1134d0f0e302.png)

* Shaderを用いることで、単純な色だけではなくグラデーションをあてることもできます

```java
Shader shader = new LinearGradient(
    0, 0, 0, textView.getTextSize(), Color.RED, Color.BLUE, Shader.TileMode.CLAMP);
textView.getPaint().setShader(shader);
```

## Pattern
![patterned.png](https://qiita-image-store.s3.amazonaws.com/0/12099/0a29eabe-ced9-8611-3493-ac26cea87f54.png)

* グラデーションだけでなく、文字をリピート画像でリッチに仕上げることも出来ます

```java
Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.cheetah_tile);
Shader shader = new BitmapShader(bitmap, Shader.TileMode.REPEAT, Shader.TileMode.REPEAT);
textView.getPaint().setShader(shader);
```

## HTML
![from_html.png](https://qiita-image-store.s3.amazonaws.com/0/12099/10994a84-11ef-c325-366b-fa5fff6cd4d9.png)

* TextViewは、Plain文字列だけでなく、HTML文字列も対応しています
* ※全てのHTML Tagに対応してる訳ではありません。

```html
<!-- 簡単なHTMLの例 -->
<h1>Hello World</h1>
Here is an <img src="octopus"><i>octopus</i>.<br>
And here is a 
<a href="http://d.android.com/reference/android/text/Html.html">
link</a>.
```

* 上記のHTMLをstrings.xmlに設定します。

```xml
<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/reference/android/text/Html.html">
link</a>.
]]>
</string>
```

### HTML.fromHtml

* getStringで取得したデータをそのままTextView#setTextするのではなく HTML.fromHtmlを使います

```java
String html = getString(R.string.from_html_text);
```


* imgタグに対応していても画像を表示してくれないので、Html.ImageGetterを継承して画像を処理します。

```java
String html = getString(R.string.from_html_text);
textView.setText(Html.fromHtml(html, new ResouroceImageGetter(this), null));
```

```java
private static class ResouroceImageGetter implements Html.ImageGetter {
  private final Context context;

  public ResouroceImageGetter(Context context) {
    this.context = context;
  }

  public Drawable getDrawable(String source) {
    int path = context.getResources().getIdentifier(source, "drawable", BuildConfig.APPLICATION_ID);
    Drawable drawable = context.getResources().getDrawable(path);
    drawable.setBounds(0, 0, drawable.getIntrinsicWidth(), drawable.getIntrinsicHeight());
    return drawable;
  }
}
```

## Clickable link
![from_html.gif](https://qiita-image-store.s3.amazonaws.com/0/12099/6bd8104d-5b4e-b222-afab-8915086752c7.gif)

* aタグのリンクは、デフォルトで無効になっているので、有効にしたい場合は下記のようにします

```java
textView.setMovementMethod(LinkMovementMethod.getInstance());
```

## Supported tags

* 下記以外のタグは、TagHandlerを使って自分で対応していく必要があります

```html
<!-- Supported Html tags -->
<a href="...">
<b>,  <big>, <blockquote>, <br>, <cite>, <dfn>
<div align="...">,  <em>, <font size="..." color="..." face="...">
<h1>,  <h2>, <h3>, <h4>,  <h5>, <h6>
<i>,  <img src="...">,  <p>, <small>
<strike>,  <strong>, <sub>, <sup>, <tt>, <u>
```

## SPAN

TextViewのSpan, TagHandlerは、とても強力で、カスタムしやすくなっています

by Flavien Laurent
http://flavienlaurent.com/blog/2014/01/31/spans/

```html
One <u>two</u> three
```

↓

```
"One two three" + Underline from position 4 to 6
```

↓

```java
spannableString.setSpan(new UnderlineSpan(), 4, 6, flags);
```

### CharacterStyle
* 文字のフォーマットで使うキーワード
* e.g. ForegroundColorSpan, BackgroundColorSpan, UnderlineSpan


### ParagraphStyle
* 段落のフォーマットで使うキーワード
* e.g. BulletSpan, IconMarginSpan, QuoteSpan

## Stacked fractions
![fraction_equation.png](https://qiita-image-store.s3.amazonaws.com/0/12099/72687595-357f-95b3-b6fb-97f6a78675ff.png)


* 画像ではなく、分数の括線のように文字を積み重ねて表示することもできます
* ただ、setFontFeatureSettingsを使う場合は、Lollipop以上である必要があります

```java
TextPaint#setFontFeatureSettings
@TargetApi(LOLLIPOP)
```

```xml
<string name="fraction_text">
<![CDATA[
1<afrc>1/2</afrc> + <afrc>11/16</afrc> = 2<afrc>3/16</afrc>
]]>
</string>
```

```java
Typeface typeface = Typeface.createFromAsset(getAssets(), "Nutso2.otf");
textView.setTypeface(typeface);
```

* HTML.fromHtmlをする際にTagHandlerも指定います

```java
String html = getString(R.string.fraction_text);
textView.setText(Html.fromHtml(html, null, new FractionTagHandler())
```

* Html.TagHandlerを継承してhandleTagの中のoutputに対して処理します

```java
private static class FractionTagHandler implements Html.TagHandler {
  @Override 
  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);
    }
  }
}
```

```java
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;
}
```

* Html.TagHandlerとSpan系は、相互に利用することが多く、ここではMetricAffectingSpanを使います

```java
private static class FractionSpan extends MetricAffectingSpan {
  public void updateMeasureState(TextPaint textPaint) {
    textPaint.setFontFeatureSettings("afrc");
  }
  public void updateDrawState(TextPaint textPaint) {
    textPaint.setFontFeatureSettings("afrc");
  }
}
```

### Arbitrary fractions
![fractions.png](https://qiita-image-store.s3.amazonaws.com/0/12099/209f5336-363a-5679-a3f1-37646e958a34.png)

## Styled string
![styled_string.png](https://qiita-image-store.s3.amazonaws.com/0/12099/00ee97f2-e6fb-90db-ceb4-6db626ed1b42.png)

* SpannableStringを使うとテキストに細かいスタイルを当てる事ができます

```java
private 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;
}
```

* 一行毎に違うスタイルを当てていきます

```java
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を使います

```xml
<style name="BigRedTextAppearance" parent="@android:style/TextAppearance">
  <item name="android:textSize">56sp</item>
  <item name="android:textColor">#c00</item>
</style>
```

## AlignmentSpan
![alignment_span.png](https://qiita-image-store.s3.amazonaws.com/0/12099/09cf5c2b-d106-1d2b-d012-78adb4d88246.png)

AlignmentSpanを使うと指定したテキスト範囲の揃え位置を変更できます

```java
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);
}
```
```java
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);
}
```

## Rainbow span
![rainbow.png](https://qiita-image-store.s3.amazonaws.com/0/12099/4853de41-d873-cae8-7d16-98d584c531f2.png)

* LinearGradientを応用して、テキストの一部分にGradientを当てます

```java
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);
```

```java
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);
  }
}
```

## Animated rainbow span
![animated_rainbow.gif](https://qiita-image-store.s3.amazonaws.com/0/12099/057bf6eb-9569-f561-fac6-1f3b0f2d9121.gif)

実際、Spanは本当に強力で、自作さえすれば何でも出来ます
ここでは、テキストの一部にアニメーションを当ててみます

* spanに対してProperty<T, V>の作成ObjectAnimatorを設定します

```java
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();
  }
};
```
```java
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();
```

```java
private static class AnimatedColorSpan extends CharacterStyle implements UpdateAppearance {

  public AnimatedColorSpan(Context context) {
    colors = context.getResources().getIntArray(R.array.rainbow);
  }

  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);
  }
}
```

## ClickableSpan
![clickable_span.gif](https://qiita-image-store.s3.amazonaws.com/0/12099/a93ca56f-a8cf-03f7-e072-e7091b373a2a.gif)

* TextView.setMovementMethodを応用すると、クリックのハンドリングも行えます
* SpannableString#setSpanで、適用するSpanクラス、開始から終了地点を指定します

```java
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());
```

* 設定画面にIntent使います

```java
private static class GoToSettingsSpan extends ClickableSpan {
  public void onClick(View view) {
    view.getContext().startActivity(new Intent(android.provider.Settings.ACTION_SETTINGS));
  }
}
```
```xml
<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"/>
```

## Lined paper
![lined_paper.gif](https://qiita-image-store.s3.amazonaws.com/0/12099/91712534-081a-71f6-6529-6e05908fc434.gif) ![lined_paper_huge.png](https://qiita-image-store.s3.amazonaws.com/0/12099/dc0c35bb-e944-8aa6-97f0-a3c90064c50f.png)

* 紙のノートのように行にEditTextのベースラインに線を引くも出来ます

```java
public class LinedEditText extends EditText {
  // Called from constructors
  private void init() {
    this.paint = new Paint();
    this.paint.setStyle(Paint.Style.STROKE);
    this.paint.setColor(getResources().getColor(R.color.paper_line));
    this.paint.setStrokeWidth(getLineHeight() / 10);
    this.paint.setStrokeCap(Paint.Cap.ROUND);
  }
}
```

## Emoji
![emoji_raw.png](https://qiita-image-store.s3.amazonaws.com/0/12099/798680c9-0f6d-e5a3-6483-96287fc2985b.png) ![emoji.png](https://qiita-image-store.s3.amazonaws.com/0/12099/b565b5a6-1b11-e128-f277-d882fdf66418.png)

* "\u26F7"に該当する文字列の部分に自作のIconFontSpanを当てます

```java
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);
}
```

* カスタムフォントを用いてアイコンを設定します

```java
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);
    }
}
```

## ImageSpan from resources
![emoji.png](https://qiita-image-store.s3.amazonaws.com/0/12099/55f36a89-9c16-faac-f8fd-1f73ae625315.png) ![font_metrics.png](https://qiita-image-store.s3.amazonaws.com/0/12099/be25c84a-08c6-3f6b-4ecb-dd38af1bb87f.png)

* カスタムフォントではなく、該当のところにBitmapを当てることもできます

```java
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);
}
```

## Dynamic ImageSpan (SpeedSignDrawable)
![emoji.png](https://qiita-image-store.s3.amazonaws.com/0/12099/b87a07d2-2b57-6da6-576f-68d3c15a27b0.png)

* ":speed_50:"や":speed_110:"などにPatternで正規表現を用いて、動的にCanvasで描いていきます

```java
// :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);
}
```

* カスタムフォントでもBitmapでもなく、Drawable(Paint, Canvas)で描画します

```java
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);
  }
```

* 黄色の丸

```java
  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);
  }
}
```

* 外周の赤枠

```java
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);
}
```

* 数字部分

```java
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);
}
```

* 上記で定義したメソッドを呼びます

```java
  public void draw(Canvas canvas) {
    drawYellowCircle(canvas);
    drawRedRing(canvas);
    drawBlackNumber(canvas);
  }
```

原文：https://github.com/chiuki/advanced-textview

# 雑感
なんか面白そうな勉強会がありましたら、呼んで下さい。

[@wasabeef_jp](https://twitter.com/wasabeef_jp)まで(੭ु๑•ૅω•´)੭ु⁾⁾