Android TextView 基于Span的各种文字样式实现<二>
Android 开发过程中文字作为页面展示的基础且主要的内容,在长期的开发过程中引发了各种各样的需求和效果.好多功能是做了就忘了.好记性不容烂笔头,也不想拾人牙慧了,所以总结一下文字展示的各种常规样式,方便自己也方便大家.
1.日常开发中各种文字效果梳理一遍
- 字体更换
- 跑马灯
- 图文混合
Span方案WebView+标签方案
- 折叠/展开
- 文字凸显
- 字体颜色(纯色/渐变色)
- 文字大小
- 文字划线
- 文字虚化
- 文字点击
2.功能实现:
下面我就将日常用到的文字效果一一实现,以此记录.
2.1 TextView字体替换
val typeface = ResourcesCompat.getFont(this, R.font.alimama_shu_hei_ti_bold);
binding.tv01.text="设置字体"
binding.tv01.setTypeface(typeface)
2.2 跑马灯效果
1.自定义
/**
* 跑马灯自定义
*/
public class MarqueeTextView extends AppCompatTextView {
private boolean isFocused = false;
public MarqueeTextView(Context context) {
this(context, null);
}
public MarqueeTextView(Context context, @Nullable AttributeSet attrs) {
this(context, attrs, 0);
}
public MarqueeTextView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
setEllipsize(TextUtils.TruncateAt.MARQUEE);
}
@Override
public boolean isFocused() {
return isFocused;
}
public void setFocused(boolean isFocused) {
this.isFocused = isFocused;
setFocusableInTouchMode(isFocused);
setFocusable(isFocused);
setSelected(isFocused);
}
@Override
protected void onFocusChanged(boolean focused, int direction, Rect previouslyFocusedRect) {
if (focused) {
super.onFocusChanged(focused, direction, previouslyFocusedRect);
}
}
@Override
public void onWindowFocusChanged(boolean hasWindowFocus) {
if (hasWindowFocus) {
super.onWindowFocusChanged(hasWindowFocus);
}
}
}
2.xml 引用
<com.wkq.ui.MarqueeTextView
android:id="@+id/tv_6"
android:textColor="@color/black"
android:textSize="20dp"
android:layout_width="160dp"
android:layout_height="wrap_content"
android:text="这是一个跑马灯展示"
android:gravity="center_vertical"
android:singleLine="true"
android:ellipsize="marquee"
android:marqueeRepeatLimit="marquee_forever"
android:scrollHorizontally="true" />
3.代码设置
binding.tv6.setEllipsize(TextUtils.TruncateAt.MARQUEE)
//一次
binding.tv6.setMarqueeRepeatLimit(1)
binding.tv6.setMarqueeRepeatLimit(-1)
binding.tv6.setFocused(true)
2.3 文字凸显
文字凸显的功能涉及的东西就很多了,涉及到颜色,背景,点击事件,字体渐变,图文混合,下划线,中划线,模糊效果,超文本等功能,其中功能又涉及到不同的实现方案.
- 自身属性
- 各种Span实现文字效果
- WebView+Html标签
- 自定义View绘制(暂不涉及)
2.3.1 自身属性
-
设置字体 val typeface = ResourcesCompat.getFont(this, R.font.alimama_shu_hei_ti_bold); binding.tv.setTypeface(typeface)
-
设置中划线下划线
``` binding.tv4.text="展示中横线" binding.tv4.getPaint().setFlags(Paint.STRIKE_THRU_TEXT_FLAG) binding.tv5.text="展示下横线" binding.tv5.getPaint().setFlags(Paint.UNDERLINE_TEXT_FLAG) ``` -
设置跑马灯
binding.tv6.setEllipsize(TextUtils.TruncateAt.MARQUEE) binding.tv6.setMarqueeRepeatLimit(1) //一次 binding.tv6.setMarqueeRepeatLimit(-1)//无线 binding.tv6.setFocused(true) -
设置省略号展示
<TextView android:textColor="@color/black" android:layout_margin="10dp" android:layout_width="140dp" android:layout_height="wrap_content" android:singleLine="true" android:ellipsize="end" android:textSize="20dp" android:text="文字展示中横线打打 大大大爱滴阿文"/>
2.3.2 Span方式设置文字凸显
- ForegroundColorSpan(
实质是设置文字颜色) 设置部分字体颜色
/**
* 设置 spanText 的文本颜色(前景色)。
*
* @param startText 起始文本,若为 null 会被处理为空字符串。
* @param spanText 要设置样式的中间文本,若为 null 会被处理为空字符串。
* @param endText 结束文本,若为 null 会被处理为空字符串。
* @param color 前景色,默认为蓝色(Color.BLUE)。
* @return 应用了前景色样式的 SpannableString。
*/
fun setForegroundColorSpan(
startText: String?,
spanText: String?,
endText: String?,
color: Int = Color.BLUE
): SpannableString {
val combinedText =
handleNullText(startText) + handleNullText(spanText) + handleNullText(endText)
val startIndex = handleNullText(startText).length
val endIndex = startIndex + handleNullText(spanText).length
val spannable = SpannableString(combinedText)
spannable.setSpan(
ForegroundColorSpan(color), startIndex, endIndex, Spannable.SPAN_INCLUSIVE_EXCLUSIVE
)
return spannable
}
-
设置局部文字背景色
/\*\* \* 设置 spanText 的背景色。 \* \* @param startText 起始文本,若为 null 会被处理为空字符串。 \* @param spanText 要设置样式的中间文本,若为 null 会被处理为空字符串。 \* @param endText 结束文本,若为 null 会被处理为空字符串。 \* @param color 背景色,默认为绿色(Color.GREEN)。 \* @return 应用了背景色样式的 SpannableString。 \*/ fun setBackgroundColorSpan( startText: String?, spanText: String?, endText: String?, color: Int = Color.GREEN ): SpannableString { val combinedText = handleNullText(startText) + handleNullText(spanText) + handleNullText(endText) val startIndex = handleNullText(startText).length val endIndex = startIndex + handleNullText(spanText).length val spannable = SpannableString(combinedText) spannable.setSpan( BackgroundColorSpan(color), startIndex, endIndex, Spannable.SPAN\_INCLUSIVE\_EXCLUSIVE ) return spannable } -
设置文字渐变色
/** * 设置文字渐变色 * @param startText String? * @param spanText String? * @param endText String? * @param startColor Int * @param endColor Int * @param isLeftToRight Boolean * @return SpannableString */ fun setTextGradientColor( startText: String?, spanText: String?, endText: String?, startColor: Int = Color.GREEN, endColor: Int = Color.GREEN, isLeftToRight:Boolean=true ): SpannableString { val combinedText = handleNullText(startText) + handleNullText(spanText) + handleNullText(endText) val startIndex = handleNullText(startText).length val endIndex = startIndex + handleNullText(spanText).length val spannable = SpannableString(combinedText) val span = KtLinearGradientFontSpan(startColor, endColor, isLeftToRight) spannable.setSpan( span, startIndex, endIndex, Spannable.SPAN_INCLUSIVE_EXCLUSIVE ) return spannable }
-
设置局部文字点击事件
/** * 设置 spanText 可点击及点击事件。 * * @param startText 起始文本,若为 null 会被处理为空字符串。 * @param spanText 要设置样式的中间文本,若为 null 会被处理为空字符串。 * @param endText 结束文本,若为 null 会被处理为空字符串。 * @param clickListener 点击 spanText 时触发的回调函数。 * @return 应用了可点击样式的 SpannableString。 */ fun setClickableSpan( startText: String?, spanText: String?, endText: String?, clickListener: (content:String) -> Unit ): SpannableString { val combinedText = handleNullText(startText) + handleNullText(spanText) + handleNullText(endText) val startIndex = handleNullText(startText).length val endIndex = startIndex + handleNullText(spanText).length val spannable = SpannableString(combinedText) val clickableSpan = object : ClickableSpan() { override fun onClick(widget: android.view.View) { clickListener(handleNullText(spanText)) } } spannable.setSpan(clickableSpan, startIndex, endIndex, Spannable.SPAN_INCLUSIVE_EXCLUSIVE) return spannable }
- 设置文字高斯效果
/\*\*
\* 设置 spanText 的修饰效果,如模糊、浮雕。
\*
\* @param startText 起始文本,若为 null 会被处理为空字符串。
\* @param spanText 要设置样式的中间文本,若为 null 会被处理为空字符串。
\* @param endText 结束文本,若为 null 会被处理为空字符串。
\* @return 应用了模糊和浮雕修饰效果的 SpannableString。
\*/
fun setMaskFilterSpan(
startText: String?,
spanText: String?,
endText: String?
): SpannableString {
val combinedText =
handleNullText(startText) + handleNullText(spanText) + handleNullText(endText)
val startIndex = handleNullText(startText).length
val endIndex = startIndex + handleNullText(spanText).length
val spannable = SpannableString(combinedText)
- 模糊(BlurMaskFilter)
val blurMaskFilterSpan = MaskFilterSpan(BlurMaskFilter(3f, BlurMaskFilter.Blur.OUTER))
spannable.setSpan(
blurMaskFilterSpan, startIndex, endIndex, Spannable.SPAN_INCLUSIVE_EXCLUSIVE
)
// 浮雕(EmbossMaskFilter)
// val embossMaskFilterSpan =
// MaskFilterSpan(EmbossMaskFilter(floatArrayOf(1f, 1f, 1f), 0.2f, 28f, 5f))
// spannable.setSpan(
// embossMaskFilterSpan, startIndex, endIndex, Spannable.SPAN_INCLUSIVE_EXCLUSIVE
// )
return spannable
}
- 设置文字中划线
/**
* 设置 spanText 的删除线(中划线)。
*
* @param startText 起始文本,若为 null 会被处理为空字符串。
* @param spanText 要设置样式的中间文本,若为 null 会被处理为空字符串。
* @param endText 结束文本,若为 null 会被处理为空字符串。
* @return 应用了删除线样式的 SpannableString。
*/
fun setStrikethroughSpan(
startText: String?,
spanText: String?,
endText: String?
): SpannableString {
val combinedText =
handleNullText(startText) + handleNullText(spanText) + handleNullText(endText)
val startIndex = handleNullText(startText).length
val endIndex = startIndex + handleNullText(spanText).length
val spannable = SpannableString(combinedText)
spannable.setSpan(
StrikethroughSpan(), startIndex, endIndex, Spannable.SPAN_INCLUSIVE_EXCLUSIVE
)
return spannable
}
- 设置文字下划线
/**
* 设置 spanText 的下划线。
*
* @param startText 起始文本,若为 null 会被处理为空字符串。
* @param spanText 要设置样式的中间文本,若为 null 会被处理为空字符串。
* @param endText 结束文本,若为 null 会被处理为空字符串。
* @return 应用了下划线样式的 SpannableString。
*/
fun setUnderlineSpan(
startText: String?,
spanText: String?,
endText: String?
): SpannableString {
val combinedText =
handleNullText(startText) + handleNullText(spanText) + handleNullText(endText)
val startIndex = handleNullText(startText).length
val endIndex = startIndex + handleNullText(spanText).length
val spannable = SpannableString(combinedText)
spannable.setSpan(UnderlineSpan(), startIndex, endIndex, Spannable.SPAN_INCLUSIVE_EXCLUSIVE)
return spannable
}
- 设置文字大小
/\*\* \* 设置 spanText 的绝对大小(文本字体)。 \* \* @param startText 起始文本,若为 null 会被处理为空字符串。 \* @param spanText 要设置样式的中间文本,若为 null 会被处理为空字符串。 \* @param endText 结束文本,若为 null 会被处理为空字符串。 \* @param size 字体的绝对大小。 \* @param dip 若为 true,表示大小以 dip 为单位;若为 false,表示大小以像素为单位,默认为 true。 \* @return 应用了绝对大小样式的 SpannableString。 \*/ fun setAbsoluteSizeSpan( startText: String?, spanText: String?, endText: String?, size: Int, dip: Boolean = true ): SpannableString { val combinedText = handleNullText(startText) + handleNullText(spanText) + handleNullText(endText) val startIndex = handleNullText(startText).length val endIndex = startIndex + handleNullText(spanText).length val spannable = SpannableString(combinedText) spannable.setSpan( AbsoluteSizeSpan(size, dip), startIndex, endIndex, Spannable.SPAN\_INCLUSIVE\_EXCLUSIVE ) return spannable }
- 设置文字中图片对其方式
fun setDynamicDrawableSpan( startText: String?, spanText: String?, endText: String?, drawableResId: Int, width: Int, height: Int ): SpannableString { val combinedText = handleNullText(startText) + handleNullText(spanText) + handleNullText(endText) val startIndex = handleNullText(startText).length val endIndex = startIndex + handleNullText(spanText).length val spannable = SpannableString(combinedText) val drawableSpanBaseline = object : DynamicDrawableSpan(DynamicDrawableSpan.ALIGN\_BASELINE) { override fun getDrawable(): Drawable { val d = context.resources.getDrawable(drawableResId) d.setBounds(0, 0, width, height) return d } } spannable.setSpan( drawableSpanBaseline, startIndex, endIndex, Spannable.SPAN\_INCLUSIVE\_EXCLUSIVE ) val drawableSpanBottom = object : DynamicDrawableSpan(DynamicDrawableSpan.ALIGN\_BOTTOM) { override fun getDrawable(): Drawable { val d = context.resources.getDrawable(drawableResId) d.setBounds(0, 0, width, height) return d } } spannable.setSpan( drawableSpanBottom, startIndex, endIndex, Spannable.SPAN\_INCLUSIVE\_EXCLUSIVE ) return spannable } - 设置图文混合
/**
* 设置 spanText 中的图片。
*
* @param startText 起始文本,若为 null 会被处理为空字符串。
* @param spanText 要设置样式的中间文本,若为 null 会被处理为空字符串。
* @param endText 结束文本,若为 null 会被处理为空字符串。
* @param drawableResId 图片资源的 ID。
* @param width 图片的宽度。
* @param height 图片的高度。
* @return 应用了图片样式的 SpannableString。
*/
fun setImageSpan(
startText: String?,
spanText: String?,
endText: String?,
drawableResId: Int,
width: Int,
height: Int
): SpannableString {
val combinedText =
handleNullText(startText) + handleNullText(spanText) + handleNullText(endText)
val startIndex = handleNullText(startText).length
val endIndex = startIndex + handleNullText(spanText).length
val spannable = SpannableString(combinedText)
val d = context.resources.getDrawable(drawableResId)
d.setBounds(0, 0, width, height)
spannable.setSpan(ImageSpan(d), startIndex, endIndex, Spannable.SPAN_INCLUSIVE_EXCLUSIVE)
return spannable
}
- 设置文字相对大小
/\*\*
\* 设置 spanText 的相对大小(文本字体)。
\*
\* @param startText 起始文本,若为 null 会被处理为空字符串。
\* @param spanText 要设置样式的中间文本,若为 null 会被处理为空字符串。
\* @param endText 结束文本,若为 null 会被处理为空字符串。
\* @param proportion 字体的相对大小比例。
\* @return 应用了相对大小样式的 SpannableString。
\*/
fun setRelativeSizeSpan(
startText: String?,
spanText: String?,
endText: String?,
proportion: Float
): SpannableString {
val combinedText =
handleNullText(startText) + handleNullText(spanText) + handleNullText(endText)
val startIndex = handleNullText(startText).length
val endIndex = startIndex + handleNullText(spanText).length
val spannable = SpannableString(combinedText)
spannable.setSpan(
RelativeSizeSpan(proportion), startIndex, endIndex, Spannable.SPAN\_INCLUSIVE\_EXCLUSIVE
)
return spannable
}
- 设置文字样式
/**
* 设置 spanText 的字体样式:粗体、斜体等。
*
* @param startText 起始文本,若为 null 会被处理为空字符串。
* @param spanText 要设置样式的中间文本,若为 null 会被处理为空字符串。
* @param endText 结束文本,若为 null 会被处理为空字符串。
* @param style 字体样式,默认为粗斜体(Typeface.BOLD_ITALIC)。
* @return 应用了字体样式的 SpannableString。
*/
fun setStyleSpan(
startText: String?,
spanText: String?,
endText: String?,
style: Int = Typeface.BOLD_ITALIC
): SpannableString {
val combinedText =
handleNullText(startText) + handleNullText(spanText) + handleNullText(endText)
val startIndex = handleNullText(startText).length
val endIndex = startIndex + handleNullText(spanText).length
val spannable = SpannableString(combinedText)
spannable.setSpan(
StyleSpan(style), startIndex, endIndex, Spannable.SPAN_INCLUSIVE_EXCLUSIVE
)
return spannable
}
- 设置文字上下标
/\*\*
\* 设置 spanText 的下标。
\*
\* @param startText 起始文本,若为 null 会被处理为空字符串。
\* @param spanText 要设置样式的中间文本,若为 null 会被处理为空字符串。
\* @param endText 结束文本,若为 null 会被处理为空字符串。
\* @return 应用了下标样式的 SpannableString。
*/
fun setSubscriptSpan(
startText: String?,
spanText: String?,
endText: String?
): SpannableString {
val combinedText = handleNullText(startText) + handleNullText(spanText) + handleNullText(endText)
val startIndex = handleNullText(startText).length
val endIndex = startIndex + handleNullText(spanText).length
val spannable = SpannableString(combinedText)
spannable.setSpan(SubscriptSpan(), startIndex, endIndex, Spannable.SPAN\_INCLUSIVE\_EXCLUSIVE)
return spannable
}
- 设置 spanText 的上标。
/*\*
\* 设置 spanText 的上标。
\*
\* @param startText 起始文本,若为 null 会被处理为空字符串。
\* @param spanText 要设置样式的中间文本,若为 null 会被处理为空字符串。
\* @param endText 结束文本,若为 null 会被处理为空字符串。
\* @return 应用了上标样式的 SpannableString。
\*/
fun setSuperscriptSpan(
startText: String?,
spanText: String?,
endText: String?
): SpannableString {
val combinedText = handleNullText(startText) + handleNullText(spanText) + handleNullText(endText)
val startIndex = handleNullText(startText).length
val endIndex = startIndex + handleNullText(spanText).length
val spannable = SpannableString(combinedText)
spannable.setSpan(SuperscriptSpan(), startIndex, endIndex, Spannable.SPAN\_INCLUSIVE\_EXCLUSIVE)
return spannable
}
- 设置超文本
/\*\*
\* 设置 spanText 的超链接。
\*
\* @param startText 起始文本,若为 null 会被处理为空字符串。
\* @param spanText 要设置样式的中间文本,若为 null 会被处理为空字符串。
\* @param endText 结束文本,若为 null 会被处理为空字符串。
\* @param url 超链接的 URL 地址。
\* @return 应用了超链接样式的 SpannableString。
\*/
fun setURLSpan(
startText: String?,
spanText: String?,
endText: String?,
url: String
): SpannableString {
val combinedText = handleNullText(startText) + handleNullText(spanText) + handleNullText(endText)
val startIndex = handleNullText(startText).length
val endIndex = startIndex + handleNullText(spanText).length
val spannable = SpannableString(combinedText)
spannable.setSpan(URLSpan(url), startIndex, endIndex, Spannable.SPAN\_INCLUSIVE\_EXCLUSIVE)
return spannable
}
2.3 文字折叠/展开效果
思路:
自定义TextView 设置最多展示的文字数,当文字数超过设置的限制展示...然后尾部动态拼接一个可点击的全文 ClickableSpan ,点击这个文字绘制全部文字,然后尾部怕添加一个收起 ClickableSpan 点击收起收起文字.
class ExpandableTextView : AutoLinkTextView {
private var mMaxCollapsedLength = 0
//省略号
private var mEllipsisHint: String = "..."
//展开文字
private var mExpandHint: String? = null
//收起文字
private var mCollapseHint: String? = null
//展开/收起文字颜色
var mHintColor: Int? = null
//原始文本
private var mOrigText: CharSequence? = null
//截取后的文本
private var mSubText: CharSequence? = null
private var mCurrentState = 0
//关闭状态
val STATE_COLLAPSE = 0
//展开状态
val STATE_EXPAND = 1
private var mOnExpandListener: OnExpandListener? = null
private var copyText: String? = null
init {
mHintColor = R.color.color_auto_high_default
}
constructor(mContext: Context) : this(mContext, null)
constructor(mContext: Context, mAttributeSet: AttributeSet?) : this(mContext, mAttributeSet, 0)
constructor(mContext: Context, mAttributeSet: AttributeSet?, defStyleAttr: Int) : super(
mContext, mAttributeSet, defStyleAttr
) {
initAttributeSet(mAttributeSet!!)
initTextView()
}
private fun initTextView() {
mOrigText = text
setTextInternal(getNewTextByConfig()!!, mBufferType)
}
//初始换数据
@SuppressLint("ResourceAsColor")
fun initAttributeSet(mAttributeSet: AttributeSet) {
val array: TypedArray = context!!.obtainStyledAttributes(
mAttributeSet, R.styleable.ExpandableTextView
)
mMaxCollapsedLength = array.getInt(
R.styleable.ExpandableTextView_maxCollapsedLength, 100
)
mExpandHint = array.getString(R.styleable.ExpandableTextView_new_expandHint)
mCollapseHint = array.getString(R.styleable.ExpandableTextView_new_collapseHint)
mHintColor = array.getColor(
R.styleable.ExpandableTextView_new_hintColor, R.color.color_auto_high_default
)
if (TextUtils.isEmpty(mExpandHint)) {
mExpandHint = "全文"
}
if (TextUtils.isEmpty(mCollapseHint)) {
mCollapseHint = "收起"
}
array.recycle()
mCurrentState = STATE_COLLAPSE
}
fun setText(text: CharSequence?, state: Int) {
mCurrentState = state
mOrigText = text
setText(getNewTextByConfig()!!)
}
private fun setTextInternal(text: CharSequence, type: BufferType) {
super.setText(text, type)
}
//扩展的点击事件
fun setExpandListener(listener: OnExpandListener) {
mOnExpandListener = listener
}
private fun getNewTextByConfig(): CharSequence? {
if (TextUtils.isEmpty(mOrigText)) return ""
when (mCurrentState) {
STATE_COLLAPSE -> {
val lastBracket: Int
if (mOrigText!!.length <= mMaxCollapsedLength) {
setClickableSpan(null, 0, 0)
return mOrigText
}
mSubText = mOrigText!!.subSequence(0, mMaxCollapsedLength)
//自定义表情处理
lastBracket = mSubText.toString().lastIndexOf("]")
if (mMaxCollapsedLength - lastBracket <= 4) {
mMaxCollapsedLength = lastBracket + 1
}
//链接处理
val links: ArrayList<TextViewLinkify.LinkSpec> =
TextViewLinkify.getGatherLinks(mAutoLinkMaskCompat, mOrigText.toString())
if (links != null && links.size > 0) {
for (linkSpec in links) {
if (mMaxCollapsedLength < linkSpec.getEnd() && mMaxCollapsedLength > linkSpec.getStart()) {
mMaxCollapsedLength = linkSpec.getEnd()
}
}
}
mSubText = mOrigText!!.subSequence(0, mMaxCollapsedLength)
var ssbCollapsed = SpannableStringBuilder(mSubText).append(mEllipsisHint) //省略号
.append(mExpandHint)
setClickableSpan(
Clickable(OnClickListener { toggle() }), ssbCollapsed.length - 2,
ssbCollapsed.length
)
return ssbCollapsed
}
STATE_EXPAND -> {
if (mOrigText!!.length <= mMaxCollapsedLength) {
setClickableSpan(null, 0, 0)
return mOrigText
}
val ssbExpand = SpannableStringBuilder(mOrigText).append(
"""
$mCollapseHint
""".trimIndent()
)
setClickableSpan(
Clickable(OnClickListener { toggle() }), ssbExpand.length - 2, ssbExpand.length
)
return ssbExpand
}
}
return mOrigText
}
private fun toggle() {
when (mCurrentState) {
STATE_COLLAPSE -> mCurrentState = STATE_EXPAND
STATE_EXPAND -> mCurrentState = STATE_COLLAPSE
}
if (mOnExpandListener != null) mOnExpandListener!!.onExpand(this)
setTextInternal(getNewTextByConfig()!!, mBufferType)
}
inner class Clickable(
private val mListener: OnClickListener
) : ClickableSpan(), OnClickListener, TouchableSpan {
protected var mPressed = false
override fun setPressed(pressed: Boolean) {
mPressed = pressed
}
override fun onClick(v: View) {
mListener.onClick(v)
}
override fun onLongClick(widget: View?) {
}
@SuppressLint("ResourceAsColor")
override fun updateDrawState(ds: TextPaint) {
ds.setColor(mHintColor!!)
ds.isUnderlineText = false //去除超链接的下划线
}
}
interface OnExpandListener {
fun onExpand(view: ExpandableTextView?)
}
}
2.4 WebView+html 实现图文混排,以及点击事件回调
思路:
不管自身属性和Span设置方式都是移动端拿到数据,做的定制化处理.当业务需求是页面展示需要客户定制化展示,以上的方案就不能满足业务需求了,这时候就需要Web+Html标签实现动态文字的需求了
要求:
- 文字样式大小后台返回
- 图文方式
- 图片点击预览
实现流程:
后台返回标签数据---> 自己拼接成html数据(设置颜色大小宽高)-->js拦截对象,和js拦截方法-->注入html中
实现:
1.根据获取的标签数据组合到Html字符串中
val picUrl="<http://n.sinaimg.cn/ent/4_img/upload/1f0ce517/160/w1024h1536/20210413/f3cc-knqqqmv1022303.jpg>"
val pTagString = "<p>这是一个包含图片的段落:<img src="转存失败,建议直接上传图片文件 $picUrl" alt="示例图片转存失败,建议直接上传图片文件" style="width: 200px; height: 150px; object-fit: cover ;"> 图片展示结束。</p>"
String html = "<html><head><meta charset="UTF-8"><style type="text/css">html,body{padding:0px;margin:0px;font-size:" + fontSize + "px} img{background-size:contain|cover;width:100%;height: auto;} p{margin:0px;font-size:" + fontSize + "px}</style></head><body>" + pData + "</body></html>";
2.WebView加载Html
webEleListener = webEleClickListener;
String html = "<html><head><meta charset="UTF-8"><style type="text/css">html,body{padding:0px;margin:0px;font-size:" + fontSize + "px} img{background-size:contain|cover;width:100%;height: auto;} p{margin:0px;font-size:" + fontSize + "px}</style></head><body>" + pData + "</body></html>";
webView.getSettings().setJavaScriptEnabled(true);
webView.getSettings().setSupportZoom(false) ;// 禁止缩放
webView.getSettings().setUseWideViewPort(false);// 不使用宽视口
webView.getSettings().setLoadWithOverviewMode(false);// 不使用概述模式
webView.addJavascriptInterface(new JsObject(), "injectedObject");
webView.setWebViewClient(new WebViewClient() {
@Override
public void onPageFinished(WebView view, String url) {
super.onPageFinished(view, url);
String js = "javascript:(function(){" +
"var images = document.getElementsByTagName("img");" +
"var imageUrls = new Array(); " +
"for(var i=0; i<images.length; i++) {" +
" imageUrls[i] = images[i].src; " +
" images[i].pos = i; " +
" images[i].onclick = function(){" +
" window.injectedObject.openImage(this.src, this.pos);" +
" }" +
"}" +
"window.injectedObject.setImageUrls(imageUrls); " +
"})()";
webView.loadUrl(js);
}
});
webView.loadDataWithBaseURL(null, html, "text/html", "utf-8", null);
3:写js方法注入点击事件监听
private static class JsObject {
private String[] mImageUrls;
@JavascriptInterface
public void setImageUrls(String[] imageUrls) {
this.mImageUrls = imageUrls;
}
@JavascriptInterface
public void openImage(String src, int pos) {
if (null != webEleListener) {
webEleListener.onImageClick(src, pos, this.mImageUrls);
}
if (null != context) {
if (null != src && !TextUtils.isEmpty(src) && null != mImageUrls) {
ArrayList<String> imgs = new ArrayList<>();
for (String imageUrl : mImageUrls) {
imgs.add(imageUrl);
}
if (null != imgs && imgs.size() > 0) {
previewImg(context, src, imgs);
}
}
}
}
}
注入 js对象
webView\.addJavascriptInterface(new JsObject(), "injectedObject");
注入 js监听方法
webView\.setWebViewClient(new WebViewClient() {
@Override
public void onPageFinished(WebView view, String url) {
super.onPageFinished(view, url);
String js = "javascript:(function(){" +
"var images = document.getElementsByTagName("img");" +
"var imageUrls = new Array(); " +
"for(var i=0; i\<images.length; i++) {" +
" imageUrls\[i] = images\[i].src; " +
" images\[i].pos = i; " +
" images\[i].onclick = function(){" +
" window\.injectedObject.openImage(this.src, this.pos);" +
" }" +
"}" +
"window\.injectedObject.setImageUrls(imageUrls); " +
"})()";
webView\.loadUrl(js);
}
});
总结
本文围绕 Android 开发中 TextView 的文字效果展开,全面梳理了字体更换、跑马灯、图文混合、折叠 / 展开、文字凸显等常见需求。通过提供具体的代码示例,详细介绍了利用自身属性、Span 方式、自定义 View 等多种实现方案。开发者可依据实际需求参考这些方案,实现多样化的文字展示效果,同时给出的相关参考链接也为进一步深入学习提供了方向。
使用各种 Span 类可以实现丰富的文字样式效果。
ForegroundColorSpan:设置部分字体的颜色,通过传入起始文本、要设置样式的中间文本、结束文本和颜色值,创建SpannableString并应用ForegroundColorSpan。BackgroundColorSpan:设置局部文字的背景色,原理与设置前景色类似。- 文字渐变色:通过自定义的
KtLinearGradientFontSpan类,实现文字的渐变色效果。 - 局部文字点击事件:使用
ClickableSpan类,为指定文本设置点击事件,当点击该部分文本时触发相应的回调函数。 - 文字高斯效果:使用
MaskFilterSpan结合BlurMaskFilter或EmbossMaskFilter,实现文字的模糊或浮雕效果。 - 文字划线效果:分别使用
StrikethroughSpan和UnderlineSpan实现文字的中划线和下划线效果。 - 文字大小设置:通过
AbsoluteSizeSpan和RelativeSizeSpan分别设置文字的绝对大小和相对大小。 - 文字中图片对齐方式:使用
DynamicDrawableSpan类,设置图片与文字的对齐方式,如基线对齐或底部对齐。 - 图文混合:使用
ImageSpan
相关参考链接