需要解决以下问题:
- 链接TextView
- 找到一种方法来监听对 TextView 中链接的点击
- 获取点击链接的url并加载到WebView中
- 可选:使 TextView 可点击而不失去选择文本的能力
- 可选:在 TextView 中处理格式化文本(不同的文本大小和样式)
#1 链接 TextView
这是最简单的问题,你已经解决了。我建议这样做:
String text = "These are some sample links:\nwww.google.com\nwww.facebook.com\nwww.yahoo.com";
Spannable spannable = new SpannableString( Html.fromHtml(text) );
Linkify.addLinks(spannable, Linkify.WEB_URLS);
我在这里使用 Spannable 来解决问题 #2。
#2 + #3 监听链接点击并在 WebView 中打开它们
要找出链接何时被点击并检索我们必须打开的 URL,我们将 TextView 中的所有 URLSpan 替换为我们的 LinkSpan(这就是我们需要 Spannable 的原因):
URLSpan[] spans = spannable.getSpans(0, spannable.length(), URLSpan.class);
for (URLSpan urlSpan : spans) {
LinkSpan linkSpan = new LinkSpan(urlSpan.getURL());
int spanStart = spannable.getSpanStart(urlSpan);
int spanEnd = spannable.getSpanEnd(urlSpan);
spannable.setSpan(linkSpan, spanStart, spanEnd, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
spannable.removeSpan(urlSpan);
}
我们的 LinkSpan 只是抓取点击的 url 并在 WebView 中打开它:
private class LinkSpan extends URLSpan {
private LinkSpan(String url) {
super(url);
}
@Override
public void onClick(View view) {
String url = getURL();
if (mWebView != null && url != null) {
mWebView.loadUrl(url);
}
}
}
现在显然我们必须在实例变量中保留对 WebView 的引用才能使其工作。为了使这个答案尽可能简短,我选择将 LinkSpan 定义为内部类,但我建议将其定义为顶级。注册一个监听器或将 WebView 作为参数传递给构造函数。
如果没有将 MovementMethod 设置为 LinkMovementMethod,TextView 根本不会打开链接:
tv.setMovementMethod(LinkMovementMethod.getInstance());
tv.setText(spannable, BufferType.SPANNABLE);
最后但同样重要的是,让我们确保 WebView 不会启动浏览器,而是在应用程序中加载页面:
mWebView.setWebViewClient(new WebViewClient() {
@Override
public boolean shouldOverrideUrlLoading(WebView view, String url) {
// we handle the url ourselves if it's a network url (http / https)
return ! URLUtil.isNetworkUrl(url);
}
});
#4 可点击和可选择的TextView + #5 格式化文本
如果将 MovementMethod 设置为 LinkMovementMethod,您可以单击链接,但不能再选择文本(您需要 ArrowKeyMovementMethod)。为了解决这个问题,我创建了一个继承自 ArrowKeyMovementMethod 的自定义 MovementMethod 类,并添加了单击链接的功能。最重要的是,它能够处理格式化的文本。因此,如果您决定在 TextView 中使用不同的字体大小和样式,下面的 MovementMethod 将覆盖它(也适用于 EditTexts):
/**
* ArrowKeyMovementMethod does support selection of text but not the clicking of links.
* LinkMovementMethod does support clicking of links but not the selection of text.
* This class adds the link clicking to the ArrowKeyMovementMethod.
* We basically take the LinkMovementMethod onTouchEvent code and remove the line
* Selection.removeSelection(buffer);
* which deselects all text when no link was found.
*/
public class EnhancedLinkMovementMethod extends ArrowKeyMovementMethod {
private static EnhancedLinkMovementMethod sInstance;
private static Rect sLineBounds = new Rect();
public static MovementMethod getInstance() {
if (sInstance == null) {
sInstance = new EnhancedLinkMovementMethod();
}
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 index = getCharIndexAt(widget, event);
if (index != -1) {
ClickableSpan[] link = buffer.getSpans(index, index, 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;
}
}
/*else {
Selection.removeSelection(buffer);
}*/
}
return super.onTouchEvent(widget, buffer, event);
}
private int getCharIndexAt(TextView textView, MotionEvent event) {
// get coordinates
int x = (int) event.getX();
int y = (int) event.getY();
x -= textView.getTotalPaddingLeft();
y -= textView.getTotalPaddingTop();
x += textView.getScrollX();
y += textView.getScrollY();
/*
* Fail-fast check of the line bound.
* If we're not within the line bound no character was touched
*/
Layout layout = textView.getLayout();
int line = layout.getLineForVertical(y);
synchronized (sLineBounds) {
layout.getLineBounds(line, sLineBounds);
if (! sLineBounds.contains(x, y)) {
return -1;
}
}
// retrieve line text
Spanned text = (Spanned) textView.getText();
int lineStart = layout.getLineStart(line);
int lineEnd = layout.getLineEnd(line);
int lineLength = lineEnd - lineStart;
if (lineLength == 0) {
return -1;
}
Spanned lineText = (Spanned) text.subSequence(lineStart, lineEnd);
// compute leading margin and subtract it from the x coordinate
int margin = 0;
LeadingMarginSpan[] marginSpans = lineText.getSpans(0, lineLength, LeadingMarginSpan.class);
if (marginSpans != null) {
for (LeadingMarginSpan span : marginSpans) {
margin += span.getLeadingMargin(true);
}
}
x -= margin;
// retrieve text widths
float[] widths = new float[lineLength];
TextPaint paint = textView.getPaint();
paint.getTextWidths(lineText, 0, lineLength, widths);
// scale text widths by relative font size (absolute size / default size)
final float defaultSize = textView.getTextSize();
float scaleFactor = 1f;
AbsoluteSizeSpan[] absSpans = lineText.getSpans(0, lineLength, AbsoluteSizeSpan.class);
if (absSpans != null) {
for (AbsoluteSizeSpan span : absSpans) {
int spanStart = lineText.getSpanStart(span);
int spanEnd = lineText.getSpanEnd(span);
scaleFactor = span.getSize() / defaultSize;
int start = Math.max(lineStart, spanStart);
int end = Math.min(lineEnd, spanEnd);
for (int i = start; i < end; i++) {
widths[i] *= scaleFactor;
}
}
}
// find index of touched character
float startChar = 0;
float endChar = 0;
for (int i = 0; i < lineLength; i++) {
startChar = endChar;
endChar += widths[i];
if (endChar >= x) {
// which "end" is closer to x, the start or the end of the character?
int index = lineStart + (x - startChar < endChar - x ? i : i + 1);
//Logger.e(Logger.LOG_TAG, "Found character: " + (text.length()>index ? text.charAt(index) : ""));
return index;
}
}
return -1;
}
}
完整的活动代码
这是我使用的完整示例活动代码。它应该完全符合您的要求。它使用的是我的 EnhandedMovementMethod,但您可以使用简单的 LinkMovementMethod(具有前面提到的缺点)。
public class LinkTestActivity extends Activity {
private WebView mWebView;
@SuppressLint("SetJavaScriptEnabled")
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mWebView = (WebView) findViewById(R.id.webView);
TextView tv = (TextView) findViewById(R.id.textView);
String text = "These are some sample links:\nwww.google.com\nwww.facebook.com\nwww.yahoo.com";
// Linkify the TextView
Spannable spannable = new SpannableString( Html.fromHtml(text) );
Linkify.addLinks(spannable, Linkify.WEB_URLS);
// Replace each URLSpan by a LinkSpan
URLSpan[] spans = spannable.getSpans(0, spannable.length(), URLSpan.class);
for (URLSpan urlSpan : spans) {
LinkSpan linkSpan = new LinkSpan(urlSpan.getURL());
int spanStart = spannable.getSpanStart(urlSpan);
int spanEnd = spannable.getSpanEnd(urlSpan);
spannable.setSpan(linkSpan, spanStart, spanEnd, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
spannable.removeSpan(urlSpan);
}
// Make sure the TextView supports clicking on Links
tv.setMovementMethod(EnhancedLinkMovementMethod.getInstance());
tv.setText(spannable, BufferType.SPANNABLE);
// Make sure we handle clicked links ourselves
mWebView.setWebViewClient(new WebViewClient() {
@Override
public boolean shouldOverrideUrlLoading(WebView view, String url) {
// we handle the url ourselves if it's a network url (http / https)
return ! URLUtil.isNetworkUrl(url);
}
});
mWebView.getSettings().setJavaScriptEnabled(true);
mWebView.getSettings().setSupportZoom(true);
mWebView.getSettings().setBuiltInZoomControls(true);
}
private class LinkSpan extends URLSpan {
private LinkSpan(String url) {
super(url);
}
@Override
public void onClick(View view) {
String url = getURL();
if (mWebView != null && url != null) {
mWebView.loadUrl(url);
}
}
}
}