2020年12月24日木曜日

android:fontFamily="sans-serif-medium" の文字の太さを動的に変更する

android:fontFamily に "sans-serif" または "sans-serif-medium"、android:textStyle に "normal" または "bold" を指定したときに生成される Typeface の weight, style, isBold は次のようになります。 <TextView ... android:fontFamily="sans-serif" or "sans-serif-medium" android:textStyle="normal" or "bold" />
fontFamilytextStyleweightstyleisBold
sans-serifnormal4000false
sans-serifbold7001true
sans-serif-mediumnormal5000false
sans-serif-mediumbold8001true

Typeface の style は textStyle の設定が反映されます。style が 1 だと Typeface.BOLD, 2 だと Typeface.ITALIC, 3 だと Typeface.BOLD_ITALIC です。なので style が 1 のとき isBold が true になっています。

"sans-serif-medium" + normal はちょっと太いですが isBold は false です。


android:textStyle の値をプログラムから変更するには TextView.setTypeface() メソッドを使います。このメソッドには引数の異なる2種類があります。 public void setTypeface(@Nullable Typeface tf, @Typeface.Style int style) { if (style > 0) { if (tf == null) { tf = Typeface.defaultFromStyle(style); } else { tf = Typeface.create(tf, style); } setTypeface(tf); // now compute what (if any) algorithmic styling is needed int typefaceStyle = tf != null ? tf.getStyle() : 0; int need = style & ~typefaceStyle; mTextPaint.setFakeBoldText((need & Typeface.BOLD) != 0); mTextPaint.setTextSkewX((need & Typeface.ITALIC) != 0 ? -0.25f : 0); } else { mTextPaint.setFakeBoldText(false); mTextPaint.setTextSkewX(0); setTypeface(tf); } } public void setTypeface(@Nullable Typeface tf) { if (mTextPaint.getTypeface() != tf) { mTextPaint.setTypeface(tf); if (mLayout != null) { nullLayouts(); requestLayout(); invalidate(); } } } Typeface.Style を渡さない方では mTextPaint に Typeface をセットして invalidate しています。

Typeface.Style を渡す方では、最終的に setTypeface(@Nullable Typeface tf) を呼び出しています。

style が 0 より大きい場合、つまり bold, italic, bold_italic のいずれかのときは Typeface.defaultFromStyle() または Typeface.create() を使って style に対応する Typeface を生成します。 生成した Typeface が style に対応していない場合だけ FakeBold や TextSkewX がセットされます。

style が 0 (= Typeface.NORMAL)の場合、style に対応する Typeface を生成せず、渡された Typeface をそのままセットします。

これによりどういうことが起こるかと言うと、
"sans-serif-medium" + normal な TextView に setTypeface(tv.typeface, Typeface.BOLD) すると bold になるのに、
"sans-serif-medium" + bold な TextView に setTypeface(tv.typeface, Typeface.NORMAL) すると normal にならない!
のです。 mediumNormalTextView.setTypeface(mediumNormalTextView.typeface, Typeface.BOLD) mediumBoldTextView.setTypeface(mediumBoldTextView.typeface, Typeface.NORMAL)

これは "sans-serif-medium" + bold のときに生成される Typeface 自体が bold になっていて(FakeBold を使わない)、style が 0 (= Typeface.NORMAL)の場合は渡した bold な Typeface をそのままセットされてしまうからです。

これを防ぐには、Typeface.create() で Typeface.NORMAL を適用した Typeface を生成して setTypeface() に渡します。 val tf = Typeface.create(mediumBoldTextView.typeface, Typeface.NORMAL) mediumBoldTextView.setTypeface(tf, Typeface.NORMAL)



1 件のコメント: