效果图
SpanUtils.java
package com.ican.subjects.spannable_string.utils;
import android.content.Context;
import android.text.Spannable;
import android.text.SpannableString;
import android.text.TextPaint;
import android.text.style.ClickableSpan;
import android.text.style.ForegroundColorSpan;
import android.view.View;
import com.ican.subjects.spannable_string.bean.Topic;
import com.ican.subjects.spannable_string.bean.User;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* Created by tuwenyuan on 2016/8/31.<br/>
* 描述:
* </br>
*/
public class SpanUtils{
public static class PatternString{
/**
* #号括起来的话题#
*/
public static final String TOPIC_PATTERN = "#[^#]+#";
/**
* 表情[大笑]
*/
public static final String EXPRESSION_PATTERN = "\\[[^\\]]+\\]";
/**
* 网址
*/
public static final String URL_PATTERN = "(([hH]ttp[s]{0,1}|ftp)://[a-zA-Z0-9\\.\\-]+\\.([a-zA-Z]{2,4})(:\\d+)?(/[a-zA-Z0-9\\.\\-~!@#$%^&*+?:_/=<>]*)?)|(www.[a-zA-Z0-9\\.\\-]+\\.([a-zA-Z]{2,4})(:\\d+)?(/[a-zA-Z0-9\\.\\-~!@#$%^&*+?:_/=<>]*)?)";
}
/**
*
* @param <T>
*/
public interface SpanClickListener<T>{
void onSpanClick(T t);
}
/**
* 关键词变色处理
* @param str
* @param patterStr 需要变色的关键词 或者 正则表达式
* @return
*/
public static SpannableString getKeyWordSpan(int color, String str, String patterStr) throws Exception {
SpannableString spannableString = new SpannableString(str);
Pattern patten = Pattern.compile(patterStr, Pattern.CASE_INSENSITIVE);
dealPattern(color, spannableString, patten, 0);
return spannableString;
}
/**
* 自动识别话题并做颜色处理,可点击
* @param color
* @param str
*/
public static SpannableString getTopicSpan(int color, String str, boolean clickable, SpanClickListener spanClickListener, Topic topic) throws Exception {
SpannableString spannableString = new SpannableString(str);
Pattern patten = Pattern.compile(PatternString.TOPIC_PATTERN, Pattern.CASE_INSENSITIVE);
if(clickable){
dealClick(spannableString, patten, 0, spanClickListener, topic);
}
dealPattern(color, spannableString, patten, 0);
return spannableString;
}
/**
* @用户 颜色处理、点击处理
* @param color 前景色
* @param str
* @param clickable 是否可点击
* @param spanClickListener
* @param atUsers
* @return
* @throws Exception
*/
public static SpannableString getAtUserSpan(int color, String str, boolean clickable, SpanClickListener spanClickListener, List<User> atUsers) throws Exception {
SpannableString spannableString = new SpannableString(str);
Pattern patten;
for (User u : atUsers) {
patten = Pattern.compile("@" + u.getName(), Pattern.CASE_INSENSITIVE);
if(clickable){
dealClick(spannableString, patten, 0, spanClickListener, u);
}
dealPattern(color, spannableString, patten, 0);
}
return spannableString;
}
/**
* 表情处理
* @param context
* @param str
* @return
*/
public static SpannableString getExpressionSpan(Context context, String str) throws Exception {
return ExpressionConvertUtil.getInstace().getExpressionString(context, str);
}
/**
* 对spanableString进行正则判断,如果符合要求,则将内容变色
* @param color
* @param spannableString
* @param patten
* @param start
* @throws Exception
*/
private static void dealPattern(int color, SpannableString spannableString, Pattern patten, int start) throws Exception {
Matcher matcher = patten.matcher(spannableString);
while (matcher.find()) {
String key = matcher.group();
// 返回第一个字符的索引的文本匹配整个正则表达式,ture 则继续递归
if (matcher.start() < start) {
continue;
}
// 计算该内容的长度,也就是要替换的字符串的长度
int end = matcher.start() + key.length();
//设置前景色span
spannableString.setSpan(new ForegroundColorSpan(color), matcher.start(), end, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
if (end < spannableString.length()) {
// 如果整个字符串还未验证完,则继续。。
dealPattern(color, spannableString, patten, end);
}
break;
}
}
/**
* 对spanableString进行正则判断,如果符合要求,将内容设置可点击
* @param spannableString
* @param patten
* @param start
* @param spanClickListener
* @param bean
*/
private static void dealClick(SpannableString spannableString, Pattern patten, int start, final SpanClickListener spanClickListener, final Object bean){
Matcher matcher = patten.matcher(spannableString);
while (matcher.find()) {
String key = matcher.group();
// 返回第一个字符的索引的文本匹配整个正则表达式,ture 则继续递归
if (matcher.start() < start) {
continue;
}
// 计算该内容的长度,也就是要替换的字符串的长度
int end = matcher.start() + key.length();
spannableString.setSpan(new ClickableSpan() {
@Override
public void onClick(View widget) {
spanClickListener.onSpanClick(bean);
}
@Override
public void updateDrawState(TextPaint ds) {
super.updateDrawState(ds);
//设置画笔属性
ds.setUnderlineText(false);//默认有下划线
}
}, matcher.start(), end, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
if (end < spannableString.length()) {
// 如果整个字符串还未验证完,则继续。。
dealClick(spannableString, patten, end, spanClickListener, bean);
}
break;
}
}
}
SpannableStringActivity.java
package com.ican.subjects.spannable_string;
import android.graphics.Color;
import android.text.SpannableString;
import android.text.method.LinkMovementMethod;
import android.widget.TextView;
import android.widget.Toast;
import com.ican.subjects.R;
import com.ican.subjects.base.BaseActivity;
import com.ican.subjects.spannable_string.bean.Topic;
import com.ican.subjects.spannable_string.bean.User;
import com.ican.subjects.spannable_string.utils.SpanUtils;
import java.util.ArrayList;
import java.util.List;
/**
* 创建者 涂文远
* 创建时间 2016/10/7 10:34
* 描述 ${TODO}
* <p/>
* 更新者 $Author$
* 更新时间 $Date$
* 更新描述 ${TODO}
*/
public class SpannableStringActivity extends BaseActivity {
private TextView tvColoredKeywd;
private TextView tvTopic;
private TextView tvTestAt;
private TextView tvExpression;
@Override
public int getLayout() {
return R.layout.activity_spannable_string;
}
@Override
protected void initView() {
tvColoredKeywd = (TextView) findViewById(R.id.tv_keyword_colored);
tvTopic = (TextView) findViewById(R.id.tv_topic);
tvTestAt = (TextView) findViewById(R.id.tv_test_at);
tvExpression = (TextView) findViewById(R.id.tv_text_expression);
}
@Override
protected void initData() {
testColoredKeywd();
testTopic();
textAtUsers();
textExpression();
}
/**
* 关键字变色
*/
private void testColoredKeywd() {
String string = "Android一词的本义指“机器人”,同时也是Google于2007年11月5日,Android logo相关图片,Android logo相关图片(36张)\n";
SpannableString cardText = null;
try {
cardText = SpanUtils.getKeyWordSpan(getResources().getColor(R.color.md_green_600), string, "Android");
} catch (Exception e) {
e.printStackTrace();
}
tvColoredKeywd.setText(cardText);
}
/**
* 测试话题
*/
private void testTopic() {
String topic = "#舌尖上的大连#四种金牌烤芝士吃法爱吃芝士的盆友不要错过了~L秒拍视频\n";
SpannableString topicText = null;
try {
topicText = SpanUtils.getTopicSpan(Color.BLUE, topic, true, new SpanUtils.SpanClickListener<Topic>() {
@Override
public void onSpanClick(Topic t) {
Toast.makeText(SpannableStringActivity.this, "点击话题:" + t.toString() , Toast.LENGTH_SHORT).show();
}
}, new Topic(1, "舌尖上的大连"));
} catch (Exception e) {
e.printStackTrace();
}
tvTopic.setText(topicText);
//如果想实现点击,必须要设置这个
tvTopic.setMovementMethod(LinkMovementMethod.getInstance());
}
/**
* 测试@好友
*/
private void textAtUsers(){
List<User> users = new ArrayList<>();
users.add(new User(1, "好友1"));
users.add(new User(2, "好友2"));
StringBuilder sb = new StringBuilder("快来看看啊");
for (User u : users) {
sb.append("@").append(u.getName());
}
sb.append("\n");
try {
SpannableString atSpan = SpanUtils.getAtUserSpan(Color.BLUE, sb.toString(), true, new SpanUtils.SpanClickListener<User>() {
@Override
public void onSpanClick(User user) {
Toast.makeText(SpannableStringActivity.this, "@好友:" + user.toString(), Toast.LENGTH_SHORT).show();
}
}, users);
tvTestAt.setText(atSpan);
tvTestAt.setMovementMethod(LinkMovementMethod.getInstance());
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 测试表情显示
*/
private void textExpression(){
String exStr = "今天天气很好啊[呲牙],是不是应该做点什么[色]";
SpannableString span = null;
try {
span = SpanUtils.getExpressionSpan(this, exStr);
} catch (Exception e) {
e.printStackTrace();
}
tvExpression.setText(span);
}
private void initViews() {
tvColoredKeywd = (TextView) findViewById(R.id.tv_keyword_colored);
tvTopic = (TextView) findViewById(R.id.tv_topic);
tvTestAt = (TextView) findViewById(R.id.tv_test_at);
tvExpression = (TextView) findViewById(R.id.tv_text_expression);
}
}
activity_spannable_string.xml
<?xml version="1.0" encoding="utf-8"?>
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingBottom="@dimen/activity_vertical_margin"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
android:background="#fff">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<TextView
android:id="@+id/tv_keyword_colored"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
<TextView
android:id="@+id/tv_topic"
android:layout_width="match_parent"
android:layout_height="wrap_content"
/>
<TextView
android:id="@+id/tv_test_at"
android:layout_width="match_parent"
android:layout_height="wrap_content"
/>
<TextView
android:id="@+id/tv_text_expression"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
</LinearLayout>
</ScrollView>