TextView自定义加粗
1、目的2、三种加粗方法3、第三种方式额外问题以及解决方案3.1实现细节1、目的
android提供的几种加粗方法不满足我司ui设计的字体字重
2、三种加粗方法
设置TextView的textStyle为Bold,这种方式的textView很粗
xml:android:textStyle="bold"
代码:paint.setTypeface(Typeface.defaultFromStyle(Typeface.BOLD));
代码设置FakeBoldText,这种方式加粗过的比较接近medium效果
paint.setFakeBoldText(true)
如果以上都不满足ui小姐姐,我们还可以换种思路,TextView的加粗其实本质就是画笔paint的粗细,我们可以通过设置画笔的宽度来满足需求
paint.setStrokeWidth(8f);paint.setStyle(Paint.Style.FILL_AND_STROKE);
(设置Style为FILL_AND_STROKE的目的是:如果字体过大,填充模式为STROKE的话就会出现字画之间的漏洞,设置FILL不会有效果,如图)
STOKE模式:
FIll模式:
3、第三种方式额外问题以及解决方案
问题:
1、每个TextView都这样设置的话,重复代码太多
2、字重之间没有一个衡量单位标准
3、字号大小不同需要设置不同的StrokeWidth,如果不设置,小字号看起来很粗,大字号看起来没效果
解决方案:
1、继承LayoutFactory2(每个view创建的地方),在xml里面自定义设置一个额外属性,如果有地方需要自定义自重,使用该属性设置
2、与自家ui小姐姐商讨确定字重的等级集,比如我们公司就制定一共5个等级字重
3、根据字号以及等级,设计一套计算规则来计算出StrokeWidth
3.1实现细节
在xml中自定义属性textBoldStyle,枚举,定义了5个等级<!-- 系统TextView自定义额外属性 --><attr name="textBold" format="enum"><enum name="zero" value="0" /><enum name="one" value="1" /><enum name="two" value="2" /><enum name="three" value="3" /><enum name="four" value="4" /></attr>
在要用到的xml的文件TextVIew节点下设置该属性
<TextViewandroid:id="@+id/textView3"style="@style/TextVIewStyle"app:textBold="two" />
style属性:
<style name="TextVIewStyle"><item name="android:layout_width">match_parent</item><item name="android:layout_height">100dp</item><item name="android:gravity">center</item><item name="android:text">Hello World!你好世界!</item><item name="android:textSize">20sp</item></style>
重写LayoutFactory2,关键思想就是重写onCreateView,自己创建TextView以及TextView的子类(怎么重写?一个字,抄,抄系统源码),然后读取textBold属性,获取加粗等级,然后设计一套计算规则(我只是简单的根据字体dp进行等比放大缩小,效果还可以),得到设置画笔宽度的值
import android.content.Context;import android.content.res.TypedArray;import android.graphics.Paint;import android.graphics.Typeface;import android.text.TextUtils;import android.util.AttributeSet;import android.view.LayoutInflater;import android.view.View;import android.widget.TextView;import java.lang.reflect.Constructor;import java.util.HashMap;import java.util.Map;public class NightThemeInflaterFactory implements LayoutInflater.Factory2 {private Map<TextView, Integer> map = new HashMap<>();private static final String NAMESPACE_RES_AUTO = "/apk/res-auto";private static final String[] mClassPrefixList = {"android.widget.","android.webkit.","android.app.","android.view."};//记录对应VIEW的构造函数private static final Class<?>[] mConstructorSignature = new Class[]{Context.class, AttributeSet.class};private static final HashMap<String, Constructor<? extends View>> mConstructorMap =new HashMap<String, Constructor<? extends View>>();@Overridepublic View onCreateView(View parent, String name, Context context, AttributeSet attrs) {return onCreateView(name, context, attrs);}@Overridepublic View onCreateView(String name, Context context, AttributeSet attrs) {//创建系统的控件View view = createSDKView(name, context, attrs);if (null == view) {//创建非系统控件view = createView(name, context, attrs);}//如果是TextView或者是继承了TextView的子控件if (view instanceof TextView) {TextView textView = (TextView) view;//查找textBold属性(xml中直接定义的)int value = attrs.getAttributeIntValue(NAMESPACE_RES_AUTO, "textBold", -1);//查找textBold属性(写在style里面的)if (value == -1) {TypedArray typedArray = context.obtainStyledAttributes(R.style.TextVIewStyle, new int[]{R.attr.textBold});value = typedArray.getInt(0, -1);typedArray.recycle();}float density = getDensity(context);float textSize = textView.getTextSize();//将文字大小换算成dp属性,根据dp进行放大缩小系数int dp = (int) (textSize / density);//我的项目是以dp的1/10为最大的等级,最低等级是0即正常字体(不加粗),//然后在1/10的基础上再划分为4个等级float fullNums = dp / 20f;value = Math.min(value, 4);float targetLevel = fullNums * value;if (value > -1 && textView.getTypeface().getStyle() != Typeface.BOLD) {textView.getPaint().setStrokeWidth(targetLevel);textView.getPaint().setStyle(Paint.Style.FILL_AND_STROKE);map.put(textView, value);}}return view;}public void generateTvBold(float percent, Context context) {//我的项目是以dp的1/10为最大的等级,最低等级是0即正常字体(不加粗),//然后在1/10的基础上再划分为4个等级float density = getDensity(context);TextView textView = null;for (Map.Entry<TextView, Integer> entry : map.entrySet()) {textView = entry.getKey();float textSize = textView.getTextSize();int dp = (int) (textSize / density);float fullNums = dp / percent;int value = entry.getValue();float targetLevel = fullNums * value;textView.getPaint().setStrokeWidth(targetLevel);textView.getPaint().setStyle(Paint.Style.FILL_AND_STROKE);textView.invalidate();}}private View createSDKView(String name, Context context, AttributeSetattrs) {//如果包含 . 则不是SDK中的view 可能是自定义view包括support库中的Viewif (-1 != name.indexOf('.')) {return null;}//不包含就要在解析的 节点 name前,拼上: android.widget. 等尝试去反射for (int i = 0; i < mClassPrefixList.length; i++) {View view = createView(mClassPrefixList[i] + name, context, attrs);if (view != null) {return view;}}return null;}/*** @param name* @param context* @param attrs 反射创建view* @return*/private View createView(String name, Context context, AttributeSetattrs) {Constructor<? extends View> constructor = findConstructor(context, name);try {return constructor.newInstance(context, attrs);} catch (Exception e) {}return null;}private Constructor<? extends View> findConstructor(Context context, String name) {Constructor<? extends View> constructor = mConstructorMap.get(name);if (constructor == null) {try {Class<? extends View> clazz = context.getClassLoader().loadClass(name).asSubclass(View.class);constructor = clazz.getConstructor(mConstructorSignature);mConstructorMap.put(name, constructor);} catch (Exception e) {}}return constructor;}private float getDensity(Context context) {return context.getResources().getDisplayMetrics().density;}}
在activity的onCreate函数的setContentView之前设置使用我们自定义的layoutFactory2
@Overrideprotected void onCreate(Bundle savedInstanceState) {LayoutInflaterCompat.setFactory2(getLayoutInflater(), CustomFactory());super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);}