SpannableString

效果图



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>





  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值