SpannableString两种匹配方法分析(第一种为模仿微博内容匹配)

来源:互联网 发布:电视剧全集获取源码 编辑:程序博客网 时间:2024/06/08 06:12

以下微博内容匹配相关内容,参考自:

http://www.jikexueyuan.com/course/1540.htmlhttp://blog.csdn.net/a62321780/article/details/52824295

最近看了一个极客视频和几篇博客。有提到微博内容匹配的。
其中,匹配规则用的是下面这种正则方法

String ALL = "(" + AT + ")" + "|" + "(" + TOPIC + ")" + "|" + "(" + URL + ")" + "|" + "(" + EMOJI + ")";SpannableString spannableString = new SpannableString(source);Pattern pattern = Pattern.compile(ALL);Matcher matcher = pattern.matcher(source);

我之前没用过这种方法。这里,针对视频、博客中的方法和我自己的方法做个对比。

注:匹配规则用的视频和博客中提到的。模拟请求网络数据加载的地方,视频和博客中没有提到,因为不知道微博源码,我只能自己想办法模拟加载到网络数据。我自认为没有微博开发大神的境界和技术。所以,以下代码,数据加载和展示,是自己想办法实现的,不代表真正的微博实现方法。因为是自己想的方法,所以,结果仅供参考!!!

首先,我先说一下实际情况(需求):给出一段文字(后台返回的),匹配其中的话题、网址链接、表情,甚至一些股票代码,如:000001代表平安银行。

因为数据是用户编辑后发出来的,很多是不确定的。如:股票代码。唯一能通用的匹配规则,就是表情、网址链接,已经话题(因为话题是#…#这样的格式)

代码如下,我这里就不多做解释了,注释里有。最后有测试结果
KeyBean

package com.chen.demo;public class KeyBean {    //关键字    private String key;    //关键字的内容。用户看不到的,如:股票代码对应的股票网页url    private String content;    public String getKey() {        return key;    }    public void setKey(String key) {        this.key = key;    }    public String getContent() {        return content;    }    public void setContent(String content) {        this.content = content;    }}

CHEN

package com.chen.demo;import android.content.Context;public class CHEN {    /**     * 网址要被替换成的文字     */    public static String REPLACEMENT_STRING = "#点击链接";    /**     * 匹配表情     */    public static String emojiRegex = "\\[\\\\[\u4e00-\u9fa5\\w]+\\]";    /**     * 匹配话题(#.....#)     */    public static String topicRegex = "#[\u4e00-\u9fa5\\w]+#";    /**     * 匹配网址的正则表达式。有http://、https://、ftp://这3中开头的     */    public static String urlRegex = "((http[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\\.\\-~!@#$%^&*+?:_/=<>,]*)?)|([a-zA-Z0-9\\.\\-]+\\.([a-zA-Z]{2,4})(:\\d+)?(/[a-zA-Z0-9\\.\\-~!@#$%^&*+?:_/=<>,]*)?)";    public static int sp2px(float spValue, Context context) {        final float fontScale = context.getResources().getDisplayMetrics().scaledDensity;        return (int) (spValue * fontScale + 0.5f);    }    /**     * 基础链接,匹配链接、表情、话题     */    public static String Regex = "(" + REPLACEMENT_STRING + ")|(" + emojiRegex + ")|(" + topicRegex + ")";}

MyTextView

package com.chen.demo;import android.content.Context;import android.graphics.drawable.Drawable;import android.support.annotation.Nullable;import android.text.Spannable;import android.text.SpannableString;import android.text.Spanned;import android.text.TextPaint;import android.text.TextUtils;import android.text.method.LinkMovementMethod;import android.text.style.ClickableSpan;import android.text.style.ImageSpan;import android.util.AttributeSet;import android.util.Log;import android.view.View;import android.widget.TextView;import android.widget.Toast;import java.util.ArrayList;import java.util.HashMap;import java.util.regex.Matcher;import java.util.regex.Pattern;public class MyTextView extends TextView {    private Context context = null;    private ArrayList<String> urlList;    public MyTextView(Context context) {        super(context);        this.context = context;    }    public MyTextView(Context context, @Nullable AttributeSet attrs) {        super(context, attrs);        this.context = context;    }    public MyTextView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {        super(context, attrs, defStyleAttr);        this.context = context;    }    public void showContent(String str, HashMap<String, HashMap<String, String>> map, String regexStr) {        Log.e("MyTextView","showContent");        long startTime=System.currentTimeMillis();        //话题的map        final HashMap<String, String> topicMap = map.get("topic");        //关键字的map        final HashMap<String, String> keyMap = map.get("key");        Log.e("regexStr", regexStr);        String[] ss = regexStr.split("\\|");        int count = ss.length;        Log.e("count", count + "");        String content = str;        //处理匹配的url        Pattern p = Pattern.compile(CHEN.urlRegex);        Matcher m = p.matcher(content);        urlList = new ArrayList<String>();        while (m.find()) {            String urlStr = m.group();            if (urlStr.contains("http://") || urlStr.contains("ftp://")) {                //如果末尾有英文逗号或者中文逗号等,就去掉                while (urlStr.endsWith(",") || urlStr.endsWith(",") || urlStr.endsWith(".") || urlStr.endsWith("。") || urlStr.endsWith(";") || urlStr.endsWith(";") || urlStr.endsWith("!") || urlStr.endsWith("!") || urlStr.endsWith("?") || urlStr.endsWith("?")) {                    urlStr = urlStr.substring(0, urlStr.length() - 1);                }                urlList.add(urlStr);                content = content.replace(urlStr, CHEN.REPLACEMENT_STRING);            }        }        //到此,文本中的链接,就都被替换成了“*点击链接”这样的文字        //讲转换后        SpannableString spannableString = new SpannableString(content);        Pattern regex = Pattern.compile(regexStr);        Matcher matcher = regex.matcher(spannableString);        if (matcher.find()) {            setMovementMethod(LinkMovementMethod.getInstance());            matcher.reset();        }        while (matcher.find()) {            final String url = matcher.group(1);            final String emoji = matcher.group(2);            final String topic = matcher.group(3);            //链接处理            if (url != null) {                int start = matcher.start(1);                spannableString.setSpan(new ClickableSpan() {                                            @Override                                            public void updateDrawState(TextPaint ds) {                                                ds.setColor(0xff2097D9);                                                ds.setUnderlineText(false);                                            }                                            @Override                                            public void onClick(View widget) {                                                Toast.makeText(context, url, Toast.LENGTH_SHORT).show();                                            }                                        }, start, start + CHEN.REPLACEMENT_STRING.length(),                        Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);                try {                    Drawable drawable = context.getResources().getDrawable(R.mipmap.web_link);                    drawable.setBounds(0, 0, CHEN.sp2px(25, context), CHEN.sp2px(25, context));                    ImageSpan imgSpan = new ImageSpan(drawable);                    spannableString.setSpan(imgSpan, start,                            start + 1, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);                } catch (Exception e) {                    //异常以后,就不加小图片了                }            }            //表情处理            if (emoji != null) {                int start = matcher.start(2);                Log.e("emoji", emoji);                Drawable drawable = context.getResources().getDrawable(R.mipmap.emoji_weixiao);                drawable.setBounds(0, 0, CHEN.sp2px(25, context), CHEN.sp2px(25, context));                ImageSpan imgSpan = new ImageSpan(drawable);                spannableString.setSpan(imgSpan, start,                        start + emoji.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);            }            //处理话题            if (topic != null) {                int start = matcher.start(3);                Log.e("topic", topic);                spannableString.setSpan(new ClickableSpan() {                                            @Override                                            public void updateDrawState(TextPaint ds) {                                                super.updateDrawState(ds);                                                ds.setColor(0xff2097D9);                                                ds.setUnderlineText(false);                                            }                                            @Override                                            public void onClick(View widget) {                                                Toast.makeText(context, topicMap.get(topic), Toast.LENGTH_SHORT).show();                                            }                                        }, start, start + topic.length(),                        Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);            }            if (count > 3) {                //除了默认的,还有其他新的,需要匹配的                for (int i = 4; i <= count; i++) {                    //额外的数据                    final String extraData = matcher.group(i);                    if (extraData != null) {                        int start = matcher.start(i);                        Log.e("extraData==" + i, extraData);                        spannableString.setSpan(new ClickableSpan() {                                                    @Override                                                    public void updateDrawState(TextPaint ds) {                                                        super.updateDrawState(ds);                                                        ds.setColor(0xff2097D9);                                                        ds.setUnderlineText(false);                                                    }                                                    @Override                                                    public void onClick(View widget) {                                                        Toast.makeText(context, keyMap.get(extraData), Toast.LENGTH_SHORT).show();                                                    }                                                }, start, start + extraData.length(),                                Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);                    }                }            }        }        setText(spannableString);        Log.e("showContent use time",System.currentTimeMillis()-startTime+"");    }    public void showData(String str, ArrayList<KeyBean> keyBeanList) {        Log.e("MyTextView","showData");        long startTime=System.currentTimeMillis();        String content = str;        //处理匹配的url        Pattern p = Pattern.compile(CHEN.urlRegex);        Matcher m = p.matcher(content);        ArrayList<String> urlList = new ArrayList<String>();        while (m.find()) {            String urlStr = m.group();            if (urlStr.contains("http://") || urlStr.contains("ftp://")) {                //如果末尾有英文逗号或者中文逗号等,就去掉                while (urlStr.endsWith(",") || urlStr.endsWith(",") || urlStr.endsWith(".") || urlStr.endsWith("。") || urlStr.endsWith(";") || urlStr.endsWith(";") || urlStr.endsWith("!") || urlStr.endsWith("!") || urlStr.endsWith("?") || urlStr.endsWith("?")) {                    urlStr = urlStr.substring(0, urlStr.length() - 1);                }                urlList.add(urlStr);                content = content.replace(urlStr, CHEN.REPLACEMENT_STRING);            }        }        SpannableString spannableString = new SpannableString(content);        //处理表情相关        String emoji_string = "\\[(.+?)\\]";        Pattern emoji_patten = Pattern.compile(emoji_string);        Matcher matcher = emoji_patten.matcher(content);        while (matcher.find()) {            Drawable drawable = context.getResources().getDrawable(R.mipmap.emoji_weixiao);            drawable.setBounds(0, 0, CHEN.sp2px(25, context), CHEN.sp2px(25, context));            ImageSpan imgSpan = new ImageSpan(drawable);            spannableString.setSpan(imgSpan, matcher.start(),                    matcher.end(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);        }        //表情相关处理结束        content = spannableString.toString();        //处理链接        if (urlList.size() > 0) {            int urlStartNew = 0;            int urlStartOld = 0;            String urlTemp = content;            for (int i = 0; i < urlList.size(); i++) {                final String regexUrl = urlList.get(i);                spannableString.setSpan(new ClickableSpan() {                                            @Override                                            public void updateDrawState(TextPaint ds) {                                                // TODO Auto-generated method stub                                                super.updateDrawState(ds);                                                ds.setColor(0xff2097D9);                                                ds.setUnderlineText(false);                                            }                                            @Override                                            public void onClick(View widget) {                                                Toast.makeText(context, regexUrl, Toast.LENGTH_SHORT).show();                                            }                                        }, urlStartOld + urlTemp.indexOf(CHEN.REPLACEMENT_STRING), urlStartOld + urlTemp.indexOf(CHEN.REPLACEMENT_STRING) + CHEN.REPLACEMENT_STRING.length(),                        Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);                try {                    //“点击链接”前面的回形针图片。大小可自己调整                    Drawable drawable = context.getResources().getDrawable(R.mipmap.web_link);                    drawable.setBounds(0, 0, CHEN.sp2px(25, context), CHEN.sp2px(25, context));                    //                    ImageSpan imgSpan = new ImageSpan(drawable);                    spannableString.setSpan(new ImageSpan(drawable), urlStartOld + urlTemp.indexOf(CHEN.REPLACEMENT_STRING), urlStartOld + urlTemp.indexOf(CHEN.REPLACEMENT_STRING) + 1, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);                } catch (Exception e) {                    //异常以后,就不加小图片了                }                setMovementMethod(LinkMovementMethod.getInstance());                urlStartNew = urlTemp.indexOf(CHEN.REPLACEMENT_STRING) + CHEN.REPLACEMENT_STRING.length();                urlStartOld += urlStartNew;                urlTemp = urlTemp.substring(urlStartNew);            }        }        //处理关键字        if (keyBeanList != null) {            for (int i = 0; i < keyBeanList.size(); i++) {                final String data = keyBeanList.get(i).getKey();                final String beanContent=keyBeanList.get(i).getContent();                String temp = content;                int startNew = 0;                int startOld = 0;                if (temp.contains(data)) {                    while (temp.contains(data)) {                        spannableString.setSpan(new ClickableSpan() {                                                    @Override                                                    public void updateDrawState(TextPaint ds) {                                                        super.updateDrawState(ds);                                                        ds.setColor(0xff2097D9);                                                        ds.setUnderlineText(false);                                                    }                                                    @Override                                                    public void onClick(View widget) {                                                        Toast.makeText(context, beanContent, Toast.LENGTH_SHORT).show();                                                    }                                                }, startOld + temp.indexOf(data), startOld + temp.indexOf(data) + data.length(),                                Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);                        setMovementMethod(LinkMovementMethod.getInstance());                        startNew = temp.indexOf(data) + data.length();                        startOld += startNew;                        temp = temp.substring(startNew);                    }                }            }        }        setText(spannableString);        Log.e("showData use time",System.currentTimeMillis()-startTime+"");    }}

activity_main

<?xml version="1.0" encoding="utf-8"?><RelativeLayout    xmlns:android="http://schemas.android.com/apk/res/android"    android:layout_width="match_parent"    android:layout_height="match_parent"    >    <com.chen.demo.MyTextView        android:id="@+id/tv"        android:layout_width="wrap_content"        android:layout_height="wrap_content"        android:layout_alignParentBottom="true"        android:layout_centerHorizontal="true"        android:layout_marginBottom="30dp"        android:background="#55ff0000"        android:padding="5dp"        android:textSize="25sp"/></RelativeLayout>

MainActivity

package com.chen.demo;import android.app.Activity;import android.os.Bundle;import java.util.ArrayList;import java.util.HashMap;public class MainActivity extends Activity {    private MyTextView tv;    private HashMap<String, HashMap<String, String>> mapMap;    private HashMap<String, String> topicMap;    private HashMap<String, String> keyMap;    private ArrayList<KeyBean> keyBeanList;    @Override    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        setContentView(R.layout.activity_main);        tv = findViewById(R.id.tv);        //方法1开始        mapMap = new HashMap<>();        String str = "哈平安银行哈http://www.baidu.com哈万科哈[\\微笑]哈平安银行哈#明星##游戏#哈万科哈http://www.jikexueyuan.com/哈哈[\\微笑]哈哈#明星#";        //话题数据准备。模拟后台返回,解析后封装到这里面        topicMap = new HashMap<>();        topicMap.put("#明星#", "去到明星话题");        topicMap.put("#游戏#", "去到游戏话题");        mapMap.put("topic", topicMap);        //关键字数据准备        keyMap = new HashMap<>();        keyMap.put("平安银行", "000001");        keyMap.put("万科", "000002");        mapMap.put("key", keyMap);        //平安银行和万科,是用户输入,然后后台返回的。因为是用户输入,具有不确定性,为了匹配他们,需要新建。        String regex = CHEN.Regex + "|(" + "平安银行" + ")|(" + "万科" + ")";        tv.showContent(str, mapMap, regex);        //以上是方法1        //方法2开始        keyBeanList = new ArrayList<>();        KeyBean keyBean_1 = new KeyBean();        keyBean_1.setKey("#明星#");        keyBean_1.setContent("去到明星话题");        keyBeanList.add(keyBean_1);        KeyBean keyBean_2 = new KeyBean();        keyBean_2.setKey("#游戏#");        keyBean_2.setContent("去到游戏话题");        keyBeanList.add(keyBean_2);        KeyBean keyBean_3 = new KeyBean();        keyBean_3.setKey("平安银行");        keyBean_3.setContent("000001");        keyBeanList.add(keyBean_3);        KeyBean keyBean_4 = new KeyBean();        keyBean_4.setKey("万科");        keyBean_4.setContent("000002");        keyBeanList.add(keyBean_4);        //        tv.showData(str, keyBeanList);        //以上是方法2    }}

说明:
1、文字中出现的表情和网页回形针小图片,需要把25sp转换,是因为文字大小是25sp的。要和文字大小对应
2、MyTextView中,showContent方法对应的是微博内容匹配的正则方法,showData用的是我之前一直用的方法。
3、MainActivity中,HashMap和ArrayList对数据的封装,都是模拟从后台拿到需要匹配的数据后进行的封装。还是上面提到的,我不知道微博源码,只能用自己想到的方法
4、如果复制了上面的代码运行起来,会发现,showContent展示数据后,点击“点击链接”关键字,不会提示网址内容,但是showData方法可以。这是因为,我没有对showContent情况下的链接点击做额外处理。因为,到目前为止,我不建议用第一种方法的方式处理链接的匹配。理由是,用户发了链接,在手机上真正展示的时候,都会变成“点击链接”这样的文字。每个链接展示的样式是一样的,当用户在一个文本中发了多条链接后,通过while循环,没办法给对应的位置加对应的链接内容。用第二种方法(showData中的)处理,for循环下,可以给每个区域加自己对应的点击事件。
5、我把上面Mainactivity中的展示内容,复制粘贴了9次,即,用10倍数据测试的。测试结果:(以下结果,是用上面的代码测出来的,每次的时间,都是后台清理APP,杀死进程后,重新启动的情况下得到的时间)
测试手机:NEM-TL00H,EMUI系统4.1.2,Android版本6.0

10-30 15:16:46.497 14863-14863/com.chen.demo E/showContent use time: 1310-30 15:17:06.484 15277-15277/com.chen.demo E/showContent use time: 1310-30 15:17:19.642 15428-15428/? E/showContent use time: 1310-30 15:17:43.331 15628-15628/? E/showContent use time: 1310-30 15:17:58.190 15789-15789/? E/showContent use time: 1310-30 15:18:35.988 16218-16218/com.chen.demo E/showData use time: 1110-30 15:18:53.825 16397-16397/? E/showData use time: 910-30 15:19:14.607 16580-16580/? E/showData use time: 910-30 15:19:26.864 16728-16728/? E/showData use time: 1010-30 15:19:38.076 16867-16867/? E/showData use time: 10
阅读全文
0 0