Android富文本實現(xiàn)的幾種方式匯總
Android富文本的實現(xiàn)的幾種方式
在Android開發(fā)過程中,最常見的富文本場景一般都是變色,點擊跳轉(zhuǎn),或者局部變大,而我們實現(xiàn)的方式通常分為兩種。
一種是Html的方式定義在string中,通過html標(biāo)簽變色,變大,通過占位符填充數(shù)據(jù)。一般常用于有國際化的需求。
另一種是CharSequence的setSpan設(shè)置自定義Span。功能更強大,細讀也更細,便于精準(zhǔn)操作。一般用于沒有國際化需求的地方。
為什么有國際化相關(guān)的要求,是因為一般setSpan的方式都是添加或者根據(jù)索引替換對應(yīng)的文本,如果國際化之后中英馬等語言的順序都變了,自然效果就不同了。當(dāng)然也可以通過判斷語言進行不同的操作。這是后話了。
一,Html的方式實現(xiàn)
1.1 占位符的處理
先看看string xml中如何處理占位符 %N代表第N個參數(shù),如%3代表的是第三個參數(shù); $是結(jié)束符;
<string name="string_test_1">學(xué)號:%1$d ;姓名:%2$s ;成績:%3$.2f</string>
使用的時候:
String testStr = getResources().getString(R.string.string_test_1); String result = String.format(testStr,1001,"張三",9.235); System.out.println(result);
1.2 Html的占位符
和上面的差不多:
<string name="purchase_points"><![CDATA[ <font color="#767676">Purchase with</font> <font color="#FF5E75">%s</font><font color="#767676"> points?</font>]]></string>
使用:
String formatPoints = PointFormatUtils.formatPoints(points); String result = String.format(getResources().getString(R.string.purchase_points),formatPoints); tv_message.setText(Html.fromHtml(result));
注:Html.fromHtml還分Android N的兼容處理,需要傳入Model,不同的Model展示的效果有所不同,這里不做展開。其實效果大差不差。
實現(xiàn)效果:
結(jié)論:
能實現(xiàn)變色,簡單的變大等簡單功能,由于TextView不能解析更多的Html標(biāo)簽,由此還出現(xiàn)了一些庫,讓TextView支持更多標(biāo)簽,但是我們Android實現(xiàn)富文本本身就是小功能,還得依賴庫支持更多標(biāo)簽也都用不上,得不償失啊。
如果有一些自定義的需求,我們可以使用自定義標(biāo)簽+自定義標(biāo)簽的功能,例如Html中的自定義字體
1.3 自定義Html標(biāo)簽
先定義自定義字體的Span類
/** * 系統(tǒng)原生的TypefaceSpan只能使用原生的默認(rèn)字體 * 如果使用自定義的字體,通過這個來實現(xiàn) */ public class MyTypefaceSpan extends MetricAffectingSpan { private final Typeface typeface; public MyTypefaceSpan(final Typeface typeface) { this.typeface = typeface; } @Override public void updateDrawState(final TextPaint drawState) { apply(drawState); } @Override public void updateMeasureState(final TextPaint paint) { apply(paint); } private void apply(final Paint paint) { final Typeface oldTypeface = paint.getTypeface(); final int oldStyle = oldTypeface != null ? oldTypeface.getStyle() : 0; int fakeStyle = oldStyle & ~typeface.getStyle(); if ((fakeStyle & Typeface.BOLD) != 0) { paint.setFakeBoldText(true); } if ((fakeStyle & Typeface.ITALIC) != 0) { paint.setTextSkewX(-0.25f); } paint.setTypeface(typeface); } }
自定義標(biāo)簽:
/** * Html的TextView標(biāo)簽解釋 * <face></face> */ public class TypeFaceLabel implements Html.TagHandler { private Typeface typeface; private int startIndex = 0; private int stopIndex = 0; public TypeFaceLabel(Typeface typeface) { this.typeface = typeface; } @Override public void handleTag(boolean opening, String tag, Editable output, XMLReader xmlReader) { if (tag.toLowerCase().equals("face")) { if (opening) { startIndex = output.length(); } else { stopIndex = output.length(); //使用的是自定義的字體來實現(xiàn) output.setSpan(new MyTypefaceSpan(typeface), startIndex, stopIndex, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); } } } }
定義Xml并使用,注意自定義face標(biāo)簽
String content = "<font color=\"#000000\">HR from </font>" + "<face><font color=\"#0689FB\">" + item.employer_name + "</font></face>" + "<font color=\"#000000\"> has viewed your resume.</font>"; if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.N) { tv_resume_log_content.setText(Html.fromHtml(content, Html.FROM_HTML_MODE_LEGACY, null, new TypeFaceLabel(TypefaceUtil.getSFSemobold(mContext)))); } else { tv_resume_log_content.setText(Html.fromHtml(content, null, new TypeFaceLabel(TypefaceUtil.getSFSemobold(mContext)))); }
效果如下:
如果想實現(xiàn)其他的變大 下劃線 中劃線等Span效果,都可以通過自定義的Html標(biāo)簽+自定義Span實現(xiàn)相應(yīng)的效果。
二,Span的幾種實現(xiàn)方式
雖然通過Html的方式可以實現(xiàn)各種效果,但是定義的時候也太過復(fù)雜,各種定義Span 定義標(biāo)簽之類的,有沒有更簡單和直接的?
有,我們直接封裝Span就行了。
2.1 java - SpanUtil
在Java中我們可以封裝工具類一個如下:
/** * String字符串通過區(qū)間來改變顏色,大小,字體,下劃線等 */ public class SpanUtils { private static final SpanUtils ourInstance = new SpanUtils(); public static SpanUtils getInstance() { return ourInstance; } private SpanUtils() { } /** * 變大變小 */ public CharSequence toSizeSpan(CharSequence charSequence, int start, int end, float scale) { SpannableString spannableString = new SpannableString(charSequence); spannableString.setSpan( new RelativeSizeSpan(scale), start, end, Spannable.SPAN_INCLUSIVE_EXCLUSIVE); return spannableString; } /** * 變色 */ public CharSequence toColorSpan(CharSequence charSequence, int start, int end, int color) { SpannableString spannableString = new SpannableString(charSequence); spannableString.setSpan( new ForegroundColorSpan(color), start, end, Spannable.SPAN_INCLUSIVE_EXCLUSIVE); return spannableString; } /** * 變背景色 */ public CharSequence toBackgroundColorSpan(CharSequence charSequence, int start, int end, int color) { SpannableString spannableString = new SpannableString(charSequence); spannableString.setSpan( new BackgroundColorSpan(color), start, end, Spannable.SPAN_INCLUSIVE_EXCLUSIVE); return spannableString; } private long mLastClickTime = 0; public static final int TIME_INTERVAL = 1000; /** * 可點擊-帶下劃線 */ public CharSequence toClickSpan(CharSequence charSequence, int start, int end, int color, boolean needUnderLine, OnSpanClickListener listener) { SpannableString spannableString = new SpannableString(charSequence); ClickableSpan clickableSpan = new ClickableSpan() { @Override public void onClick(@NonNull View widget) { if (listener != null) { //防止重復(fù)點擊 if (System.currentTimeMillis() - mLastClickTime >= TIME_INTERVAL) { //to do listener.onClick(charSequence.subSequence(start, end)); mLastClickTime = System.currentTimeMillis(); } } } @Override public void updateDrawState(@NonNull TextPaint ds) { ds.setColor(color); ds.setUnderlineText(needUnderLine); } }; spannableString.setSpan( clickableSpan, start, end, Spannable.SPAN_INCLUSIVE_EXCLUSIVE); return spannableString; } public interface OnSpanClickListener { void onClick(CharSequence charSequence); } /** * 變成自定義的字體 */ public CharSequence toCustomTypeFaceSpan(CharSequence charSequence, int start, int end, Typeface typeface) { SpannableString spannableString = new SpannableString(charSequence); spannableString.setSpan( new MyTypefaceSpan(typeface), start, end, Spannable.SPAN_INCLUSIVE_EXCLUSIVE); return spannableString; } }
2.2 kotlin擴展
/** * 將一段文字中指定range的文字改變大小 * @param range 要改變大小的文字的范圍 * @param scale 縮放值,大于1,則比其他文字大;小于1,則比其他文字小;默認(rèn)是1.5 */ fun CharSequence.toSizeSpan(range: IntRange, scale: Float = 1.5f): CharSequence { return SpannableString(this).apply { setSpan( RelativeSizeSpan(scale), range.start, range.endInclusive, Spannable.SPAN_INCLUSIVE_EXCLUSIVE ) } } /** * 將一段文字中指定range的文字改變前景色 * @param range 要改變前景色的文字的范圍 * @param color 要改變的顏色,默認(rèn)是紅色 */ fun CharSequence.toColorSpan(range: IntRange, color: Int = Color.RED): CharSequence { return SpannableString(this).apply { setSpan( ForegroundColorSpan(color), range.start, range.endInclusive, Spannable.SPAN_INCLUSIVE_EXCLUSIVE ) } } /** * 將一段文字中指定range的文字改變背景色 * @param range 要改變背景色的文字的范圍 * @param color 要改變的顏色,默認(rèn)是紅色 */ fun CharSequence.toBackgroundColorSpan(range: IntRange, color: Int = Color.RED): CharSequence { return SpannableString(this).apply { setSpan( BackgroundColorSpan(color), range.start, range.endInclusive, Spannable.SPAN_INCLUSIVE_EXCLUSIVE ) } } /** * 將一段文字中指定range的文字添加刪除線 * @param range 要添加刪除線的文字的范圍 */ fun CharSequence.toStrikeThrougthSpan(range: IntRange): CharSequence { return SpannableString(this).apply { setSpan( StrikethroughSpan(), range.start, range.endInclusive, Spannable.SPAN_INCLUSIVE_EXCLUSIVE ) } } /** * 將一段文字中指定range的文字添加顏色和點擊事件 * @param range 目標(biāo)文字的范圍 */ fun CharSequence.toClickSpan( range: IntRange, color: Int = Color.RED, isUnderlineText: Boolean = false, clickAction: (() -> Unit)? ): CharSequence { return SpannableString(this).apply { val clickableSpan = object : ClickableSpan() { override fun onClick(widget: View) { clickAction?.invoke() } override fun updateDrawState(ds: TextPaint) { ds.color = color ds.isUnderlineText = isUnderlineText } } setSpan(clickableSpan, range.start, range.endInclusive, Spannable.SPAN_INCLUSIVE_EXCLUSIVE) } } /** * 將一段文字中指定range的文字添加style效果 * @param range 要添加刪除線的文字的范圍 */ fun CharSequence.toStyleSpan(style: Int = Typeface.BOLD, range: IntRange): CharSequence { return SpannableString(this).apply { setSpan( StyleSpan(style), range.start, range.endInclusive, Spannable.SPAN_INCLUSIVE_EXCLUSIVE ) } } /** * 將一段文字中指定range的文字添加自定義效果 * @param range 要添加刪除線的文字的范圍 */ fun CharSequence.toCustomTypeFaceSpan(typeface: Typeface, range: IntRange): CharSequence { return SpannableString(this).apply { setSpan( CustomTypefaceSpan(typeface), range.start, range.endInclusive, Spannable.SPAN_INCLUSIVE_EXCLUSIVE ) } } /** * 將一段文字中指定range的文字添加自定義效果,可以設(shè)置對齊方式,可以設(shè)置margin * @param range */ fun CharSequence.toImageSpan( imageRes: Int, range: IntRange, verticalAlignment: Int = 0, //默認(rèn)底部 4是垂直居中 maginLeft: Int = 0, marginRight: Int = 0, width: Int = 0, height: Int = 0 ): CharSequence { return SpannableString(this).apply { setSpan( MiddleIMarginImageSpan( CommUtils.getDrawable(imageRes) .apply { setBounds(0, 0, if (width == 0) getIntrinsicWidth() else width, if (height == 0) getIntrinsicHeight() else height) }, verticalAlignment, maginLeft, marginRight ), range.start, range.endInclusive, Spannable.SPAN_INCLUSIVE_EXCLUSIVE ) } }
擴展方法的使用
mBinding.tvTextSpan1.text = "演示一下appendXX方法的用法\n" mBinding.tvTextSpan1.appendSizeSpan("變大變大", 1.5f) .appendColorSpan("我要變色", color = Color.parseColor("#f0aafc")) .appendBackgroundColorSpan("我是有底色的", color = Color.parseColor("#cacee0")) .appendStrikeThrougthSpan("添加刪除線哦哦哦哦") .appendClickSpan("來點我一下試試啊", isUnderlineText = true, clickAction = { toast("哎呀,您點到我了呢,嘿嘿") }) .appendImageSpan(R.mipmap.ic_launcher) //默認(rèn)的大圖什么都不加 默認(rèn)在底部對齊 .appendStyleSpan("我是粗體的") //可以是默認(rèn)粗體 斜體等 .appendImageSpan(R.mipmap.ic_launcher_round, 4, width = dp2px(35f), height = dp2px(35f))//4是居中的,限制Drawable .appendCustomTypeFaceSpan("Xiao mi Hua wei", TypefaceUtil.getSFFlower(mActivity)) //自定義字體文件 //默認(rèn)底部對齊,加左右margin .appendImageSpan(R.mipmap.iv_me_red_packet, maginLeft = dp2px(10f), marginRight = dp2px(10f)) //添加刪除線 .appendStrikeThrougthSpan("添加刪除線哦哦哦哦添加刪除線哦哦哦哦")
效果:
2.3 kotlin DSL方式
如果是使用Kotlin的語言開發(fā),那么還有更簡單的DSL封裝方式:
第一層的DSL接口
interface DslSpannableStringBuilder { //增加一段文字 fun addText(text: String, method: (DslSpanBuilder.() -> Unit)? = null) //添加一個圖標(biāo) fun addImage(imageRes: Int, verticalAlignment: Int = 0, maginLeft: Int = 0, marginRight: Int = 0, width: Int = 0, height: Int = 0) }
第一層的DSL接口實現(xiàn)
class DslSpannableStringBuilderImpl : DslSpannableStringBuilder { private val builder = SpannableStringBuilder() //添加文本 override fun addText(text: String, method: (DslSpanBuilder.() -> Unit)?) { val spanBuilder = DslSpanBuilderImpl() method?.let { spanBuilder.it() } var charSeq: CharSequence = text spanBuilder.apply { if (issetColor) { charSeq = charSeq.toColorSpan(0..text.length, textColor) } if (issetBackground) { charSeq = charSeq.toBackgroundColorSpan(0..text.length, textBackgroundColor) } if (issetScale) { charSeq = charSeq.toSizeSpan(0..text.length, scaleSize) } if (isonClick) { charSeq = charSeq.toClickSpan(0..text.length, textColor, isuseUnderLine, onClick) } if (issetTypeface) { charSeq = charSeq.toCustomTypeFaceSpan(typefaces, 0..text.length) } if (issetStrikethrough) { charSeq = charSeq.toStrikeThrougthSpan(0..text.length) } builder.append(charSeq) } } //添加圖標(biāo) override fun addImage(imageRes: Int, verticalAlignment: Int, maginLeft: Int, marginRight: Int, width: Int, height: Int) { var charSeq: CharSequence = "1" charSeq = charSeq.toImageSpan(imageRes, 0..1, verticalAlignment, maginLeft, marginRight, width, height) builder.append(charSeq) } fun build(): SpannableStringBuilder { return builder } }
第二層Text的DSL接口
interface DslSpanBuilder { //設(shè)置文字顏色 fun setColor(color: Int = 0) //設(shè)置點擊事件 fun setClick(useUnderLine: Boolean = true, onClick: (() -> Unit)?) //設(shè)置縮放大小 fun setScale(scale: Float = 1.0f) //設(shè)置自定義字體 fun setTypeface(typeface: Typeface) //是否需要中劃線 fun setStrikethrough(isStrikethrough: Boolean = false) //設(shè)置背景 fun setBackground(color: Int = Color.TRANSPARENT) }
第二層Text的DSL接口實現(xiàn)
class DslSpanBuilderImpl : DslSpanBuilder { var issetColor = false var textColor: Int = Color.BLACK var isonClick = false var isuseUnderLine = false var onClick: (() -> Unit)? = null var issetScale = false var scaleSize = 1.0f var issetTypeface = false var typefaces: Typeface = Typeface.DEFAULT var issetStrikethrough = false var issetBackground = false var textBackgroundColor = 0 override fun setColor(color: Int) { issetColor = true textColor = color } override fun setClick(useUnderLine: Boolean, onClick: (() -> Unit)?) { isonClick = true isuseUnderLine = useUnderLine this.onClick = onClick } override fun setScale(scale: Float) { issetScale = true scaleSize = scale } override fun setTypeface(typeface: Typeface) { issetTypeface = true typefaces = typeface } override fun setStrikethrough(isStrikethrough: Boolean) { issetStrikethrough = isStrikethrough } override fun setBackground(color: Int) { issetBackground = true textBackgroundColor = color } }
創(chuàng)建TextVuew的擴展入口
//為 TextView 創(chuàng)建擴展函數(shù),其參數(shù)為接口的擴展函數(shù) fun TextView.buildSpannableString(init: DslSpannableStringBuilder.() -> Unit) { //具體實現(xiàn)類 val spanStringBuilderImpl = DslSpannableStringBuilderImpl() spanStringBuilderImpl.init() movementMethod = LinkMovementMethod.getInstance() //通過實現(xiàn)類返回SpannableStringBuilder text = spanStringBuilderImpl.build() }
使用:
mBinding.tvTextSpan4.buildSpannableString { addText("我已詳細閱讀并同意") addText("測試紅色的文字顏色") { setColor(Color.RED) } addText("測試白色文字加上灰色背景") { setColor(Color.WHITE) setBackground(Color.GRAY) } addText("測試文本變大了") { setColor(Color.DKGRAY) setScale(1.5f) } addImage(R.mipmap.ic_launcher) addText("測試可以點擊的文本") { setClick(true) { toast("點擊文本拉啦啦") } } addImage(R.mipmap.ic_launcher_round, 5, dp2px(10f), dp2px(10f), dp2px(35f), dp2px(35f)) addText("Test Custom Typeface Font is't Success?") { setTypeface(TypefaceUtil.getSFFlower(mActivity)) } addText("測試中劃線是否生效") { setStrikethrough(true) } }
效果:
總結(jié)
如果是順序固定,效果復(fù)雜,那么可以用Span的方式。
如果順序不固定(如國際化)那么可以使用Html的方式。
總的來說,兩種方式都不算太難,都是些固定的代碼。如果需求可以看源碼。
到此這篇關(guān)于Android富文本實現(xiàn)的幾種方式的文章就介紹到這了,更多相關(guān)Android富文本實現(xiàn)內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Android邊播放邊緩存視頻框架AndroidVideoCache詳解
這篇文章主要為大家介紹了Android邊播放邊緩存視頻框架AndroidVideoCache詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪2022-09-09ViewPager+Fragment實現(xiàn)側(cè)滑導(dǎo)航欄
這篇文章主要為大家詳細介紹了ViewPager+Fragment實現(xiàn)側(cè)滑導(dǎo)航欄,文中示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下2021-05-05Flutter?LinearProgressIndicator使用指南分析
這篇文章主要為大家介紹了Flutter?LinearProgressIndicator使用指南分析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪2023-03-03ViewPager+PagerAdapter實現(xiàn)帶指示器的引導(dǎo)頁
這篇文章主要為大家詳細介紹了ViewPager+PagerAdapter實現(xiàn)帶指示器的引導(dǎo)頁,文中示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下2020-09-09Android GridView實現(xiàn)橫向列表水平滾動
這篇文章主要為大家詳細介紹了Android GridView實現(xiàn)橫向列表水平滾動,具有一定的參考價值,感興趣的小伙伴們可以參考一下2018-07-07Android編程實現(xiàn)簡單設(shè)置按鈕顏色的方法
這篇文章主要介紹了Android編程實現(xiàn)簡單設(shè)置按鈕顏色的方法,涉及Android控件布局與屬性設(shè)置相關(guān)操作技巧,需要的朋友可以參考下2017-03-03解決Android 沉浸式狀態(tài)欄和華為虛擬按鍵沖突問題
對于現(xiàn)在的 App 來說,布局頁面基本都會用到沉浸式狀態(tài)欄,單純的沉浸式狀態(tài)欄很容易解決,但是在華為手機上存在一個底部虛擬按鍵的問題,會導(dǎo)致頁面底部和頂部出現(xiàn)很大的問題,下面通過本文給大家分享Android 沉浸式狀態(tài)欄和華為虛擬按鍵沖突問題,一起看看吧2017-07-07