较粗糙的中划线TextView

3,085 阅读3分钟

为啥要写

主要最近有中划线TextView的需求,效果如下图:

需求图

历程1

乍一看,也就那么回事,因为android的TextView本身就可以支持中划线、下划线等: 具体方法为:

textview.getPaint().setFlags(Paint. STRIKE_THRU_TEXT_FLAG);

然后通过多个TextView的拼接达到效果。这是一闪而过的解决方案,也是最笨耦合最大的方法。

存在问题:

1.无故渲染多个Widget,从性能上不可取;
2.代码冗余;
3.拓展性差,很可能后面有支持其他样式的需求。

历程2

那就只能自定义了,继承TextView实现,利用画布、Paint绘制横线,需要 2 个信息:

  • 启始划线位置;
  • 结束划线位置

而计算这两个信息又需要 2 个信息

  • fullText,即显示的全部Text内容
  • targetText,要划横线的文本

这样的话,我们需要网自定义TextView传递这两个参数,但是存在问题:

  • 破坏了统一控件的设置接口;

  • 假如在画横线的基础上有额外的UI需求,如:变大,则传参变得复杂。

历程3

这时第一时间让我想到的是 SpannableString, 平时开发中经常会用来各种富文本需求,如:一段文字中间某几个字颜色/大小/点击 以及简单的图文混排。

但是呢,又不太一样,SpannableString的粒度是字符,但是我们可以利用SpannableString的对象结构来达到兼容效果。 首先来看一段设置字体大小的代码:

spannable.setSpan(object, position[0], position[1], Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);

object是样式对象,可以是ForegroundColorSpan也可以是AbsoluteSizeSpan等,理论上可以是任意的对象,甚至是 Object,无论这个对象有没有意义。他的一个作用是用来设置样式,另一个作用是用来获取position[0]position[1]的值.

SpannableStringInternal的方法

的缺,可以看到,可以通过object获取startend值。

由此我们自定义TextView如下:

public class CrossLineTextView extends AppCompatTextView{}

onDraw()方法:

    //计算start和end的值
    computeLineOffset();
    //绘制中划线
    drawCrossLine(canvas);

computeLineOffset()方法部分代码

SpannedString ss = (SpannedString) getText();
Paint paint = getPaint();
//下划线启始字符位置
int startIndex = ss.getSpanStart(this);
//下划线结束字符位置
int endIndex = ss.getSpanEnd(this);

我们还需要把启始位置转换成offset像素单位。

 String content = getText().toString();
        if(TextUtils.isEmpty(content)){
            return;
        }
        if(startIndex < 0 || endIndex <= startIndex || endIndex >= content.length()){
            return;
        }
        String prefix;
        if(startIndex == 0){
            prefix = "";
        }else{
            prefix = content.substring(0, startIndex);
        }

        String suffix = content.substring(startIndex, endIndex + 1);

    //别忘记加上padding
        start = paint.measureText(prefix) + getPaddingLeft();
        end   = start + paint.measureText(suffix);

drawCrossLine()方法

    private void drawCrossLine(@NonNull Canvas canvas){
        if(start != -1 && end != -1 && end > start){
            if(linePaint == null){
                linePaint = new Paint();
                linePaint.setColor(getPaint().getColor());
                linePaint.setStrokeWidth(lineWidth);
            }
            canvas.drawLine(start, getMeasuredHeight() * HEIGHT_PERCENT,
                            end, getMeasuredHeight() * HEIGHT_PERCENT, linePaint);
        }
    }

这就是核心(垃圾)代码。具体使用方法是:

    SpannableString crossSpanString = new SpannableString("我爱我的祖国,也爱那个祖国");
    crossSpanString.setSpan(tipTv, 4, 8, Spannable.SPAN_INCLUSIVE_EXCLUSIVE);
    tipTv.setText(crossSpanString);

就酱紫,我们甚至可以在crossSpanString上继续叠加各种其他效果,这样就可以不破坏原有统一控件的接口,不影响原有功能和使用。

总结

记录本文仅仅是分享今天工作的一点小想法和大家探讨,又或是闲来无事,希望能抛砖引玉。仅此而已!