关于 spannableStrting 基本的一些用法效果在这里
http://blog.csdn.net/kongbaidepao/article/details/12087197
一、对 edittext 或 textview 中的 特殊显示效果字符进行保存 与显示
1.基本抽象方法定义基本功能
public abstract interface IMediaHandler {
//处理联系人
public abstract void handleContact(SpannableStringBuilder builder, int start, int end,
String name,
String phone);
//处理图片
public abstract void handleImage(SpannableStringBuilder builder, int start, int end, String name);
//处理删除线
public abstract void handleStrike(SpannableStringBuilder builder, int start, int end);
}
2.对要显示的数据进行处理
public class MediaHandler implements IMediaHandler {
public void handleContact(SpannableStringBuilder builder, int start, int end, String name,
String phone) {
//得到 ContactImage
Bitmap localBitmap = RichEditView.this.getContactImage(name);
//这里设置 联系人的 TouchableSpan
TouchableContactSpan span = new TouchableContactSpan(mContext, localBitmap,
RichEditView.this, name,
phone);
//首先替换该处联系人的字符
String objStr = "\uFFFC";
builder.replace(start, end, objStr);
//再替换成联系人图片(span)
builder.setSpan(span, start, start + objStr.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
}
public void handleImage(SpannableStringBuilder builder, int start, int end, String name) {
//同联系人
TouchableImageSpan span = new TouchableImageSpan(mContext, RichEditView.this, name);
span.initialize();
String objStr = "\uFFFC";
builder.replace(start, end, objStr);
builder.setSpan(span, start, start + objStr.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
}
public void handleStrike(SpannableStringBuilder builder, int start, int end) {
//设置删除线
builder.setSpan(new StrikethroughSpan(), start, end, Spanned.SPAN_INCLUSIVE_EXCLUSIVE);
}
}
3.调用
Spanned localSpanned = SpanUtils.parseSpannable(paramCharSequence.toString(), mMediaHandler);
setText(localSpanned);
4. 工具类
import java.io.File;
import java.util.ArrayList;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import android.app.Activity;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.text.Editable;
import android.text.SpannableStringBuilder;
import android.text.Spanned;
import android.text.TextUtils;
import android.text.style.ImageSpan;
import android.text.style.StrikethroughSpan;
import android.widget.TextView;
public class SpanUtils {
public static final char OBJ_CHAR = '\uFFFC'; //类似小图片的特殊符
public static final char NEWLINE_CHAR = '\n'; //换行符
public static final char IMG_START_CHAR = '\u263A'; //&
public static final char IMG_END_CHAR = '\u263B'; //笑脸
public static final char CONTARCT_START_CHAR = '\u263E'; //符号 (
public static final char CONTARCT_END_CHAR = '\u263D'; //符号 )
public static final char CONTARCT_SPEATROR_CHAR = '\u263C'; //虫子
public static final char CHECK_CHAR = '\u221A';//对号。
public static final char BLANK_CHAR = ' ';
//删除线的匹配(正则)
private static final String REGEX_STRIKE_SPAN = "^" + CHECK_CHAR + "([^\n" + CHECK_CHAR
+ "]+)$";
//图片的匹配 (正则)
private static final String REGEX_IMAGE_SPAN = IMG_START_CHAR + "([A-Za-z0-9]+)" + IMG_END_CHAR;
//电话号码的匹配 , add phone number rules (正则)
private static final String REGEX_CONTACT_SPAN =
//电话号码的正则匹配
CONTARCT_START_CHAR +
"(\\(\\d{1,4}\\)\\s\\d{1,4}-\\d{1,4}|" +
"\\d{1,3}-\\d{1,8}|" + // 029 - 123456789
"\\+\\d{1,13}|" + // \\ 13484668924
"\\+\\d{2}\\s\\d{1,11}|" + // 86 123484466456
"\\+\\d{2}\\s\\d{1,4}\\s\\d{1,4}\\s\\d{1,4}|" +
"\\+\\d{2}-\\d{1,11}|" +
"\\+\\d{2}-\\d{1,11}-\\d{1,11}|" +
"\\+\\d{2}-\\d{1,11}-\\d{1,11}\\*\\d{1,5}|" +
"\\d{1,4}\\s\\d{1,4}\\s\\d{1,4}|" +
"\\d{1,13}|" +
"\\d{1,2}\\s\\d{1,4}-\\d{1,4}-\\d{1,4}+)" + // 11 1344-4584-7456
CONTARCT_SPEATROR_CHAR +
"([^"
+ IMG_START_CHAR + IMG_END_CHAR + CONTARCT_START_CHAR + CONTARCT_END_CHAR
+ CONTARCT_SPEATROR_CHAR + CHECK_CHAR + "]+)"
+ CONTARCT_END_CHAR;
//对数据库取出来的数据, 要在界面显示的文字进行处理
public static Spanned parseSpannable(String text, IMediaHandler handler) {
SpannableStringBuilder builder = new SpannableStringBuilder(text);
int start, end;
//正则进行匹配先,这里是删除线
Pattern p1 = Pattern.compile(REGEX_STRIKE_SPAN, Pattern.MULTILINE);
Matcher m1 = p1.matcher(builder);
while (m1.find()) {
String content = m1.group(1);
start = m1.start();
end = m1.end();
handler.handleStrike(builder, start, end);
builder.replace(start, end, content);
m1.reset(builder);
}
//电话号码
Pattern p2 = Pattern.compile(REGEX_CONTACT_SPAN, Pattern.MULTILINE);
Matcher m2 = p2.matcher(builder);
while (m2.find()) {
String phone = m2.group(1);
String name = m2.group(2);
start = m2.start();
end = m2.end();
handler.handleContact(builder, start, end, name, phone);
//reset the Matcher
m2.reset(builder);
}
//image
Pattern p3 = Pattern.compile(REGEX_IMAGE_SPAN, Pattern.MULTILINE);
Matcher m3 = p3.matcher(builder);
while (m3.find()) {
String imgName = m3.group(1);
start = m3.start();
end = m3.end();
handler.handleImage(builder, start, end, imgName);
//reset the Matcher
m3.reset(builder);
}
return builder;
}
//单独删除线的处理(闹钟显示,widget 对从数据库中取出来的数据,正则匹配,然后还原成原来的样子),
public static Spanned parseStrikethroughSpan(CharSequence showText) {
SpannableStringBuilder builder = new SpannableStringBuilder(showText);
Pattern p = Pattern.compile(REGEX_STRIKE_SPAN, Pattern.MULTILINE);
Matcher m = p.matcher(builder);
int start, end;
while (m.find()) {
String content = m.group(1);
start = m.start();
end = m.end();
builder.setSpan(new StrikethroughSpan(), start, end, Spanned.SPAN_INCLUSIVE_EXCLUSIVE);
builder.replace(start, end, content);
m.reset(builder);
}
return builder;
}
// widget 和 dialog 上显示联系人 [ lilei: 1234567890 ],进行格式化
public static CharSequence normalizeSnippet(String content) {
return parseStrikethroughSpan(normalizeSnippet(null, content));
}
// 将图片信息删掉,将联系人信息重新组织输出,调用下面紧跟着的这个方法,用于显示在 其他不能点击的地方,比如 widget 和 dialog
public static String normalizeSnippet(String content, NoteImgInfo info) {
Pattern p = Pattern.compile(REGEX_IMAGE_SPAN, Pattern.MULTILINE);
Matcher m = p.matcher(content);
boolean bool = false;
while (m.find()) {
String name = m.group(1);
if (!bool) {
bool = true;
if (info != null)
info.firstImgName = name;
}
}
return normalizeSnippet(null, content).toString();
}
// 单独格式化联系人((闹钟显示, 因为闹钟提醒里面的话,不像平常文本可点击查看联系人 [ lilei: 1234567890 ] )
public static CharSequence normalizeSnippet(Context context, String content) {
String out = new StringBuilder(content).toString();
Pattern p1 = Pattern.compile(REGEX_CONTACT_SPAN, Pattern.MULTILINE);
Matcher m1 = p1.matcher(out);
while (m1.find()) {
out = m1.replaceFirst("[" + m1.group(2) + ": " + m1.group(1) + "]");
m1 = p1.matcher(out);
}
Pattern p2 = Pattern.compile(REGEX_IMAGE_SPAN, Pattern.MULTILINE);
Matcher m2 = p2.matcher(out);
if (m2.find())
out = m2.replaceAll("");
return out;
}
//当前的文本对spannble格式化,存入数据库
public static CharSequence buildSpannable(Editable editable) {
// 此处传入的是当前edit窗口的editable的引用,不能修改,含有span对象,创建新的editable对象时需将其序列化?传入
Editable out = Editable.Factory.getInstance().newEditable(
editable.subSequence(0, editable.length()));
int start, end;
String content;
//查看是否有联系人
TouchableContactSpan[] contacts = out.getSpans(0, out.length(), TouchableContactSpan.class);
for (int i = 0; i < contacts.length; i++) {
start = out.getSpanStart(contacts[i]);
end = out.getSpanEnd(contacts[i]);
content = CONTARCT_START_CHAR + contacts[i].getPhoneNumber() + CONTARCT_SPEATROR_CHAR
+ contacts[i].getContactName()
+ CONTARCT_END_CHAR;
out.replace(start, end, content);
}
//图片
TouchableImageSpan[] images = out.getSpans(0, out.length(), TouchableImageSpan.class);
for (int i = 0; i < images.length; i++) {
start = out.getSpanStart(images[i]);
end = out.getSpanEnd(images[i]);
content = IMG_START_CHAR + images[i].getName() + IMG_END_CHAR;
if (start >= 0 && end >= 0)
out.replace(start, end, content);
}
//删除线
StrikethroughSpan[] spans = out.getSpans(0, out.length(), StrikethroughSpan.class);
for (int i = 0; i < spans.length; i++) {
start = out.getSpanStart(spans[i]);
end = out.getSpanEnd(spans[i]);
content = out.subSequence(start, end).toString();
if (start >= 0 && out.charAt(start) != CHECK_CHAR )
out.replace(start, end, CHECK_CHAR + content);
}
return out;
}
public static class SimpleBuilder extends SpannableStringBuilder {
public SimpleBuilder(String text) {
super(text);
}
public SimpleBuilder() {
super();
}
public boolean isEmpty() {
return TextUtils.isEmpty(this.toString());
}
public void appendNewLine() {
this.append("\n");
}
public void appendImage(String name) {
// File img = new File(name);
// if (img.exists()) {
// BitmapFactory.Options option = new BitmapFactory.Options();
// option.inJustDecodeBounds = false;
// Bitmap bmp = BitmapFactory.decodeFile(img.getAbsolutePath(),
// option);
// // ImageSpan spans = new ImageSpan(bmp);
// TouchableImageSpan span = new TouchableImageSpan(mContext, this,
// imgName);
// span.initialize();
// // ImageSpan span = new ImageSpan(getMissedDrawable());
// int start = this.length();
// this.append(OBJ_CHAR);
// this.setSpan(span, start, end,
//
// this.setSpan(spans, start, start + 1,
// Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
// Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
// }
this.append(IMG_START_CHAR + name + IMG_END_CHAR);
}
public void appendText(String text) {
this.append(text);
}
}
public static ArrayList<String> retrieveImages(Activity mActivity, CharSequence content) {
ArrayList<String> out = new ArrayList<String>();
Pattern p = Pattern.compile(REGEX_IMAGE_SPAN, Pattern.MULTILINE);
Matcher m = p.matcher(content);
while (m.find()) {
String name = m.group(1);
out.add(name);
}
return out;
}
public static class NoteImgInfo {
public String firstImgName;
public NoteImgInfo() {
firstImgName = null;
}
}
//查看中是否有图片
public static boolean findImageInSnippet(String content) {
Pattern p = Pattern.compile(REGEX_IMAGE_SPAN, Pattern.MULTILINE);
Matcher m = p.matcher(content);
if (m.find())
return true;
return false;
}
public static ArrayList<String> collectImageNames(String content) {
ArrayList<String> names = new ArrayList<String>();
Pattern p = Pattern.compile(REGEX_IMAGE_SPAN, Pattern.MULTILINE);
Matcher m = p.matcher(content);
while (m.find()) {
names.add(m.group(1));
}
return names;
}
}
二、内容事件处理
1.定义基本接口
private static abstract interface IActionDetector {
public abstract boolean handleMotionEvent(MotionEvent event);
}
- 联系人的处理
private class ClickContactDetector implements IActionDetector {
private TouchableContactSpan nActiveSpan;
private boolean nTraking;
private ClickContactDetector() {
}
//对点击的地方处理,得到坐标进行处理
private TouchableContactSpan getActiveSpan(MotionEvent event) {
Editable editable = getText();
TouchableContactSpan[] spans = (TouchableContactSpan[]) editable.getSpans(0,
editable.length(), TouchableContactSpan.class);
for (int i = 0; i < spans.length; i++) {
if (isTouchInContactSpan(spans[i], event))
return spans[i];
}
return null;
}
//对坐标进行判断,是否点击的是 可点击的联系人区域
private boolean isTouchInContactSpan(TouchableContactSpan span,
MotionEvent event) {
if (span != null)
return span.isTouched((int) event.getX(),
(int) event.getY());
return false;
}
//对接口类方法的实现,外部调用此方法
public boolean handleMotionEvent(MotionEvent event) {
boolean bool = false;
switch (event.getActionMasked()) {
case MotionEvent.ACTION_DOWN:
nActiveSpan = getActiveSpan(event);
if (nActiveSpan != null) {
nTraking = true;
bool = true;
}
else
nTraking = false;
break;
case MotionEvent.ACTION_UP:
if (nTraking && isTouchInContactSpan(nActiveSpan, event)) {
viewContact(nActiveSpan);
nTraking = false;
nActiveSpan = null;
bool = true;
}
break;
case MotionEvent.ACTION_MOVE:
if (nTraking)
break;
case MotionEvent.ACTION_CANCEL:
if (!nTraking)
break;
default:
nTraking = false;
nActiveSpan = null;
break;
}
return bool;
}
}
调用系统联系人显示
private void viewContact(TouchableContactSpan span) {
Uri localUri = Contact
.getContactUri(mContext, span.getContactName(), span.getPhoneNumber());
if (localUri != null)
ContactsContract.QuickContact.showQuickContact(getContext(), this, localUri,
ContactsContract.QuickContact.MODE_LARGE, null);
}
通过查询
public static Uri getContactUri(Context context, String name, String phoneNumber) {
Cursor cursor = getContactCursor(context, name, phoneNumber);
Uri uri = null;
if (cursor != null) {
try {
if (cursor.moveToNext()) {
String str = cursor.getString(0);
long l = cursor.getLong(1);
uri = ContactsContract.Contacts.CONTENT_LOOKUP_URI.buildUpon().appendPath(str)
.appendPath(Long.toString(l)).build();
}
} catch (Exception e) {
// TODO: handle exception
} finally {
if (cursor != null)
cursor.close();
}
}
return uri;
}
查询
private static Cursor getContactCursor(Context context, String name, String phoneNumber) {
Object[] arrayOfObject = new Object[2];
arrayOfObject[0] = "display_name";
arrayOfObject[1] = "data1";
String str = String.format(Locale.US, "%s=? AND PHONE_NUMBERS_EQUAL(%s,?)", arrayOfObject);
String[] arrayOfString = new String[] {
name, phoneNumber
};
return context.getContentResolver().query(ContactsContract.Data.CONTENT_URI,
IDENTITY_PROJECTION, str, arrayOfString, null);
}
2.对问题点击到图片的处理
private class ClickImageDetector implements IActionDetector {
private TouchableImageSpan nActiveSpan;
private boolean nTraking;
private ClickImageDetector() {
}
private TouchableImageSpan getActiveSpan(MotionEvent event)
{
Editable editable = getText();
TouchableImageSpan[] spans = (TouchableImageSpan[]) editable.getSpans(0,
editable.length(), TouchableImageSpan.class);
for (int i = 0; i < spans.length; i++) {
if (isTouchInImageSpan(spans[i], event))
return spans[i];
}
return null;
}
private boolean isTouchInImageSpan(TouchableImageSpan paramSmartImageSpan,
MotionEvent paramMotionEvent) {
if (paramSmartImageSpan != null)
return paramSmartImageSpan.isTouched((int) paramMotionEvent.getX(),
(int) paramMotionEvent.getY());
return false;
}
public boolean handleMotionEvent(MotionEvent paramMotionEvent) {
boolean bool = false;
switch (paramMotionEvent.getActionMasked()) {
case MotionEvent.ACTION_DOWN:
nActiveSpan = getActiveSpan(paramMotionEvent);
if (nActiveSpan != null) {
nTraking = true;
bool = true;
}
else
nTraking = false;
break;
case MotionEvent.ACTION_UP:
if (nTraking && isTouchInImageSpan(nActiveSpan, paramMotionEvent)) {
viewImage(nActiveSpan);
nTraking = false;
nActiveSpan = null;
bool = true;
}
break;
case MotionEvent.ACTION_MOVE:
if (nTraking)
break;
case MotionEvent.ACTION_CANCEL:
if (!nTraking)
break;
default:
nTraking = false;
nActiveSpan = null;
break;
}
return bool;
}
}
使自定义的对点击事件的处理生效,
- 在界面创建时
mClickImageDetector = new ClickImageDetector();
mClickContactDetector = new ClickContactDetector();
/*
* 如果dispatchTouchEvent返回true
* ,则交给这个view的onTouchEvent处理,如果dispatchTouchEvent返回 false ,则交给这个 view 的
* interceptTouchEvent 方法来决定是否要拦截这个事件,如果 interceptTouchEvent 返回 true
* ,也就是拦截掉了,则交给它的 onTouchEvent 来处理,如果 interceptTouchEvent 返回 false ,那么就传递给子
* view ,由子 view 的 dispatchTouchEvent 再来开始这个事件的分发。如果事件传递到某一层的子 view 的
* onTouchEvent 上了,这个方法返回了 false ,那么这个事件会从这个 view 往上传递,都是 onTouchEvent
* 来接收。而如果传递到最上面的 onTouchEvent 也返回 false 的话,这个事件就会“消失”,而且接收不到下一次事件。
*/
@Override
public boolean dispatchTouchEvent(MotionEvent event) {
if ((mClickImageDetector.handleMotionEvent(event))
|| (mClickContactDetector.handleMotionEvent(event))
|| mStrokeDetector.handleMotionEvent(event)
|| (super.dispatchTouchEvent(event)))
return true;
return false;
}