android要实现富文本有一下几种方法:

  1. Spannable
  2. Html.fromHtml
  3. 第三方库(RichText)   GitHub地址:https://github.com/zzhoujay/RichText

这里简单分析一下SpannableUtils和Html.fromHtml的实现

一、SpannableStringBuilder分析

要用Spannable离不开SpannableStringBuilder

首先看他的源码:实现GraphicsOperations接口,实现此接口CharSequence可以快速绘制/测量/宽度

public class SpannableStringBuilder implements CharSequence, GetChars, Spannable, Editable,        Appendable, GraphicsOperations {    

SpannableStringBuilder有一个setSpan方法,作用是设置Spannable

为什么TextView设置text为Spannable就可以实现富文本呢?

再看看TextView的setText()方法

 private void setText(CharSequence text, BufferType type,                         boolean notifyBefore, int oldlen) {        mTextFromResource = false;        if (text == null) {            text = "";        }        // If suggestions are not enabled, remove the suggestion spans from the text        if (!isSuggestionsEnabled()) {            text = removeSuggestionSpans(text);        }        if (!mUserSetTextScaleX) mTextPaint.setTextScaleX(1.0f);        if (text instanceof Spanned                && ((Spanned) text).getSpanStart(TextUtils.TruncateAt.MARQUEE) >= 0) {            if (ViewConfiguration.get(mContext).isFadingMarqueeEnabled()) {                setHorizontalFadingEdgeEnabled(true);                mMarqueeFadeMode = MARQUEE_FADE_NORMAL;            } else {                setHorizontalFadingEdgeEnabled(false);                mMarqueeFadeMode = MARQUEE_FADE_SWITCH_SHOW_ELLIPSIS;            }            setEllipsize(TextUtils.TruncateAt.MARQUEE);        }        int n = mFilters.length;        for (int i = 0; i < n; i++) {            CharSequence out = mFilters[i].filter(text, 0, text.length(), EMPTY_SPANNED, 0, 0);            if (out != null) {                text = out;            }        }        if (notifyBefore) {            if (mText != null) {                oldlen = mText.length();                sendBeforeTextChanged(mText, 0, oldlen, text.length());            } else {                sendBeforeTextChanged("", 0, 0, text.length());            }        }        boolean needEditableForNotification = false;        if (mListeners != null && mListeners.size() != 0) {            needEditableForNotification = true;        }        if (type == BufferType.EDITABLE || getKeyListener() != null                || needEditableForNotification) {            createEditorIfNeeded();            mEditor.forgetUndoRedo();            Editable t = mEditableFactory.newEditable(text);            text = t;            setFilters(t, mFilters);            InputMethodManager imm = InputMethodManager.peekInstance();            if (imm != null) imm.restartInput(this);        } else if (type == BufferType.SPANNABLE || mMovement != null) {            text = mSpannableFactory.newSpannable(text);        } else if (!(text instanceof CharWrapper)) {            text = TextUtils.stringOrSpannedString(text);        }        if (mAutoLinkMask != 0) {            Spannable s2;            if (type == BufferType.EDITABLE || text instanceof Spannable) {                s2 = (Spannable) text;            } else {                s2 = mSpannableFactory.newSpannable(text);            }            if (Linkify.addLinks(s2, mAutoLinkMask)) {                text = s2;                type = (type == BufferType.EDITABLE) ? BufferType.EDITABLE : BufferType.SPANNABLE;                /*                 * We must go ahead and set the text before changing the                 * movement method, because setMovementMethod() may call                 * setText() again to try to upgrade the buffer type.                 */                mText = text;                // Do not change the movement method for text that support text selection as it                // would prevent an arbitrary cursor displacement.                if (mLinksClickable && !textCanBeSelected()) {                    setMovementMethod(LinkMovementMethod.getInstance());                }            }        }        mBufferType = type;        mText = text;        if (mTransformation == null) {            mTransformed = text;        } else {            mTransformed = mTransformation.getTransformation(text, this);        }        final int textLength = text.length();        if (text instanceof Spannable && !mAllowTransformationLengthChange) {            Spannable sp = (Spannable) text;            // Remove any ChangeWatchers that might have come from other TextViews.            final ChangeWatcher[] watchers = sp.getSpans(0, sp.length(), ChangeWatcher.class);            final int count = watchers.length;            for (int i = 0; i < count; i++) {                sp.removeSpan(watchers[i]);            }            if (mChangeWatcher == null) mChangeWatcher = new ChangeWatcher();            sp.setSpan(mChangeWatcher, 0, textLength, Spanned.SPAN_INCLUSIVE_INCLUSIVE                    | (CHANGE_WATCHER_PRIORITY << Spanned.SPAN_PRIORITY_SHIFT));            if (mEditor != null) mEditor.addSpanWatchers(sp);            if (mTransformation != null) {                sp.setSpan(mTransformation, 0, textLength, Spanned.SPAN_INCLUSIVE_INCLUSIVE);            }            if (mMovement != null) {                mMovement.initialize(this, (Spannable) text);                /*                 * Initializing the movement method will have set the                 * selection, so reset mSelectionMoved to keep that from                 * interfering with the normal on-focus selection-setting.                 */                if (mEditor != null) mEditor.mSelectionMoved = false;            }        }        if (mLayout != null) {            checkForRelayout();        }        sendOnTextChanged(text, 0, oldlen, textLength);        onTextChanged(text, 0, oldlen, textLength);        notifyViewAccessibilityStateChangedIfNeeded(AccessibilityEvent.CONTENT_CHANGE_TYPE_TEXT);        if (needEditableForNotification) {            sendAfterTextChanged((Editable) text);        } else {            // Always notify AutoFillManager - it will return right away if autofill is disabled.            notifyAutoFillManagerAfterTextChangedIfNeeded();        }        // SelectionModifierCursorController depends on textCanBeSelected, which depends on text        if (mEditor != null) mEditor.prepareCursorControllers();    }

代码中很清晰的看到CharSequence为Spannable 时同样可添加到text中,具体它是怎么绘制这里就不分析,想了解的话可以看一下TextView的绘制。

 

二、Html.fromHtml分析

public static Spanned fromHtml(String source, Html.ImageGetter imageGetter,                                   Html.TagHandler tagHandler) {        //创建HTML解析器        Parser parser = new Parser();        try {            parser.setProperty(Parser.schemaProperty, HtmlParser.schema);        } catch (org.xml.sax.SAXNotRecognizedException e) {            // Should not happen.            throw new RuntimeException(e);        } catch (org.xml.sax.SAXNotSupportedException e) {            // Should not happen.            throw new RuntimeException(e);        }        //HTML数据转换成span        HtmlToSpannedConverter converter =                new HtmlToSpannedConverter(source, imageGetter, tagHandler,                        parser);        return converter.convert();    }
 private void handleStartTag(String tag, Attributes attributes) {        if (tag.equalsIgnoreCase("br")) {            // We don't need to handle this. TagSoup will ensure that there's a 
for each
// so we can safely emite the linebreaks when we handle the close tag. } else if (tag.equalsIgnoreCase("p")) { handleP(mSpannableStringBuilder); } else if (tag.equalsIgnoreCase("div")) { handleP(mSpannableStringBuilder); } else if (tag.equalsIgnoreCase("em")) { start(mSpannableStringBuilder, new Bold()); } else if (tag.equalsIgnoreCase("b")) { start(mSpannableStringBuilder, new Bold()); } else if (tag.equalsIgnoreCase("strong")) { start(mSpannableStringBuilder, new Italic()); } else if (tag.equalsIgnoreCase("cite")) { start(mSpannableStringBuilder, new Italic()); } else if (tag.equalsIgnoreCase("dfn")) { start(mSpannableStringBuilder, new Italic()); } else if (tag.equalsIgnoreCase("i")) { start(mSpannableStringBuilder, new Italic()); } else if (tag.equalsIgnoreCase("big")) { start(mSpannableStringBuilder, new Big()); } else if (tag.equalsIgnoreCase("small")) { start(mSpannableStringBuilder, new Small()); } else if (tag.equalsIgnoreCase("font")) { startFont(mSpannableStringBuilder, attributes); } else if (tag.equalsIgnoreCase("blockquote")) { handleP(mSpannableStringBuilder); start(mSpannableStringBuilder, new Blockquote()); } *****省略代码}
 private static void startFont(SpannableStringBuilder text,                                  Attributes attributes) {        String color = attributes.getValue("", "color");        String face = attributes.getValue("", "face");        int len = text.length();        text.setSpan(new Font(color, face), len, len, Spannable.SPAN_MARK_MARK);    }

原来Html.fromHtml也是通过spannable来实现富文本

 

问题延伸:

若文本中存在:右到左的语言(如阿拉伯语),这时候会导致文本顺序错乱

解决方案:

在文本最前面创建一个透明占位符:

 /**     * 消息占位符     *     * @param msg     * @return     */    public static SpannableStringBuilder placeHolderTextSp(String msg) {        final SpannableStringBuilder mStringBuilder = new SpannableStringBuilder(msg);        mStringBuilder.setSpan(new ForegroundColorSpan(Color.TRANSPARENT), 0, msg.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);        return mStringBuilder;    }

 

 

更多相关文章

  1. Android中的接口的使用举例
  2. 解决Mac上adb: command not found问题
  3. android 实现在文本内容超过固定宽度可手动左右滚动查看效果
  4. Android自带音乐播放器代码分析(2)
  5. Android中Intent传递类对象的两种方式
  6. Android双屏异显另辟蹊径---minui的移植
  7. 详解Android解析Xml的三种方式——DOM、SAX以及XMLpull
  8. Android(安卓)-- 序列化Parcelable与Serializable区别及用法
  9. 【Android代码片段之六】Toast工具类(实现带图片的Toast消息提示)

随机推荐

  1. 源码分析Android启动流程
  2. Android_AndroidManifest.xml
  3. Android Studio开发笔记
  4. 2014.07.23(2) ——— android FragmentPag
  5. 【Android开发学习08】SurfaceView显示动
  6. 史上最强NDK入门项目实战
  7. 从android 4.1.1 到android 4.2 telephon
  8. java算法之去重查找重复元素
  9. Android中BroadCast与Activity之间的通信
  10. Android(安卓)交叉编译