Android开发中字符串的使用技巧

在开发Android应用中,常用的控件就是TextView、Button、EditText等,打交道最多的就是文本字符串了。在此分享一些字符串显示、处理的一些技巧。

String

String 字符串,我们应用中展示的文本信息,大多是以String的形式展示的。

CharSequence

细心的朋友留意到,TextView的setText(CharSequence text)的参数、getText()返回的值,不是String类型的,而是CharSequence类型的。那CharSequence是什么呢?它和String又有什么关系呢?

CharSequence,字面翻译即字符序列,也就是字符串。CharSequence,它是一个接口interface。

public interface CharSequence {
    //获取字符串长度
    int length();
    //返回指定索引出的字符
    char charAt(int var1);
    //截取指定索引区间的子字符串
    CharSequence subSequence(int var1, int var2);
    //转为String类型
    String toString();
    default IntStream chars() {
        throw new RuntimeException("Stub!");
    default IntStream codePoints() {
        throw new RuntimeException("Stub!");

CharSequence是一个接口,那String一定是继承了CharSequence,看一下源码:

    public final class String implements java.io.Serializable, Comparable<String>, CharSequence {

原来CharSequence是一个接口,String实现了这个接口。

StringBuffer、StringBuilder

StringBuffer、StringBuilder这两个比较相似,都是字符串缓冲区,用于处理字符串的拼接append、插入insert操作,都实现了CharSequence接口,TextView的setText方法可以直接使用。在拼接字符串上面都比String之间的“+”操作快,尤其是大量的字符串拼接操作。

private void func3() {
    String str = "123";
    long startTime_1 = new Date().getTime();
    for (int i = 0; i < 100000; i ++){
        str += "1";
    logTimeInterval("String", startTime_1);
    StringBuffer sf = new StringBuffer("123");
    long startTime_2 = new Date().getTime();
    for (int i = 0; i < 100000; i ++){
        sf.append("1");
    logTimeInterval("StringBuffer", startTime_2);
    StringBuilder sb = new StringBuilder("123");
    long startTime_3 = new Date().getTime();
    for (int i = 0; i < 100000; i ++){
        sb.append("1");
    logTimeInterval("StringBuilder", startTime_3);
private void logTimeInterval(String tag, long startTime){
    long interval = new Date().getTime() - startTime;
    Log.i(tag, interval + "");

打印的日志:

当字符串计算量超大时,使用StringBuffer、StringBuilder,显著的优点计算速度快、内存开销小。

以上是StringBuffer、StringBuilder的相同点,他们的不同点有:

  • StringBuilder比StringBuffer速度快
  • StringBuffer线程安全,可将字符串缓冲区安全地用于多个线程,不需要额外的同步用于多线程中。StringBuilder线程不安全。
  • SpannableString

    SpannableString,可以理解为带样式的字符串,它也实现了CharSequence接口

    public class SpannableString extends SpannableStringInternal implements CharSequence, GetChars, Spannable{
    

    那都可以带哪些样式呢?

  • BackgroundColorSpan 背景色
  • ClickableSpan 文本可点击,有点击事件
  • ForegroundColorSpan 文本颜色(前景色)
  • MaskFilterSpan 修饰效果,如模糊(BlurMaskFilter)、浮雕(EmbossMaskFilter)
  • MetricAffectingSpan 父类,一般不用
  • RasterizerSpan 光栅效果
  • StrikethroughSpan 删除线(中划线)
  • SuggestionSpan 相当于占位符
  • UnderlineSpan 下划线
  • AbsoluteSizeSpan 绝对大小(文本字体)
  • DynamicDrawableSpan 设置图片,基于文本基线或底部对齐。
  • ImageSpan 图片
  • RelativeSizeSpan 相对大小(文本字体)
  • ReplacementSpan 父类,一般不用
  • ScaleXSpan 基于x轴缩放
  • StyleSpan 字体样式:粗体、斜体等
  • SubscriptSpan 下标(数学公式会用到)
  • SuperscriptSpan 上标(数学公式会用到)
  • TextAppearanceSpan 文本外貌(包括字体、大小、样式和颜色)
  • TypefaceSpan 文本字体
  • URLSpan 文本超链接
  • 1、 BackgroundColorSpan 背景色

    public void func4(){
        SpannableString ss = new SpannableString(text);
        ss.setSpan(new BackgroundColorSpan(Color.BLUE), 2, 4, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
        tv.setText(ss);
    

    SpannableString的setSpan方法:

    public void setSpan(Object what, int start, int end, int flags) {
        super.setSpan(what, start, end, flags);
    

    object what :对应的各种Span,后面会提到;

    int start:开始应用指定Span的位置,索引从0开始

    int end:结束应用指定Span的位置,特效并不包括这个位置。比如如果这里数为3(即第4个字符),第4个字符不会有任何特效。从下面的例子也可以看出来。

    int flags:取值有如下四个

    Spannable.SPAN_EXCLUSIVE_EXCLUSIVE:前后都不包括,即在指定范围的前面和后面插入新字符都不会应用新样式

    Spannable.SPAN_EXCLUSIVE_INCLUSIVE:前面不包括,后面包括。即仅在范围字符的后面插入新字符时会应用新样式

    Spannable.SPAN_INCLUSIVE_EXCLUSIVE:前面包括,后面不包括。

    Spannable.SPAN_INCLUSIVE_INCLUSIVE:前后都包括。

    当给TextView的子串设置样式时,这些都一样的。只有当子串的内容改变时,flag才会影响插入字符在start、end索引处的效果,其实就是对EditText设置的有用。

    public void func1(){
        SpannableString ss = new SpannableString(text);
        ss.setSpan(new ForegroundColorSpan(Color.BLUE), 2, 4, Spanned.SPAN_EXCLUSIVE_INCLUSIVE);
        et.setText(ss);
    

    初始效果:

    2、 ClickableSpan 文本可点击,有点击事件

    public void func5(){
        SpannableString ss = new SpannableString(text);
        ClickableSpan span = new ClickableSpan() {
            @Override
            public void onClick(View view) {
                Toast.makeText(MainActivity.this, "ClickableSpan", Toast.LENGTH_LONG).show();
        ss.setSpan(span, 2, 4, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
        tv.setText(ss);
        tv.setMovementMethod(LinkMovementMethod.getInstance());
    

    PS.如果不设置会不起作用setMovementMethod(LinkMovementMethod.getInstance());

    可以重写ClickableSpan类来去掉下划线样式,以及改变点击后的字体颜色,有兴趣的可以自己试试。

    3、 ForegroundColorSpan 文本颜色(前景色)

    public void func6(){
        SpannableString ss = new SpannableString(text);
        ss.setSpan(new ForegroundColorSpan(Color.BLUE), 2, 4, Spanned.SPAN_EXCLUSIVE_INCLUSIVE);
        tv.setText(ss);
    

    4、 StrikethroughSpan 删除线(中划线)

    public void func7(){
        SpannableString ss = new SpannableString(text);
        ss.setSpan(new StrikethroughSpan(), 2, 4, Spanned.SPAN_EXCLUSIVE_INCLUSIVE);
        tv.setText(ss);
    
    public void func8(){
        SpannableString ss = new SpannableString(text);
        ss.setSpan(new UnderlineSpan(), 2, 4, Spanned.SPAN_EXCLUSIVE_INCLUSIVE);
        tv.setText(ss);
    
    public void func9(){
        SpannableString ss = new SpannableString(text);
        Drawable d = getResources().getDrawable(R.mipmap.ic_launcher);
        d.setBounds(0, 0, d.getIntrinsicWidth(), d.getIntrinsicHeight());
        ImageSpan span = new ImageSpan(d, DynamicDrawableSpan.ALIGN_BASELINE);
        ss.setSpan(span, 2, 4, Spanned.SPAN_EXCLUSIVE_INCLUSIVE);
        tv.setText(ss);
    

    7、 SuperscriptSpan 上标(数学公式会用到)

    public void func10(){
        SpannableString ss = new SpannableString(text);
        SuperscriptSpan superscriptSpan = new SuperscriptSpan();
        ss.setSpan(superscriptSpan, 2, 4, Spanned.SPAN_EXCLUSIVE_INCLUSIVE);
        tv.setText(ss);
    
    public void func11(){
        SpannableString ss = new SpannableString(text);
        URLSpan urlSpan = new URLSpan("http://www.baidu.com");
        ss.setSpan(urlSpan, 2, 4, Spanned.SPAN_EXCLUSIVE_INCLUSIVE);
        tv.setText(ss);
        tv.setMovementMethod(new LinkMovementMethod());
    

    PS.不要忘了setMovementMethod(new LinkMovementMethod());

    举得是一些常用的例子,其他的如果感兴趣可以自己试试,用法都是类似的。

    SpannableStringBuilder

    SpannableString与SpannableStringBuilder和String与StringBuilder类似,SpannableStringBuilder可以拼接各种的SpannableString,使字符串具有多种样式。而且这两个都实现了CharSequence接口,TextView的setText方法可以直接使用。

    public void func12(){
        SpannableString ss1 = new SpannableString(text);
        SuperscriptSpan superscriptSpan = new SuperscriptSpan();
        ss1.setSpan(superscriptSpan, 2, 4, Spanned.SPAN_EXCLUSIVE_INCLUSIVE);
        SpannableString ss2 = new SpannableString(text);
        URLSpan urlSpan = new URLSpan("http://www.baidu.com");
        ss2.setSpan(urlSpan, 2, 4, Spanned.SPAN_EXCLUSIVE_INCLUSIVE);
        SpannableStringBuilder ssb = new SpannableStringBuilder();
        ssb.append(ss1);
        ssb.append(ss2);
        tv.setText(ssb);
        tv.setMovementMethod(new LinkMovementMethod());
    

    TextUtils 文本辅助类,位于android.text.TextUtils,Android提供的专门用于处理字符串文本的。

    该类下常用的方法:

    public void func13(){
        Log.d("===", "---------------------------------");
        //字符串拼接  
        Log.d("===", TextUtils.concat("Hello", " ", "world!").toString());
        //判断是否为空字符串  
        Log.d("===", TextUtils.isEmpty("Hello") + "");
        //判断是否只有数字  
        Log.d("===", TextUtils.isDigitsOnly("Hello") + "");
        //判断字符串是否相等  
        Log.d("===", TextUtils.equals("Hello", "Hello") + "");
        //获取字符串的倒序  
        Log.d("===", TextUtils.getReverse("Hello", 0, "Hello".length()).toString());
        //获取字符串的长度  
        Log.d("===", TextUtils.getTrimmedLength("Hello world!") + "");
        Log.d("===", TextUtils.getTrimmedLength("  Hello world!  ") + "");
        //获取html格式的字符串 , 将<、>、\、空格……转为html定义的 <  >等
        Log.d("===", TextUtils.htmlEncode("<html>\n" +
                "<body>\n" +
                "这是一个非常简单的HTML。\n" +
                "</body>\n" +
                "</html>"));
        //获取字符串中第一次出现子字符串的字符位置  
        Log.d("===", TextUtils.indexOf("Hello world!", "Hello") + "");
        //截取字符串  
        Log.d("===", TextUtils.substring("Hello world!", 0, 5));
        //通过表达式截取字符串  
        Log.d("===", TextUtils.split("  Hello world!  ", " ")[0]);
    

    打印的结果:

    对于这个html文本我们要怎么显示呢?

    <p>用户就诊须知:</p><p>1.用户添加就诊人,就诊卡请确保其内容真实性!</p><p>2.添加的就诊人与其就诊卡为仁济医院真实存在。</p><p>3.不支持初次就诊。</p><p>4.一个账号最多只能绑定两个就诊人。</p>
    

    用WebView显示,大材小用,不好。Android提供了一个Html类,专门用于处理,这个附带html样式的文本,支持的标签也很多,使用也很简单。

    先看怎么使用:

    public void func14(){
        String text = "<p>用户就诊须知:</p><p>1.用户添加就诊人,就诊卡请确保其内容真实性!</p><p>2.添加的就诊人与其就诊卡为仁济医院真实存在。</p><p>3.不支持初次就诊。</p><p>4.一个账号最多只能绑定两个就诊人。</p>";
        tv.setText(Html.fromHtml(text));
    @Deprecated
    public static Spanned fromHtml(String source, Html.ImageGetter imageGetter, Html.TagHandler tagHandler) {
    public static Spanned fromHtml(String source, int flags, Html.ImageGetter imageGetter, Html.TagHandler tagHandler) {
    

    String source : html文本字符串

    int flags : 文本显示的模式(有很多种,有兴趣的可以试试)

    Html.ImageGetter imageGetter : 当html文本字符串带图片时,用来处理图片的加载,重写其方法即可,可以从应用内部、手机内存、网络加载图片。
    这里不再举例子,可参考这篇博客:Android中Textview显示带html文本二-------【Textview显示本地图片】

    Html.TagHandler tagHandler : html文本中可以自定义标签,自己使用这个自己解析。

    Spanned : 是一个接口,它继承了CharSequence接口,所以可以直接用于TextView

    查看Html的源码,可以知道Html支持解析的标签有:

    private void handleEndTag(String tag) {
        if (tag.equalsIgnoreCase("br")) {
            handleBr(mSpannableStringBuilder);
        } else if (tag.equalsIgnoreCase("p")) {
            endCssStyle(mSpannableStringBuilder);
            endBlockElement(mSpannableStringBuilder);
        } else if (tag.equalsIgnoreCase("ul")) {
            endBlockElement(mSpannableStringBuilder);
        } else if (tag.equalsIgnoreCase("li")) {
            endLi(mSpannableStringBuilder);
        } else if (tag.equalsIgnoreCase("div")) {
            endBlockElement(mSpannableStringBuilder);
        } else if (tag.equalsIgnoreCase("span")) {
            endCssStyle(mSpannableStringBuilder);
        } else if (tag.equalsIgnoreCase("strong")) {
            end(mSpannableStringBuilder, Bold.class, new StyleSpan(Typeface.BOLD));
        } else if (tag.equalsIgnoreCase("b")) {
            end(mSpannableStringBuilder, Bold.class, new StyleSpan(Typeface.BOLD));
        } else if (tag.equalsIgnoreCase("em")) {
            end(mSpannableStringBuilder, Italic.class, new StyleSpan(Typeface.ITALIC));
        } else if (tag.equalsIgnoreCase("cite")) {
            end(mSpannableStringBuilder, Italic.class, new StyleSpan(Typeface.ITALIC));
        } else if (tag.equalsIgnoreCase("dfn")) {
            end(mSpannableStringBuilder, Italic.class, new StyleSpan(Typeface.ITALIC));
        } else if (tag.equalsIgnoreCase("i")) {
            end(mSpannableStringBuilder, Italic.class, new StyleSpan(Typeface.ITALIC));
        } else if (tag.equalsIgnoreCase("big")) {
            end(mSpannableStringBuilder, Big.class, new RelativeSizeSpan(1.25f));
        } else if (tag.equalsIgnoreCase("small")) {
            end(mSpannableStringBuilder, Small.class, new RelativeSizeSpan(0.8f));
        } else if (tag.equalsIgnoreCase("font")) {
            endFont(mSpannableStringBuilder);
        } else if (tag.equalsIgnoreCase("blockquote")) {
            endBlockquote(mSpannableStringBuilder);
        } else if (tag.equalsIgnoreCase("tt")) {
            end(mSpannableStringBuilder, Monospace.class, new TypefaceSpan("monospace"));
        } else if (tag.equalsIgnoreCase("a")) {
            endA(mSpannableStringBuilder);
        } else if (tag.equalsIgnoreCase("u")) {
            end(mSpannableStringBuilder, Underline.class, new UnderlineSpan());
        } else if (tag.equalsIgnoreCase("del")) {
            end(mSpannableStringBuilder, Strikethrough.class, new StrikethroughSpan());
        } else if (tag.equalsIgnoreCase("s")) {
            end(mSpannableStringBuilder, Strikethrough.class, new StrikethroughSpan());
        } else if (tag.equalsIgnoreCase("strike")) {
            end(mSpannableStringBuilder, Strikethrough.class, new StrikethroughSpan());
        } else if (tag.equalsIgnoreCase("sup")) {
            end(mSpannableStringBuilder, Super.class, new SuperscriptSpan());
        } else if (tag.equalsIgnoreCase("sub")) {
            end(mSpannableStringBuilder, Sub.class, new SubscriptSpan());
        } else if (tag.length() == 2 &&
                Character.toLowerCase(tag.charAt(0)) == 'h' &&
                tag.charAt(1) >= '1' && tag.charAt(1) <= '6') {
            endHeading(mSpannableStringBuilder);
        } else if (mTagHandler != null) {