(请忽略内容 - -)
“@xxx”还要会变色,跟正文不一样。
看上去也不是很复杂嘛。ok,开工。
首先是textView
public class SpanTextView extends TextView {
private SpannableString spannableString;
private Context context;
private final String AiteRule="@[^,,::\\s@]+"; // @人的正则表达式
private String MatchRule ="";
private String str="";
private MovementMethod mMovement;
public SpanTextView(Context context) {
super(context);
this.context = context;
}
public SpanTextView(Context context, AttributeSet attrs) {
super(context, attrs);
this.context = context;
}
public SpanTextView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
this.context = context;
}
@Override
public boolean hasFocusable(){
return false;
}
/**
* @param text
* @return
*/
public void setText(String text) {
if(getMatchRule()==null) {
/* 默认一个@的匹配规则 */
setMatchRule(AiteRule);
}
super.setText(setHighlight(text, getMatchRule()));
}
/**
* '@XXX
* @param text 文字
* @param rule 匹配规则
* @return
*/
private SpannableString setHighlight(String text,String rule) {
spannableString = new SpannableString(text);
Pattern pattern = Pattern.compile(rule);
Matcher matcher = pattern.matcher(text);
int length = text.length();
int end = 0;
for (int i = 0; i < length; i++) {
if (matcher.find()) {
int start = matcher.start();
end = matcher.end();
str = text.substring(start + 1, end);
spannableString.setSpan(new ClickSpan(context, str), start, end, Spanned.SPAN_MARK_POINT);
}
}
return spannableString;
}
public String getMatchRule() {
return MatchRule;
}
public void setMatchRule(String matchRule){n
this.MatchRule = matchRule;
}}
接下来是clickableSpan
public class ClickSpan extends ClickableSpan{
private Context context;
private String memberName="";
public ClickSpan(Context context, String memberName){
this.context = context;
this.memberName = memberName;
}
@Override
public void onClick(View view) {
/** 跳转 **/
}
@Override
public void updateDrawState(TextPaint ds) {
/** 给文字染色 **/
ds.setARGB(255,96,134,174);
/** 去掉下划线 , 默认自带下划线 **/
ds.setUnderlineText(false);
}
那这样可以实现了。。。
。。
。。
。。
。。
。。
。。
。。
。。
。。
。。
。
慢着。。。。。。。。。怎么会这样。。
SpanTextView 如果添加了 OnClickListener , 那么久会出现灰常坑爹事情:
在android 4.4 或以上版本,clickableSpan的onClick和SpanTextView的onClick是可以分别响应的,即点击了添加了clickableSpan的那部分文字时,跳转的是clickableSpan的onClick事件,点击没有被添加到clickableSpan的时候,跳转的是SpanTextView的onClick事件。
但,在4.4以下。。。这两者是响应先后明显就不一样的。。。变成了先响应SpanTextView的onClick事件,再去响应clickableSpan的onClick事件。。
这明显不是我们想要的。。。。
还好。。在国外网站的stackoverflow里面找到了答案
http://stackoverflow.com/questions/16792963/android-clickablespan-intercepts-the-click-event
有兴趣的去看一下。。
当然,还是要贴一下解决了上述问题的SpanTextView
public class SpanTextView extends TextView {
private SpannableString spannableString;
private Context context;
private final String AiteRule="@[^,,::\\s@]+";
private String MatchRule ="";
private String str="";
private MovementMethod mMovement;
public SpanTextView(Context context) {
super(context);
this.context = context;
}
public SpanTextView(Context context, AttributeSet attrs) {
super(context, attrs);
this.context = context;
}
public SpanTextView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
this.context = context;
}
@Override
public boolean hasFocusable(){
return false;
}
/**
* @param text
* @return
*/
public void setText(String text) {
if(getMatchRule()==null) {
/* 默认一个@的匹配规则 */
setMatchRule(AiteRule);
}
super.setText(setHighlight(text, getMatchRule()));
}
/**
* '@XXX
* @param text 文字
* @param rule 匹配规则
* @return
*/
private SpannableString setHighlight(String text,String rule) {
spannableString = new SpannableString(text);
Pattern pattern = Pattern.compile(rule);
Matcher matcher = pattern.matcher(text);
int length = text.length();
int end = 0;
for (int i = 0; i < length; i++) {
if (matcher.find()) {
int start = matcher.start();
end = matcher.end();
str = text.substring(start + 1, end);
spannableString.setSpan(new ClickSpan(context, str), start, end, Spanned.SPAN_MARK_POINT);
}
}
return spannableString;
}
/** 这个就是解决之道 **/
@Override
public boolean onTouchEvent(MotionEvent event){
boolean handled = false;
mMovement = getMovementMethod();
if (mMovement != null) {
handled |= mMovement.onTouchEvent(this, (Spannable) getText(), event);
}
ClickableSpan[] links = ((Spannable) getText()).getSpans(getSelectionStart(),
getSelectionEnd(), ClickableSpan.class);
if (links.length > 0) {
handled = true;
}if(handled){
return true;
}else {
final boolean superResult = super.onTouchEvent(event);
return superResult;
}
}
public String getMatchRule() {
return MatchRule;
}
public void setMatchRule(String matchRule){
this.MatchRule = matchRule;
}
}
还有为了解决这个问题,还是需要我们去重写一下LinkMovemenMethod
public class mLinkMovementMethod extends LinkMovementMethod {
public static MovementMethod getInstance(Context context){
if(sInstance==null){
sInstance = new mLinkMovementMethod(context);
}
return sInstance;
}
public mLinkMovementMethod(Context context){
this.context = context;
}
private Context context;
private static mLinkMovementMethod sInstance;
private static final int CLICK = 1;
private static final int UP = 2;
private static final int DOWN = 3;
@Override
public boolean canSelectArbitrarily() {
return true;
}
@Override
protected boolean handleMovementKey(TextView widget, Spannable buffer, int keyCode,
int movementMetaState, KeyEvent event) {
switch (keyCode) {
case KeyEvent.KEYCODE_DPAD_CENTER:
case KeyEvent.KEYCODE_ENTER:
if (KeyEvent.metaStateHasNoModifiers(movementMetaState)) {
if (event.getAction() == KeyEvent.ACTION_DOWN &&
event.getRepeatCount() == 0 && action(CLICK, widget, buffer)) {
return true;
}
}
break;
}
return super.handleMovementKey(widget, buffer, keyCode, movementMetaState, event);
}
@Override
protected boolean up(TextView widget, Spannable buffer) {
if (action(UP, widget, buffer)) {
return true;
}
return super.up(widget, buffer);
}
@Override
protected boolean down(TextView widget, Spannable buffer) {
if (action(DOWN, widget, buffer)) {
return true;
}
return super.down(widget, buffer);
}
@Override
protected boolean left(TextView widget, Spannable buffer) {
if (action(UP, widget, buffer)) {
return true;
}
return super.left(widget, buffer);
}
@Override
protected boolean right(TextView widget, Spannable buffer) {
if (action(DOWN, widget, buffer)) {
return true;
}
return super.right(widget, buffer);
}
private boolean action(int what, TextView widget, Spannable buffer) {
Layout layout = widget.getLayout();
int padding = widget.getTotalPaddingTop() +
widget.getTotalPaddingBottom();
int areatop = widget.getScrollY();
int areabot = areatop + widget.getHeight() - padding;
int linetop = layout.getLineForVertical(areatop);
int linebot = layout.getLineForVertical(areabot);
int first = layout.getLineStart(linetop);
int last = layout.getLineEnd(linebot);
ClickableSpan[] candidates = buffer.getSpans(first, last, ClickableSpan.class);
int a = Selection.getSelectionStart(buffer);
int b = Selection.getSelectionEnd(buffer);
int selStart = Math.min(a, b);
int selEnd = Math.max(a, b);
if (selStart < 0) {
if (buffer.getSpanStart(FROM_BELOW) >= 0) {
selStart = selEnd = buffer.length();
}
}
if (selStart > last)
selStart = selEnd = Integer.MAX_VALUE;
if (selEnd < first)
selStart = selEnd = -1;
switch (what) {
case CLICK:
if (selStart == selEnd) {
return false;
}
ClickableSpan[] link = buffer.getSpans(selStart, selEnd, ClickableSpan.class);
if (link.length != 1)
return false;
link[0].onClick(widget);
break;
case UP:
int beststart, bestend;
beststart = -1;
bestend = -1;
for (int i = 0; i < candidates.length; i++) {
int end = buffer.getSpanEnd(candidates[i]);
if (end < selEnd || selStart == selEnd) {
if (end > bestend) {
beststart = buffer.getSpanStart(candidates[i]);
bestend = end;
}
}
}
if (beststart >= 0) {
Selection.setSelection(buffer, bestend, beststart);
return true;
}
break;
case DOWN:
beststart = Integer.MAX_VALUE;
bestend = Integer.MAX_VALUE;
for (int i = 0; i < candidates.length; i++) {
int start = buffer.getSpanStart(candidates[i]);
if (start > selStart || selStart == selEnd) {
if (start < beststart) {
beststart = start;
bestend = buffer.getSpanEnd(candidates[i]);
}
}
}
if (bestend < Integer.MAX_VALUE) {
Selection.setSelection(buffer, beststart, bestend);
return true;
}
break;
}
return false;
}
@Override
public boolean onTouchEvent(final TextView widget, Spannable buffer,
MotionEvent event) {
int action = event.getAction();
/* 只需要检测按下 */
if (action == MotionEvent.ACTION_DOWN) {
int x = (int) event.getX();
int y = (int) event.getY();
x -= widget.getTotalPaddingLeft();
y -= widget.getTotalPaddingTop();
x += widget.getScrollX();
y += widget.getScrollY();
Layout layout = widget.getLayout();
int line = layout.getLineForVertical(y);
int off = layout.getOffsetForHorizontal(line, x);
ClickableSpan[] link = buffer.getSpans(off, off, ClickableSpan.class);
/* 检测Span */
if (link.length != 0) {
link[0].onClick(widget);
Selection.setSelection(buffer,
buffer.getSpanStart(link[0]),
buffer.getSpanEnd(link[0]));
} else {
Selection.removeSelection(buffer);
// Your call to your layout's onTouchEvent or it's
//onClick listener depending on your needs
context.startActivity(new Intent(context,DetailsActivity.class));
}
}
return true;
}
@Override
public void initialize(TextView widget, Spannable text) {
Selection.removeSelection(text);
text.removeSpan(FROM_BELOW);
}
@Override
public void onTakeFocus(TextView view, Spannable text, int dir) {
Selection.removeSelection(text);
if ((dir & View.FOCUS_BACKWARD) != 0) {
text.setSpan(FROM_BELOW, 0, 0, Spannable.SPAN_POINT_POINT);
} else {
text.removeSpan(FROM_BELOW);
}
}
private static Object FROM_BELOW = new NoCopySpan.Concrete();
clickablespan 无需改变,所以就参照上面那个就可以了
最后的最后写一下调用方法
tv.setText("@aaa");
tv.setMovementMethod(mLinkMovementMethod.getInstance(context));