技术联盟

在Android實作HTML TextView與AutoLink使用的建議方式

AndroidTextView除了可以用來顯示文字資料之外,還可以使用HTML語法來調整文字的樣式和文字超連結,無需特地去使用WebView。只是一旦使用TextView製作超連結,該TextView就會變得難以控制,因此有幾個特性應該是設計師必須事先知道的。

TextView顯示HTML

要使用TextView顯示HTML的作法很簡單,只要用以下方式來setText即可。

textView.setText(Html.fromHtml("HTML語法字串"));

例如:

textView.setText(Html.fromHtml("歡迎大家來到<big><font color=\"#FF0000\"><b>MagicLen</b></font></big>,本站網址如下:<br/><br/><a href=\"http://magiclen.org/\">http://magiclen.org/</a>"));

結果如下:

在Android實作HTML TextView與AutoLink使用的建議方式

啟用HTML超連結

如果只是單單使用TextView的setText方法來指定HTML語法,HTML超連結將無法有真正的作用,可以使用Android SDK內建提供的方式來讓TextView擁有超連結的功能,程式只要添加一行即可,如下:

textView.setMovementMethod(LinkMovementMethod.getInstance());

指定LinkMovementMethod給textView,使其擁有超連結的功能。

TextView使用AutoLink自動判斷連結

若資料來源並非是HTML,而是純文字的話,如果想要讓文字中的網址、電子郵件等等的資訊也擁有超連結,可以設定TextView的AutoLink屬性,決定要將哪些資訊自動產生超連結。

XML檔案中設定autolink屬性

添加方式如下:

    <TextView
        android:id="@+id/textView"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:autoLink="all" />

若將android:autoLink設為all,那它會將text文字中的網址、電子郵件、地址、電話都變成超連結。如果沒有特殊需求的話,不要自動將電話變成超連結,因為不是所有的Android裝置都能夠打電話,且電話格式各國都不太一樣,很大的機率會將一般數字誤判成電話。也不要自動將地址變成超連結,因為並非在所有裝置上都有地圖App,而且Android好像沒辦法判斷中文地址。所以autoLink屬性可以改寫如下:

    <TextView
        android:id="@+id/textView"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:autoLink="web|email" />

如果在XML設定autoLink,啟用了AutoLink屬性的話,LinkMovementMethod將會被自動加入TextView中,因此沒有必要使用setMovementMethod再指定LinkMovementMethod

SDK中用Java程式指定AutoLinkMask

添加方式如下:

textView.setAutoLinkMask(Linkify.ALL);

不將電話和地址加上超連結的寫法如下:

textView.setAutoLinkMask(Linkify.EMAIL_ADDRESSES|Linkify.WEB_URLS);

僅用setAutoLinkMask方法是無法啟用AutoLink屬性的,還再指定LinkMovementMethodTextView

textView.setMovementMethod(LinkMovementMethod.getInstance());

不必使用HTML的a標籤指定超連結

啟用textView的AutoLink之後,就不必再使用HTML的a標籤來製作超連結。因為Android會自動抓取text文字中符合的格式,將其自動更換為HTML的超連結。當然如果想要讓超連結顯示出不同的文字,還是可以使用a標籤來指定。完全不使用HTMLAndroid也會自動產生超連結。

以下舉個例子:

textView.setText("歡迎大家來到MagicLen\n本站網址:http://magiclen.org\n電子郵件:len@magiclen.org");
textView.setAutoLinkMask(Linkify.EMAIL_ADDRESSES|Linkify.WEB_URLS);
textView.setMovementMethod(LinkMovementMethod.getInstance());

結果如下:

在Android實作HTML TextView與AutoLink使用的建議方式

支援超連結的TextView所產生的問題

要讓TextView擁有超連結功能,必須要指定LinkMovementMethodTextView。然而TextView一旦設定了MovementMethod或是KeyListener,TextView會自動將Focusable、Clickable、LongClickable屬性設定為true。

問題與解決方法

TextView為Focusable的時候,且此TextView作為GridView或是ListView的Item,會使得無論是否點擊到TextView,onItemClickListener和onItemLongClickListener都會沒有作用。所以一定要覆寫掉hasFocusable方法,讓他永遠傳回false。

TextView為Clickable的時候,onTouchEvent將永遠回傳true,所以點擊事件在TextView中就被擋下了,無法傳給更上層的View,因此當此TextView作為GridView或是ListView的Item時,onItemClickListener和onItemLongClickListener將無作用,所以一定要想辦法覆寫掉onTouchEvent方法,讓它可以在點擊到TextView中連結的時候傳回true就好。

但是,Android內建的LinkMovementMethod並不會讓我們知道使用者到底是不是點擊連結,因此我們還必須改寫LinkMovementMethod

改寫後的TextView命名為LinkTextViewLinkMovementMethod命名為LinkTextViewMovementMethod,程式碼如下:

/**
 * 解決可使用Link HTML的TextView變成Clickable和Focusable的問題
 * 
 * @author magiclen
 */
public class LinkTextView extends TextView {
 
	// -----物件變數-----
	private boolean linkHit; // 是否為按下連結
 
	// -----建構子-----
	public LinkTextView(Context context) {
		super(context);
	}
 
	public LinkTextView(Context context, AttributeSet attrs) {
		super(context, attrs);
	}
 
	public LinkTextView(Context context, AttributeSet attrs, int defStyle) {
		super(context, attrs, defStyle);
	}
 
	// -----物件方法-----
	/**
	 * 覆寫onTouchEvent,使其不會永遠傳回true,若為true,則無法將touch事件傳出給上層的View。
	 *
	 */
	@SuppressLint("ClickableViewAccessibility")
	@Override
	public boolean onTouchEvent(MotionEvent event) {
		super.onTouchEvent(event);
		return linkHit;
	}
 
	/**
	 * 設定HTML內容給TextView,如果已啟用AutoLink屬性,則不需要使用這個方法來製作a標籤的連結。
	 * 
	 * @param html
	 */
	public void setTextViewHTML(String html) {
		Spanned sequence = Html.fromHtml(html);
		SpannableStringBuilder strBuilder = new SpannableStringBuilder(sequence);
		setText(strBuilder);
	}
 
	/**
	 * 設定MovementMethod之後,TextView會成為Focusable,所以LinkTextView覆寫此方法,永遠傳回false。
	 */
	@Override
	public boolean hasFocusable() {
		return false;
	}
 
	/**
	 * 繼承LinkMovementMethod的LinkTextViewMovementMethod,將會針對連結點擊進行處理,
	 * 讓LinkTextView知道目前點擊是否為連結點擊。
	 * 
	 * @author magiclen
	 *
	 */
	public static class LinkTextViewMovementMethod extends LinkMovementMethod {
 
		// -----類別變數-----
		private static LinkTextViewMovementMethod sInstance; // 儲存唯一的實體參考
 
		// -----物件方法-----
		/**
		 * 取得LinkTextViewMovementMethod的唯一實體參考。
		 * 
		 * @return
		 */
		public static LinkTextViewMovementMethod getInstance() {
			if (sInstance == null) {
				sInstance = new LinkTextViewMovementMethod(); // 建立新的實體
			}
			return sInstance;
		}
 
		/**
		 * 覆寫觸控事件,分辨出是否為連結的點擊。
		 */
		@Override
		public boolean onTouchEvent(TextView widget, Spannable buffer, MotionEvent event) {
			int action = event.getAction(); // 取得事件類型
 
			if (action == MotionEvent.ACTION_UP || 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);
 
				if (link.length != 0) { // 是連結點擊
					if (widget instanceof LinkTextView) { // 如果實體是LinkTextView
						((LinkTextView) widget).linkHit = true;
					}
					if (action == MotionEvent.ACTION_UP) { // 放開時
						link[0].onClick(widget); // 開啟連結
					} else if (action == MotionEvent.ACTION_DOWN) { // 按下時
						Selection.setSelection(buffer, buffer.getSpanStart(link[0]), buffer.getSpanEnd(link[0])); // 選擇連結
					}
					return true;
				} else { // 不是連結點擊
					if (widget instanceof LinkTextView) { // 如果實體是LinkTextView
						((LinkTextView) widget).linkHit = false;
					}
					Selection.removeSelection(buffer);
					Touch.onTouchEvent(widget, buffer, event);
					return false;
				}
			}
			return Touch.onTouchEvent(widget, buffer, event);
		}
	}
}

LinkTextView的使用方式和一般的TextView一樣,只不過如果要使其支援超連結功能,一定要手動指定LinkTextViewMovementMethodLinkTextView,如下:

linkTextView.setMovementMethod(LinkTextViewMovementMethod.getInstance());

 

转自:http://magiclen.org/android-html-textview/

最近在做项目研发过程中有这样一个需求:ListView的Item里的子控件TextView要设置超链接、指定文字高亮显示,然后点击超链接后跳转到指定URL的网页。实现超链接的跳转这很容易,只要通过对TextView设置SpannableString对象即可,即如下代码:
TextView tv = (TextView)findViewById(R.id.tv);
SpannableString sp = new SpannableString(“此处为tv里显示的内容”);
sp.setSpan( new URLSpan( “http://www.baidu.com” ), 3 , 8 , Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
tv.setText(sp);
tv.setMovementMethod(LinkMovementMethod.getInstance());
通过这样的设置后已经能够实现对超链接点击跳转到指定URL的网页了,可麻烦又来了,你会发现点击ListView里的Item时没有任何反应,这是什么原因呢?还记得上面在设置超链接的时候有这样一条语句:tv.setMovementMethod(LinkMovementMethod.getInstance());我们现在把这条语句注释掉看看……run App,结果点击Item的时候能够响应了,可惜超链接又失效了,任你点击也没有响应了,哎,真可谓是“鱼和熊掌不可兼得”。现在已经断定Item失效的原因何在了,那为什么这条语句就会导致Item点击失效呢?源码往往是揭开程序中各种奇芭问题的尖锐利器,让我们跟踪下源码吧……打开TextView的源码文件,截取的关键代码片段如下:
wKioL1LHkTyjKLGQAACZ9Ux9MIg927.jpg
该代码片段是TextView源码的第1421行(Android4.2.2版本),我们发现不能通过重写此方法来解决我们所面临的问题,再继续跟踪LinkMovementMethod这个类吧,关键源码如下:
@Override
public boolean onTouchEvent(TextView widget, Spannable buffer,
MotionEvent event) {
int action = event.getAction();
if (action == MotionEvent.ACTION_UP ||
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);
//获取textview里的超链接对象,并用ClickableSpan数组盛装
ClickableSpan[] link = buffer.getSpans(off, off, ClickableSpan.class);
if (link.length != 0) { //此处是对超链接点击事件进行处理……
if (action == MotionEvent.ACTION_UP) {
link[0].onClick(widget);
} else if (action == MotionEvent.ACTION_DOWN) {
Selection.setSelection(buffer,
buffer.getSpanStart(link[0]),
buffer.getSpanEnd(link[0]));
}
return true; //处理结束后返回true,这里一定要注意:当返回值为true时则表示点击事件已经被,就再也不会继续传递了
} else {
Selection.removeSelection(buffer);
}
}
return super.onTouchEvent(widget, buffer, event);
}
上述源码为LinkMovementMethod类的第188行。发现了Item点击失效的原因是因为onTouch方法总是返回true而并没有根据不同的情况来作处理,此时大家可能会想直接对这个方法重写就能解决问题了。我和大家想的也一样,对该方法重写,并根据情况返回true或false,遗憾的是还是没有效果,并且在重写的方法里看不到输出的日志信息,暂时不明其中原因,请高手赐教!眼看就要看到光明了,可还是被打击了……不要灰心,面包总是会有的,如果你不放弃的话,哈哈。换个keywords google一下,终于找到国外的一个网站,上面的文章标题是:
wKiom1LHlTzxAlYtAAA2uld9Jrk531.jpg
此时此刻的心情真是如获至宝呀……接着看下去……发现有高人是这样解决的:
wKioL1LHldywWR1tAAE4WGfZtn4031.jpg
发现这位高人本质思路是我们开始分析的本质思路一样,只不过他把要处理的代码放在了TextView的onTouch方法里,然后根据不同的情况返回true或false.在getView()方法里对相应的TextView进行设置吧,Run app……见证奇迹的时刻到了……哈哈,结果真的能行了,耶哦!
原文链接:http://stackoverflow.com/questions/8558732/listview-textview-with-linkmovementmethod-makes-list-item-unclickable
经过此劫,我发现了为什么国外的技术总是比国内要先进得多了,我在此并不是贬低同僚,不支持国产,而是在国内的技术普遍都是你抄我,我抄你的,有的甚至连原文链接都不放……大家可以看看国外这个技术网站人家是怎么解决问题的,真是按部就班,循序渐进的,不仅让你知其然还知其所以然。

There are THREE show-stoppers in this case. the root reason is that when you call setMovementMethod or setKeyListener, TextView “fixes” it’s settings:

setFocusable(true);
setClickable(true);
setLongClickable(true);
So, the first reason is that when a View is clickable – it always consumes ACTION_UP event (by returning true in onTouchEvent(MotionEvent event) ). To fix that you should return true in that method only if the user actually clicks the URL. But the LinkMovementMethod doesn’t tell us, if the user actually clicked a link. It returns “true” in it’s onTouch if the user clicks the link, but also in many other cases.

So, actually I did a hack here:

public class TextViewFixTouchConsume extends TextView {
boolean dontConsumeNonUrlClicks = true;
boolean linkHit;

public TextViewFixTouchConsume(Context context) {
super(context);
}

public TextViewFixTouchConsume(Context context, AttributeSet attrs) {
super(context, attrs);
}

public TextViewFixTouchConsume(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}

@Override
public boolean onTouchEvent(MotionEvent event) {
linkHit = false;
boolean res = super.onTouchEvent(event);

if (dontConsumeNonUrlClicks)
return linkHit;
return res;

}

public void setTextViewHTML(String html)
{
CharSequence sequence = Html.fromHtml(html);
SpannableStringBuilder strBuilder = new SpannableStringBuilder(sequence);
setText(strBuilder);
}

public static class LocalLinkMovementMethod extends LinkMovementMethod{
static LocalLinkMovementMethod sInstance;

public static LocalLinkMovementMethod getInstance() {
if (sInstance == null)
sInstance = new LocalLinkMovementMethod();

return sInstance;
}

@Override
public boolean onTouchEvent(TextView widget, Spannable buffer, MotionEvent event) {
int action = event.getAction();

if (action == MotionEvent.ACTION_UP ||
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);

if (link.length != 0) {
if (action == MotionEvent.ACTION_UP) {
link[0].onClick(widget);
} else if (action == MotionEvent.ACTION_DOWN) {
Selection.setSelection(buffer,
buffer.getSpanStart(link[0]),
buffer.getSpanEnd(link[0]));
}

if (widget instanceof TextViewFixTouchConsume){
((TextViewFixTouchConsume) widget).linkHit = true;
}
return true;
} else {
Selection.removeSelection(buffer);
Touch.onTouchEvent(widget, buffer, event);
return false;
}
}
return Touch.onTouchEvent(widget, buffer, event);
}
}
}
You should call somewhere

textView.setMovementMethod(TextViewFixTouchConsume.LocalLinkMovementMethod.getInstance());
to set this MovementMethod for the textView.

This MovementMethod raises a flag in TextViewFixTouchConsume if user actually hits link. (only in ACTION_UP and ACTION_DOWN events) and TextViewFixTouchConsume.onTouchEvent returns true only if user actually hit link.

But that’s not all!!!! The third problem is that ListView (AbsListView) calls it’s performClick method (that calls onItemClick event handler) ONLY if ListView’s item view has no focusables. So, you need to override

@Override
public boolean hasFocusable() {
return false;
}
in a view that you add to ListView. (in my case that is a layout that contains textView)

or you can use setOnClickLIstener for that view. so, theese are hacks, but they work.

更多地址参考http://stackoverflow.com/questions/8558732/listview-textview-with-linkmovementmethod-makes-list-item-unclickable

 

ClickableSpan(URLSpan)的长按处理

给TextView设置了OnLongClickListener后,如果TextView中同时有ClickableSpan(一般为URLSpan),此时长按TextView,如果长按的位置在ClickableSpan上,会发现同时触发了OnLongClickListener和ClickableSpan的onClick。比如下面的代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
final TextView tv = (TextView) findViewById(R.id.txt);
String url = “http://www.baidu.com”;
URLSpan span = new URLSpan(url);
String content = url + “Test”;
SpannableString ss = new SpannableString(content);
ss.setSpan(span, 0, url.length(), Spannable.SPAN_INCLUSIVE_INCLUSIVE);
tv.setText(ss);
tv.setMovementMethod(LinkMovementMethod.getInstance());
tv.setOnLongClickListener(new OnLongClickListener() {
@Override
public boolean onLongClick(View v) {
Toast.makeText(getApplicationContext(), “OnLongClick”, Toast.LENGTH_SHORT).show();
return true;
}
});

当在Url上长按时,会同时弹出Toast并打开指定的网页。为什么这样?当然还要从源码中查找,看下TextView的onTouchEvent函数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
@Override
public boolean onTouchEvent(MotionEvent event) {
final int action = event.getActionMasked();
if (mEditor != null) mEditor.onTouchEvent(event);
final boolean superResult = super.onTouchEvent(event); // 1
if (mEditor != null && mEditor.mDiscardNextActionUp && action == MotionEvent.ACTION_UP) {
mEditor.mDiscardNextActionUp = false;
return superResult;
}
final boolean touchIsFinished = (action == MotionEvent.ACTION_UP) &&
(mEditor == null || !mEditor.mIgnoreActionUpEvent) && isFocused();
if ((mMovement != null || onCheckIsTextEditor()) && isEnabled()
&& mText instanceof Spannable && mLayout != null) {
boolean handled = false;
if (mMovement != null) { // 2
handled |= mMovement.onTouchEvent(this, (Spannable) mText, event);
}
if (handled) {
return true;
}
}
return superResult;
}

在上面标注的第一步中,调用super.onTouchEvent,会处理长按事件。在接下来的第二步中,判断mMovement是否等于null,如果不为null,无论第一步中返回的结果是true还是false,都会调用mMovementonTouchEvent,Shit,问题就在这里了。而LinkMovementMethodonTouchEvent是这样的

1
2
3
4
5
6
if (action == MotionEvent.ACTION_UP) {
link[0].onClick(widget);
} else if (action == MotionEvent.ACTION_DOWN) {
Selection.setSelection(buffer, buffer.getSpanStart(link[0]), buffer.getSpanEnd(link[0]));
}

省略了次要部分,在ACTION_UP时就调用linkClickableSpan的onClick了,Holy Shit。

问题找到了,那么怎么解决?从上面代码中看到,在进入第二步前有一个很长的if条件判断,破坏这个条件就行了,所以可以在OnLongClickListnersetEnabled(false),在ACTION_UP中再setEnabled(true),但什么时候调用setEnabled(true)?可以设置OnTouchListener,在ACTION_UP时post一个Runnable来设置,不过个人不喜欢这个方法。

另一个办法比较取巧,你要调用onClick就调用吧,我判断一下如果触发了长按就在onClick中什么都不做不就行了,这就要求继承ClickableSpan。那么判断的方法呢?最简单的方法就是通过View.setTag,如下所示

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
tv.setOnLongClickListener(new OnLongClickListener() {
@Override
public boolean onLongClick(View v) {
Toast.makeText(getApplicationContext(), “onLongClick”, Toast.LENGTH_SHORT).show();
v.setTag(“Test”);
return true;
}
});
private static class NonLongClickableUrlSpan extends URLSpan {
public NonLongClickableUrlSpan(String url) {
super(url);
}
@Override
public void onClick(View widget) {
if (widget.getTag() != null) {
widget.setTag(null);
return;
}
super.onClick(widget);
}
}

直接通过tag判断,这个TextView的Tag已经用做其他用途了?没事,setTag还有一个重载版本:setTag(int, Object)

主要问题已经解决了,但可能还会遇到一点小问题,对于URLSpan,点击时后有个背景,有时松手后这个背景并不消失,解决方法也很简单,手动让它消失就行了,在onLongClick中添加以下代码

1
2
3
4
5
6
7
8
9
10
11
12
tv.setOnLongClickListener(new OnLongClickListener() {
@Override
public boolean onLongClick(View v) {
TextView txt = (TextView) v;
Spannable span = (Spannable) txt.getText();
Selection.removeSelection(span);
return true;
}
});

前面说了简单针对TextView的情况,再来看一下其他情况,比如说ListView,假设它是一个聊天信息列表,每一项包含一个头像和一个消息内容,我们可能希望给这个消息内容加个长按事件,而这个长按事件的范围不包括头像,所以不能使用setOnItemLongClickListener,只能给这个消息内容的布局设置OnLongClickListener。这时可能会遇到一个问题:

如果消息内容中有一个TextView包含URLSpan,当长按时点击的位置在这个URLSpan上时,可能不会触发外层Layout的长按事件,这个TextView拦截了焦点。

这时可以给这个TextView也设置一个OnLongClickListener,URLSpan的长按处理和前面讲的一样,而长按事件的处理,可以简单地调用外层Layout的performLongClick()方法。

如果这个外层布局设置了Selector Background,在长按包含URLSpan的TextView时外层Layout的背景是不会改变的,可以在这个Layout中添加android:addStatesFromChildren="true"属性解决。

android textview 自动链接网址 修改默认点击事件

1 修改XML文件即可,android:autoLink=”web” 
[code=xml”] 
<TextView 
        android:id=”@+id/text_view” 
        android:layout_width=”fill_parent” 
        android:layout_height=”wrap_content” 
        android:autoLink=”all” 
        android:text=”@string/hello” /> 

autoLink有好几种类型:web phone all等。 

2 修改链接的默认点击事件 

Java代码  收藏代码
  1. public class HtmlAllTestActivity extends Activity{  
  2.     private TextView tv;  
  3.       
  4.     @Override  
  5.     protected void onCreate(Bundle savedInstanceState) {  
  6.         super.onCreate(savedInstanceState);  
  7.         setContentView(R.layout.main);  
  8.         this.tv = (TextView)findViewById(R.id.text_view);  
  9.           
  10.         CharSequence text = tv.getText();  
  11.         if (text instanceof Spannable) {  
  12.             int end = text.length();  
  13.             Spannable sp = (Spannable) tv.getText();  
  14.             URLSpan[] urls = sp.getSpans(0, end, URLSpan.class);  
  15.             SpannableStringBuilder style = new SpannableStringBuilder(text);  
  16.             style.clearSpans();// should clear old spans  
  17.             for (URLSpan url : urls) {  
  18.                 MyURLSpan myURLSpan = new MyURLSpan(url.getURL());  
  19.                 style.setSpan(myURLSpan, sp.getSpanStart(url), sp.getSpanEnd(url), Spannable.SPAN_EXCLUSIVE_INCLUSIVE);  
  20.             }  
  21.             tv.setText(style);  
  22.         }  
  23.     }  
  24.       
  25.       
  26.     private  class MyURLSpan extends ClickableSpan {  
  27.   
  28.         private String mUrl;  
  29.   
  30.         MyURLSpan(String url) {  
  31.             mUrl = url;  
  32.         }  
  33.   
  34.         @Override  
  35.         public void onClick(View widget) {  
  36.             Toast.makeText(HtmlAllTestActivity.this, mUrl, Toast.LENGTH_LONG).show();  
  37.             widget.setBackgroundColor(Color.parseColor(“#00000000”));  
  38.         }  
  39.     }  
  40. }  
  41. 原文链接:http://dingbuoyi.iteye.com/blog/1553464

所以我通过以下方法控制textview的超链触发,总体思路是通过ontouch事件阻止超链的onclick事件。主要是因为长按事件在onclick事件之前完成,说白了就是MotionEvent.ACTION_DOWN和MotionEvent.ACTION_UP出现的前后顺序不同,控制是否消化掉事件。

通过修改textview的长按和touch事件,实现在点击了长按事件以后,防止触发超链接的link事件。

textview.setOnLongClickListener(new View.OnLongClickListener() {

            @Override
            public boolean onLongClick(View v) {
                isLongClick= true;
                return false;
            }
        });
        textview.setOnTouchListener(new View.OnTouchListener() {

            @Override
            public boolean onTouch(View v, MotionEvent event) {
                if(event.getAction() == MotionEvent.ACTION_UP && isLongClick){
                    isLongClick= false;
                    return true;
                }
                if(event.getAction() == MotionEvent.ACTION_DOWN){
                    isLongClick= false;
                }
                return v.onTouchEvent(event);
            }

        });


控制textview的超链的长按和点击事件都执行的解决方案:现在2种方法解决,1是通过ontouch事键和长按事件逻辑判断控制。2是通过修改点击事件中的
MyURLSpan extends ClickableSpan重新点击事件。
下面是我用自定义LinkedTextView,使他能够在ListView中的item点击事件起作用。

public class LinkTextView extends TextView {
 // 如果执行了长按
 boolean isOnLongClickRun = false;
 // boolean dontConsumeNonUrlClicks = true;
 boolean linkHit;

 public LinkTextView(Context context) {
 super(context);
 }

 public LinkTextView(Context context, AttributeSet attrs) {
 super(context, attrs);
 }

 public LinkTextView(Context context, AttributeSet attrs, int defStyle) {
 super(context, attrs, defStyle);
 }
 @Override
 public boolean hasFocusable() {
 return false;
 }

 public void setTextViewHTML(String html) {
 CharSequence sequence = Html.fromHtml(html);
 SpannableStringBuilder strBuilder = new SpannableStringBuilder(sequence);
 setText(strBuilder);
 }

 public static class LocalLinkMovementMethod extends LinkMovementMethod {
 static LocalLinkMovementMethod sInstance;

 public static LocalLinkMovementMethod getInstance() {
 if (sInstance == null)
 sInstance = new LocalLinkMovementMethod();

 return sInstance;
 }

 @Override
 public boolean onTouchEvent(TextView widget, Spannable buffer,
 MotionEvent event) {
 int action = event.getAction();
 if (action == MotionEvent.ACTION_UP
 || 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);
 if (link.length != 0) {
 if (action == MotionEvent.ACTION_UP) {
 link[0].onClick(widget);
 } else if (action == MotionEvent.ACTION_DOWN) {
 Selection.setSelection(buffer,
 buffer.getSpanStart(link[0]),
 buffer.getSpanEnd(link[0]));
 }

 if (widget instanceof LinkTextView) {
 ((LinkTextView) widget).linkHit = true;
 }
 return true;
 } else {
 Selection.removeSelection(buffer);
 Touch.onTouchEvent(widget, buffer, event);
 return false;
 }
 }
 return Touch.onTouchEvent(widget, buffer, event);
 }
 }
}

Share this:

码字很辛苦,转载请注明来自技术联盟《在Android實作HTML TextView與AutoLink使用的建議方式》

评论