2020年12月24日木曜日

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

android:fontFamily に "sans-serif" または "sans-serif-medium"、android:textStyle に "normal" または "bold" を指定したときに生成される Typeface の weight, style, isBold は次のようになります。
  1. <TextView  
  2.   ...  
  3.   android:fontFamily="sans-serif" or "sans-serif-medium"  
  4.   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種類があります。
  1. public void setTypeface(@Nullable Typeface tf, @Typeface.Style int style) {  
  2.     if (style > 0) {  
  3.         if (tf == null) {  
  4.             tf = Typeface.defaultFromStyle(style);  
  5.         } else {  
  6.             tf = Typeface.create(tf, style);  
  7.         }  
  8.   
  9.         setTypeface(tf);  
  10.         // now compute what (if any) algorithmic styling is needed  
  11.         int typefaceStyle = tf != null ? tf.getStyle() : 0;  
  12.         int need = style & ~typefaceStyle;  
  13.         mTextPaint.setFakeBoldText((need & Typeface.BOLD) != 0);  
  14.         mTextPaint.setTextSkewX((need & Typeface.ITALIC) != 0 ? -0.25f : 0);  
  15.     } else {  
  16.         mTextPaint.setFakeBoldText(false);  
  17.         mTextPaint.setTextSkewX(0);  
  18.         setTypeface(tf);  
  19.     }  
  20. }  
  21.   
  22. public void setTypeface(@Nullable Typeface tf) {  
  23.     if (mTextPaint.getTypeface() != tf) {  
  24.         mTextPaint.setTypeface(tf);  
  25.   
  26.         if (mLayout != null) {  
  27.             nullLayouts();  
  28.             requestLayout();  
  29.             invalidate();  
  30.         }  
  31.     }  
  32. }  
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 にならない!
のです。
  1. mediumNormalTextView.setTypeface(mediumNormalTextView.typeface, Typeface.BOLD)  
  2.   
  3. 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() に渡します。
  1. val tf = Typeface.create(mediumBoldTextView.typeface, Typeface.NORMAL)  
  2. mediumBoldTextView.setTypeface(tf, Typeface.NORMAL)  



1 件のコメント: