一、效果及源代码
参考了网上几种实现方式,采用了在原有TextView的基础上增加一个新的TextView的方式,同时将新的TextView的paint设置为描边模式,支持Html、Span等内容。
文本效果如下
使用setStroke设置描边的宽度和颜色,必须使用setText2设置TextView的颜色,完整代码如下。
public class StrokeTextView extends AppCompatTextView {
private TextView borderText;///用于描边的TextView
TextPaint tp1; //borderText的Paint
private int mStrokeColor = Color.BLACK;
private int strokeWidth = 2;
public StrokeTextView(Context context){
super(context);
borderText = new TextView(context);
borderText.setTypeface(Typeface.defaultFromStyle(Typeface.BOLD));
borderText.setTextColor(this.mStrokeColor); //设置描边颜色
borderText.setGravity(getGravity());
tp1 = borderText.getPaint();
tp1.setStrokeWidth(this.strokeWidth); //设置描边宽度
tp1.setStyle(Paint.Style.STROKE); //设置画笔样式为描边
tp1.setStrokeJoin(Paint.Join.ROUND); //连接方式为圆角
}
//设置描边的颜色和宽度
public void setStroke(int color,int strokeWidth) {
tp1.setStrokeWidth(strokeWidth);
borderText.setTextColor(color); //设置描边颜色
}
@Override
public void setLineSpacing(float add, float mult) {
super.setLineSpacing(add, mult);
borderText.setLineSpacing(add, mult);
}
@Override
public void setMaxWidth(int maxPixels) {
super.setMaxWidth(maxPixels);
borderText.setMaxWidth(maxPixels);
}
public void setLayoutParams(ViewGroup.LayoutParams params) {
super.setLayoutParams(params);
borderText.setLayoutParams(params);
}
@Override
public void setGravity(int gravity) {
super.setGravity(gravity);
borderText.setGravity(gravity);
}
@Override
public void setPadding(int left, int top, int right, int bottom) {
super.setPadding(left, top, right, bottom);
borderText.setPadding(left,top,right,bottom);
}
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
borderText.measure(widthMeasureSpec, heightMeasureSpec);
}
@Override
public void setTextSize(int unit, float size) {
super.setTextSize(unit, size);
borderText.setTextSize(unit,size);
}
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
super.onLayout(changed, left, top, right, bottom);
borderText.layout(left, top, right, bottom);
}
//必须使用这个方法设置字体
public void setText2(CharSequence text) {
setText(text);
borderText.setText(getText().toString());
}
protected void onDraw(Canvas canvas) {
borderText.draw(canvas);
super.onDraw(canvas);
}
}
二、原理分析
这周接到一个新的需求--所有的文本都要有描边,我心想,这种功能应该很简单吧,网上肯定有很多现成的例子,没想到复制粘贴了一大堆,找了一堆开源库,最终竟然没找到一个好用的,不是位置不对,就是颜色不对,没办法,只能自己研究一下了。
首先网上主要有两种实现方法
- 一种是使用在TextView的onDraw方法中,用反射的方式更改paint的颜色(
在onDraw中直接调用setTextColor会引起多次invalidate,对性能影响非常大,绝对要避免这样使用), - 一种是采用新增一个TextView,将其的paint设置为描边,因为描边的字体会比原来的字体大,两个叠加在一起就有描边的效果了。 这里采用第二种方法,以下代码设置画笔属性为描边。
tp1 = borderText.getPaint();
tp1.setStyle(Paint.Style.STROKE); //设置画笔样式为描边
tp1.setStrokeJoin(Paint.Join.ROUND); //连接方式为圆角
tp1.setStrokeWidth(this.strokeWidth); //设置描边宽度
经过研究,网上的其他写法导致描边和文本出现的错位的原因,是因为描边的文本和原有本文的各种属性不一致导致的,我们要维持两个布局属性完全相同的TextView,所以要重写onMeasure、onLayout、setPadding等方法。
如果代码中动态设置了其他属性,务必重写该方法或者写一个新的方法,保证strokeTextView的属性一致。
FAQ
-
网上有一种方法是在onMeasure中判断两者text是否相同,不同则重新设置,
但是如果将TextView设置为固定宽高,则TextView并不会触发onMeasure流程。 -
为什么要使用
borderText.setText(getText().toString());,而不是borderText.setText(getText());
对于使用HTML或者SpannableStringBuilder设置了文本的颜色,则getText得到的内容会包含这些信息,导致描边颜色变为和文本一样的颜色,最终看上去的是加粗的效果,所以一定要使用getText().toString()去除这些颜色信息。
- 进阶优化 为了维持两者文本的一致性,可以重写onTextChanged方法,当文本发生改变的时候,自动重新设置描边的文字
@Override
protected void onTextChanged(CharSequence text, int start, int lengthBefore, int lengthAfter) {
super.onTextChanged(text, start, lengthBefore, lengthAfter);
if (bgText != null) {
CharSequence tt = bgText.getText();
if (tt == null || !tt.equals(this.getText())) {
bgText.setText(getText());
this.postInvalidate();
}
}
}