TextViewの文字の周りに縁を表示したい場合の実装。
#コード
まずはコード。
public class OutlineTextView extends AppCompatTextView {
public OutlineTextView(Context context, AttributeSet attrs) {
super(context, attrs);
}
@Override
public void draw(Canvas canvas) {
super.draw(canvas);
canvas.drawColor(Color.TRANSPARENT, PorterDuff.Mode.CLEAR);
String text = (String) getText();
int textColor = getCurrentTextColor();
int textSize = (int) getTextSize();
Paint paint = new Paint();
paint.setAntiAlias(true);
paint.setTextSize(textSize);
paint.setTextAlign(Align.LEFT);
paint.setStrokeWidth(8.0f);
paint.setStrokeJoin(Paint.Join.ROUND);
paint.setStrokeCap(Paint.Cap.ROUND);
paint.setColor(Color.WHITE);
paint.setStyle(Paint.Style.STROKE);
Rect textBounds = new Rect();
paint.getTextBounds(text, 0, text.length(), textBounds);
int posX = 0;
int posY = getHeight() / 2 + textBounds.height() / 2 - textBounds.bottom;
canvas.drawText(text, posX, posY, paint);
paint.setStrokeWidth(0);
paint.setColor(textColor);
paint.setStyle(Paint.Style.FILL);
canvas.drawText(text, posX, posY, paint);
}
}
#解説
##1. Canvasのクリア
canvas.drawColor(Color.TRANSPARENT, PorterDuff.Mode.CLEAR);
AppCompatTextViewのsuperでdrawしたものを全部削除します。super.drawは必須で削除できないためしょうがないです。
(AppCompatTextViewではなく、Viewを継承するという手もありますが、その場合Viewの高さの計算等自分でやらないといけない部分も増えます。)
##2. 情報の取得
String text = (String) getText();
int textColor = getCurrentTextColor();
int textSize = (int) getTextSize();
TextViewに既に設定されている情報を取得するだけで特別な事はありません。
##3. Paintの作成
Paint paint = new Paint();
paint.setAntiAlias(true);
paint.setTextSize(textSize);
paint.setTextAlign(Align.LEFT);
setAntiAlias(true)
してないと文字がギザギザになってしまいます。その他は特別なことは無し。
##4. フチ部分の描画用設定
paint.setStrokeWidth(8.0f);
paint.setStrokeJoin(Paint.Join.ROUND);
paint.setStrokeCap(Paint.Cap.ROUND);
paint.setColor(Color.WHITE);
paint.setStyle(Paint.Style.STROKE);
まずは、フチ部分を描画し、後から文字部分を重ねて描画します。
setStrokeWidth
はフチの幅です。適当な値をセットします(たぶん4ぐらいからがちょうどいいと思います)。
setStrokeJoin
とsetStrokeCap
でフチに丸みを帯びさせます。実際文字はかなり小さいのでパッと見よくわからないですが、気持ち丸くなります。丸くしたくない場合は両方共Paint.Join.SQUARE
を選びます。恐らくですが、Joinの方はつなぎ部分だけで、Capが文字の端になると思われます(名前からの推測だけで実際どうかわからないです・・・)。
setColor
は文字通り、フチの色になります。今回は白です。
setStyle
はフチのみ(STROKE)、文字のみ(FILL)、フチと文字両方(FILL_AND_STROKE)があります。今回はフチだけ描画できればいいので、STROKEを設定します(FILL_AND_STROKEでも大丈夫なはずです)。
##5. フチ部分の描画
Rect textBounds = new Rect();
paint.getTextBounds(text, 0, text.length(), textBounds);
int posX = 0;
int posY = getHeight() / 2 + textBounds.height() / 2 - textBounds.bottom;
canvas.drawText(text, posX, posY, paint);
ここまででできたPaintオブジェクトを使ってdrawTextしますが、そのための描画位置を計算します。posXとposYは__描画位置の左下__の位置になります。Xは今回は0からの描画ですが、YはViewの高さによって変更する必要があります。
まず、該当のPaintオブジェクトを使った場合に描画で使用される範囲をgetTextBounds()
で計算します。このメソッドで取れるRectは、(0,0)を左下として描画した場合の座標位置です。よって文字は上側に描画されるため、textBounds.topの値はマイナスになるはずです。
描画位置の計算ですが、今回はViewの縦の真ん中(ようはgravityにcenter_verticalを指定したような形式)に描画します。そこで、Viewの真ん中(getHeight() / 2)を求め、文字の真ん中分ずらします(textBounds.height() / 2)。そして、最後にtextBounds.bottomを引きます。これは実際にやってもらうとわかると思いますが、(0,0)から描画をしているのにbottomは0以上の数字になると思います。詳しくはよくわかりませんが、恐らくTextViewのincludeFontPaddingで高さがかわるのと同じような感じじゃないかと推測しています。何にせよ下に少しずれているので、その分を引いてあげることで位置を調整します。
※実際これも計算が完全じゃない気がします。なんか他のViewと並べたりすると少しずれてる・・・気がする。
以上で計算した値を使って最終的にdrawTextで描画します。
##6. 文字部分の描画
paint.setStrokeWidth(0);
paint.setColor(textColor);
paint.setStyle(Paint.Style.FILL);
canvas.drawText(text, posX, posY, paint);
あとはほとんど先程の繰り返しです。フチを描画するために使った、Strokeの太さを0に戻し、文字の色を設定し、StyleでFILLを指定します。
posX、posYは変更せずにdrawTextを行うと、綺麗に上にかぶさったように描画されます。
#最後に
今回のはテキストが1行の場合のみ有効です(たぶん)。あと他にも制限が色々あるかもしれません。今回は自作したかったのでこういう形にしましたが、こちらはStackOverflowで紹介されてたので無難にこういうのを使ったほうがいいかもしれません(私は未使用ですのであしからず)。
https://github.com/m5/MagicTextView