以下问题需要解决:
- 将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,我们需要用LinkSpan替换TextView中的所有URLSpans(这就是为什么我们需要一个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 可点击和可选择的文本视图 + #5 格式化文本
如果 MovementMethod 被设置为 LinkMovementMethod,你可以点击链接,但无法再选择文本(你需要使用 ArrowKeyMovementMethod 来实现)。为了解决这个问题,我创建了一个自定义的 MovementMethod 类,继承自 ArrowKeyMovementMethod,并添加了点击链接的功能。此外,它还能处理格式化文本。因此,如果您决定在 TextView 中使用不同的字体大小和样式,下面的 MovementMethod 将可以胜任(同样适用于 EditText):
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;
}
}
}
return super.onTouchEvent(widget, buffer, event);
}
private int getCharIndexAt(TextView textView, MotionEvent event) {
int x = (int) event.getX();
int y = (int) event.getY();
x -= textView.getTotalPaddingLeft();
y -= textView.getTotalPaddingTop();
x += textView.getScrollX();
y += textView.getScrollY();
Layout layout = textView.getLayout();
int line = layout.getLineForVertical(y);
synchronized (sLineBounds) {
layout.getLineBounds(line, sLineBounds);
if (! sLineBounds.contains(x, y)) {
return -1;
}
}
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);
int margin = 0;
LeadingMarginSpan[] marginSpans = lineText.getSpans(0, lineLength, LeadingMarginSpan.class);
if (marginSpans != null) {
for (LeadingMarginSpan span : marginSpans) {
margin += span.getLeadingMargin(true);
}
}
x -= margin;
float[] widths = new float[lineLength];
TextPaint paint = textView.getPaint();
paint.getTextWidths(lineText, 0, lineLength, widths);
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;
}
}
}
float startChar = 0;
float endChar = 0;
for (int i = 0; i < lineLength; i++) {
startChar = endChar;
endChar += widths[i];
if (endChar >= x) {
int index = lineStart + (x - startChar < endChar - x ? i : i + 1);
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";
Spannable spannable = new SpannableString( Html.fromHtml(text) );
Linkify.addLinks(spannable, Linkify.WEB_URLS);
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);
}
tv.setMovementMethod(EnhancedLinkMovementMethod.getInstance());
tv.setText(spannable, BufferType.SPANNABLE);
mWebView.setWebViewClient(new WebViewClient() {
@Override
public boolean shouldOverrideUrlLoading(WebView view, String url) {
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);
}
}
}
}