黑马程序员技术交流社区

标题: 社交软件里的SpannableString的使用--实现类似微博的信息流展示 [打印本页]

作者: 丁佼    时间: 2016-1-26 10:01
标题: 社交软件里的SpannableString的使用--实现类似微博的信息流展示
本帖最后由 丁佼 于 2016-1-26 10:24 编辑

简介:社交软件里常见的emoji表情、@联系人等功能,可以在一个TextView里处理图片显示和文字点击等复杂的行为,通过此文档可以掌握该功能的实现方法。
1.     基本知识
1.1.   SpannableString(复合字符串)
该类的对象可被设置为TextView的正文,在显示原有文本的基础上,增加多种富文本特性。
// 基本方法介绍
// 1. 构造方法里接收一个字符串作为基本数据
SpannableString span = new SpannableString(str);
// 2. 为指定区间的字符串增加特殊属性
span.setSpan(object,start,end,flags);
// 3. 设置复合字符串为TextView的正文
tv_arties.setText(span);
1.2.   CharacterStyle(字符格式)的子类
在上面的setSpan方法可以看到需要一个object作为参数,该参数即为CharacterStyle的子类,有文本的前景颜色、点击、背景色、图片、下划线等格式可供使用。
2.     可点击文本,如 #话题#@联系人 的处理
2.1.   先上代码
下面的代码使得字符串里的“只是”两个字符可被点击,点击时会弹出Toast提示。


// 初始文本数据
String str = "我只是一个表情而已 (~ o ~)~zZ";
// 参数1 创建可点击的Span
ClickableSpan clickableSpan= new ClickableSpan() {
    @Override
    public void onClick(View widget) {
        Toast.makeText(MainActivity.this, "\'只是\'被点击了~\',可以跳转界面或者开启数据加载", Toast.LENGTH_SHORT).show();
    }
};
// 参数2 可点击字符的开始位置
int start = 1;
// 参数3 可点击字符的终止位置
int end = 3;
// 参数4 选择点击字符范围的方式
int flags = SpannableString.SPAN_EXCLUSIVE_EXCLUSIVE;
// 参数5
MovementMethod movementMethod = LinkMovementMethod.getInstance();

// 基本使用方法
// 1. 构造方法里接收一个字符串作为基本数据
SpannableString span = new SpannableString(str);
// 2. 为指定区间的字符串增加特性
span.setSpan(clickableSpan,start,end,flags);
// 3. 设置复合字符串为TextView的正文
tv_arties.setText(span);
// 4. 设置点击事件的分配,以处理TextView内可能存在的多个ClickSpan
tv_arties.setMovementMethod(movementMethod);
下面分析代码里的几个参数
2.1.1. str是用于显示的原始字符串
2.1.2. 参数1ClickableSpan对象
声明被修饰的字符串可以被点击。被点击时会回调onClick方法,要跳转界面还是要弹个Toast,根据业务需求变化。
2.1.3. 参数2start
该值表示要设置为clickspan的字符串起始位置,最小值为0.
2.1.4. 参数3end
该值表示要设置为clickspan的字符串终止位置,最大值为文本的length.
2.1.5. 参数4flags
该值用于说明,当被选中文字前后新增内容,新的字符是否受span影响。从使用来看,只在使用EditText的时候才会产生影响。信息流展示只要使用SPAN_EXCLUSIVE_EXCLUSIVE 即可。
具体的值有:
SpannableString.SPAN_EXCLUSIVE_EXCLUSIVE,在start和end区间内新增字符才受span影响
SpannableString.SPAN_EXCLUSIVE_INCLUSIVE,在start前的新增字符不受span影响,在end后新增字符受span影响
SpannableString.SPAN_INCLUSIVE_EXCLUSIVE,在start前的新增字符受span影响,在end后新增字符不受span影响
SpannableString.SPAN_INCLUSIVE_INCLUSIVE,在start或end前后新增字符都受span影响
2.1.6. 参数5MovementMethod对象
用于分析TextView使用的所有span,在TextView发生touch事件的时候会先交由movementMethod对象判断是否有CLickableSpan需要被处理。此参数必须设置,否则ClickableSpan的onClick方法不会被回调。

2.png (18.36 KB, 下载次数: 16)

2.png

3.png (19.85 KB, 下载次数: 27)

3.png

4.png (12.3 KB, 下载次数: 14)

4.png

作者: 丁佼    时间: 2016-1-26 10:02
本帖最后由 丁佼 于 2016-1-26 10:24 编辑

2.2.   匹配#话题#@联系人,并提供点击响应。
2.2.1. 使用正则表达式确定startend位置
从之前的代码可以看到,设置文字点击监听的代码是固定不变的,比较费脑的是怎么确定start和end的位置。#话题#和@联系人都是固定格式的字符串,查找固定格式的字符串应使用正则表达式来处理。上代码:

/**
* 将字符串内容的#话题#设置为可点击,并放入TextView</br>
* 搜索关键字:SpannableString,ClickableSpan</br>
* @param msgSpan 需要传入SpannableString对象,此方法使用完后还要继续添加@联系人的的点击处理,要共用同一个对象
*/
public static void setTopicSpan(TextView textView, SpannableString msgSpan) {
    // 参数1 #话题#使用的正则表达式,正则验证网站 : http://tool.chinaz.com/regex/
    String topicPatternStr = "#(.+?)#";

    // 参数2 使用正则表达式创建查询对象
    Pattern topicPattern = Pattern.compile(topicPatternStr);

    // 参数3 使用正则查找出msgSpan里所有的话题
    Matcher matcher = topicPattern.matcher(msgSpan);
    while (matcher.find()) {
        // matcher.group() 的返回值为匹配出来的字符串
        String topicName = matcher.group();
        Log.e(TAG, "start=" + matcher.start() + ";end=" + matcher.end()
                + ";话题为:=" + topicName);

        // 自定义的ClickableSpan,构造方法里接收话题字符串
        ClickableSpan clickableSpan = new TopicClickableSpan(topicName);
        // 设置点击
        msgSpan.setSpan(clickableSpan, matcher.start(), matcher.end(),
                SpannableString.SPAN_INCLUSIVE_EXCLUSIVE);
    }

    textView.setText(msgSpan);
    textView.setMovementMethod(LinkMovementMethod.getInstance());
}
2.2.2. 参数1,,正则表达式的查询规则
本文不做讲解,有需要请自行搜索“正则表达式”。
2.2.3. 参数2,参数3PatternMatcher
都是JDK里用于处理正则表达式的类,使用方法是固定的,可以参见代码注释。
2.2.4. TopicClickableSpan
继承自ClickableSpan,由于ClickableSpan的onClick方法参数为View,无法区分出被点击的span,需要在构造方法里传递话题字符串以供区分不同的话题。
private static class TopicClickableSpan extends ClickableSpan {

    private String topic;

    /** 由于ClickableSpan的onClick方法参数为View,无法区分出被点击的span,只能在构造方法里传递话题字符串以供区分 */
    private TopicClickableSpan(String topic){
        this.topic = topic;
    }

    public void onClick(View widget) {
        Toast.makeText(widget.getContext(), "被点击的话题是:"+topic, 1).show();
    }
};
2.2.5. @联系人的点击处理
更换正则表达式和自定义的ClickableSpan即可。
3.     emoji表情的显示处理
3.1.   ImageSpan
可以将文字替换为图片显示,接收的图片可以是资源id也可以是Bitmap。
3.2.   Emoji表情
从服务器传过来的只是字符串,但是应该具备类似[/吓死]或者/xs这样的特殊格式,客户端通过正则表达式确定表情字符串的start和end位置,将文字转换为图片并显示到文本框。


int emojiResId = EmojiList.findEmoji(emojiName);
if (emojiResId != -1) {
    // 参数4 如果查询到图片则将SpannableString里表情文字替换为图片
    ImageSpan emojiSpan = new ImageSpan(textView.getContext(), emojiResId);
    msgSpan.setSpan(emojiSpan, matcher.start(), matcher.end(), SpannableString.SPAN_INCLUSIVE_EXCLUSIVE);
}
3.3.   EmojiList
根据匹配到的表情名称查找出来的对应图片id。用于封装所有的表情资源
4.     将超链接转换为图片,并提供点击处理
通过正则表达式匹配到URL的start和end位置,并且同时设置ImageSpan和ClickableSpan到该段字符上,使得该段字符串同时具备两种特性


msgSpan.setSpan(httpPicSpan, start, end, SpannableString.SPAN_INCLUSIVE_EXCLUSIVE);
msgSpan.setSpan(clickableSpan,start,end, SpannableString.
SPAN_EXCLUSIVE_EXCLUSIVE);


作者: 丁佼    时间: 2016-1-26 10:04
本帖最后由 丁佼 于 2016-1-26 10:22 编辑

占位




欢迎光临 黑马程序员技术交流社区 (http://bbs.itheima.com/) 黑马程序员IT技术论坛 X3.2