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 + "");
}

打印的日志:

01.png

当字符串计算量超大时,使用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);
}

效果:

22.png

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);
}

初始效果:

03.png

在start、end索引处插入字符串,效果

04.png

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());

效果:

5.png

可以重写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);
}

效果:

06.png

4、 StrikethroughSpan 删除线(中划线)

public void func7(){
    SpannableString ss = new SpannableString(text);
    ss.setSpan(new StrikethroughSpan(), 2, 4, Spanned.SPAN_EXCLUSIVE_INCLUSIVE);
    tv.setText(ss);
}

效果:

07.png

5、 UnderlineSpan 下划线

public void func8(){
    SpannableString ss = new SpannableString(text);
    ss.setSpan(new UnderlineSpan(), 2, 4, Spanned.SPAN_EXCLUSIVE_INCLUSIVE);
    tv.setText(ss);
}

效果:

08.png

6、ImageSpan 图片

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);
}

效果:

09.png

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);
}

效果:

010.png

8、 URLSpan 文本超链接

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());

效果:

011.png

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

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());
}

效果

012.png

对于如下面这样的内容,用这个在合适不过了,可以避免定义过多的TextView,当需求增加要显示另一种消息的格式时,不用再去定义一种XML布局。

012.png

TextUtils

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]);
}

打印的结果:

013.png

Html

对于这个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));
}

效果:

014.png
@Deprecated
public static Spanned fromHtml(String source) {
}

public static Spanned fromHtml(String source, int flags) {
}

@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) {
        mTagHandler.handleTag(false, tag, mSpannableStringBuilder, mReader);
    }
}

示例代码:https://github.com/jinxiyang/StringTips

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,445评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,889评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,047评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,760评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,745评论 5 367
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,638评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,011评论 3 398
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,669评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,923评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,655评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,740评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,406评论 4 320
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,995评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,961评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,197评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,023评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,483评论 2 342

推荐阅读更多精彩内容