TextView自定义加粗
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节点下设置该属性
<TextView
android: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 = "http://schemas.android.com/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>>();
@Override
public View onCreateView(View parent, String name, Context context, AttributeSet attrs) {
return onCreateView(name, context, attrs);
}
@Override
public 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, AttributeSet
attrs) {
//如果包含 . 则不是SDK中的view 可能是自定义view包括support库中的View
if (-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, AttributeSet
attrs) {
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
@Override
protected void onCreate(Bundle savedInstanceState) {
LayoutInflaterCompat.setFactory2(getLayoutInflater(), CustomFactory());
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}