android实现富文本
16lz
2021-01-24
android要实现富文本有一下几种方法:
- Spannable
- Html.fromHtml
- 第三方库(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; }
更多相关文章
- Android中的接口的使用举例
- 解决Mac上adb: command not found问题
- android 实现在文本内容超过固定宽度可手动左右滚动查看效果
- Android自带音乐播放器代码分析(2)
- Android中Intent传递类对象的两种方式
- Android双屏异显另辟蹊径---minui的移植
- 详解Android解析Xml的三种方式——DOM、SAX以及XMLpull
- Android(安卓)-- 序列化Parcelable与Serializable区别及用法
- 【Android代码片段之六】Toast工具类(实现带图片的Toast消息提示)