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());