在Lollipop上打破的TextView fromHtml链接

时间:2015-02-28 13:51:32

标签: android spannablestring

我们的应用程序有几个TextViews实例,其内容由myTv.setText(Html.fromHtml());设置,适用于Android 4.4.0及更低版本。

从4.4.2和Lollypop开始,这些链接已停止工作。文本仍然带有下划线和超链接颜色,但点击它们不会产生任何结果。

必须要说的是,这些字段被标记为可复制粘贴,已知这些字段与这些字段相互作用。

有没有人能够解决这个问题?

4 个答案:

答案 0 :(得分:2)

问题在于,当在TextView中启用复制和粘贴时,Android将使用ArrowKeyMovementMethod,它支持选择文本但不支持单击链接。当您使用LinkMovementMethod时,您可以单击链接但不能选择文本(无论您是在Lollipop,KitKat还是较低的Android版本)。

为了解决这个问题,我扩展了ArrayKeyMovementMethod类并使用LinkMovementMethod onTouchEvent覆盖了onTouchEvent。为了允许文本选择,我必须删除三行代码。由于我在具有大量文本格式的富文本编辑器中使用该类,因此我还添加了逻辑来查找所单击的字符,而不管文本大小,缩进或文本对齐。如果您希望使用纯文本的简单解决方案,请在自定义ArrowKeyMovementMethod类中使用它:

@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]));
            }

            return true;
        }
        /* These are the lines of code you want to remove
        else {
            Selection.removeSelection(buffer);
        }*/
    }

    return super.onTouchEvent(widget, buffer, event);
}
别忘了打电话: myTv.setMovementMethod(new ClickAndSelectMovementMethod());

如果您希望支持各种文本格式的版本使用此代码:

import android.graphics.Rect;
import android.text.Layout;
import android.text.Selection;
import android.text.Spannable;
import android.text.Spanned;
import android.text.TextPaint;
import android.text.method.ArrowKeyMovementMethod;
import android.text.method.MovementMethod;
import android.text.style.AbsoluteSizeSpan;
import android.text.style.ClickableSpan;
import android.text.style.LeadingMarginSpan;
import android.view.MotionEvent;
import android.widget.TextView;

/**
 * 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 de-selects
 * all text when no link was found.
 */
public class ClickAndSelectMovementMethod extends ArrowKeyMovementMethod {

    private static Rect sLineBounds = new Rect();

    @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);
                return index;
            }
        }

        return -1;
    }
}

答案 1 :(得分:1)

默认情况下,素材ButtonsTextviews的样式设置为以全大写字母显示文字。但是,AllCapsTransformationMethod中存在导致丢弃其他文本格式的错误,例如。 Spannable。 因此,当您尝试在Lollipop上更改Button的字体大小时,如下所示:

SpannableString span = new SpannableString(text);
span.setSpan(new AbsoluteSizeSpan(8, true), 5, 10, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
testButton.setText(span);

它也不会起作用(仅适用于Lollipop)。

<强>解决方案

针对您的案例的解决方法和描述的Spannable案例已设置为 textAllCaps false

<TextView
...
android:textAllCaps="false" />

答案 2 :(得分:0)

解决方案

final Spanned spanned = Html.fromHtml("<a href='http://google.com'>My link</a>");

textView.setText(spanned);
textView.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View v) {
        ClickableSpan[] links = spanned.getSpans(0, spanned.length(), ClickableSpan.class);

        if (links.length > 0) {
            links[0].onClick(v);
        }
    }
});

通过这种方式,您可以通过长按选择文本,只需单击即可打开链接。您可以实现更高级的实现,以允许双击文本选择。

问题

问题似乎存在于以下代码中。它是默认TextView onTouchEvent的一部分:

if (touchIsFinished && mLinksClickable && mAutoLinkMask != 0 && textIsSelectable) {
    // The LinkMovementMethod which should handle taps on links has not been installed
    // on non editable text that support text selection.
    // We reproduce its behavior here to open links for these.
    ClickableSpan[] links = ((Spannable) mText).getSpans(getSelectionStart(),
            getSelectionEnd(), ClickableSpan.class);

    if (links.length > 0) {
        links[0].onClick(this);
        handled = true;
    }
}

我们可以看到if块的运行条件之一是mAutoLinkMask != 0

如果您将android:autoLink更改为默认值0或none以外的值,则if块会运行但没有链接。

我想这是开发人员错过的,应该在我看来是一个错误。

旁注

如果您只需要http://google.com之类的简单链接而不更改其外观,则可以在android:autoLink="web"上设置TextView并完成。链接将自动找到并可以点击。但这不适用于<a href="..."></a>类型的链接。

答案 3 :(得分:0)

试试这个,它对我来说很好。我从我的资产文件夹加载html文件以满足我的要求,但你只需要设置textview的所有可链接属性,它就可以了......它会起作用......

<TextView
        android:id="@+id/txt_terms_and_conditions"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:text="@string/hello_world"
        android:linksClickable="true"
        android:autoLink="phone|email|web" />

并在代码中尝试设置为像这样的HTML

mTxtTearmsAndConditions.setText(Html.fromHtml(total.toString()));