Android的TextView除了可以用來顯示文字資料之外,還可以使用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="https://magiclen.org/">https://magiclen.org/</a>"));

結果如下:

啟用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屬性的,還再指定LinkMovementMethod給TextView:

textView.setMovementMethod(LinkMovementMethod.getInstance());

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

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

以下舉個例子:

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

結果如下:

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

要讓TextView擁有超連結功能,必須要指定LinkMovementMethod給TextView。然而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命名為LinkTextView,LinkMovementMethod命名為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一樣,只不過如果要使其支援超連結功能,一定要手動指定LinkTextViewMovementMethod給LinkTextView,如下:

linkTextView.setMovementMethod(LinkTextViewMovementMethod.getInstance());