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
- SpannableString两种匹配方法分析(第一种为模仿微博内容匹配)
- combobox的输入内容匹配的源码分析--修改为真正的模糊匹配
- 文件输入输出的两种方法(常用第一种)
- 正则匹配两字符串之间内容
- poj 2226 匈牙利匹配 一种构图方法
- android第八节自动匹配输入内容
- 两种正则表达式匹配E-Mail的方法
- python匹配文本中全角符号的两种方法
- 三菱V3菱悦智能遥控匹配详细的(两种)方法 配钥匙
- 使用SpannableString实现微博内容
- Xpath 匹配节点的内容为空(inner text 为空)
- 文件的输出与输入两种方法(常用第一种)
- 方法匹配
- 匹配括号( ( ),{ },[ ]等左右对应的字符 ) 的一种方法
- ORB 一种特征匹配替代方法:对比SIFT或SURF
- awk之修改匹配的第N个内容
- 正则匹配内容为空的HTML标签
- 元素类型为 "class" 的内容必须匹配 "(meta*
- Ubuntu与Centos的Hadoop安装以及编译运行MapReduce,Hadoop集群安装配置教程
- SpringBoot 中文手册 --Part IV --30 使用NoSQL技术
- 简单代码实现TabLayout 条目的图文混排
- 基于 JAVA NIO 的socket通信
- Mac 常用命令行
- SpannableString两种匹配方法分析(第一种为模仿微博内容匹配)
- sed正则表达式
- VirtualBox Centos安装增强工具并实现文件夹共享
- 获取控件宽高
- 求最小环 floyed 与 dijkstra
- 【工具】福昕阅读器快捷键
- 网站建设解决响应式网站图片响应式难题
- c#运算符和控制流
- 打开excel提示:向程序发送命令时出现问题,如何解决