模拟微博 #热贴 和 @用户 的这种 富文本形式组件,不说了, 直接上代码
package com.tongtong.feat_watch.viewimport android.content.Context
import android.graphics.Color
import android.util.AttributeSet
import android.view.LayoutInflater
import android.widget.FrameLayout
import android.widget.TextView
import androidx.core.view.isVisible
import com.didi.drouter.annotation.Router
import com.google.android.material.chip.ChipGroup
import com.google.gson.Gson
import com.tongtong.feat_watch.R
import com.tongtong.feat_watch.databinding.ViewRichBinding
import com.tongtong.feat_watch.ui.topic.bean.ContentBean
import com.tongtong.feat_watch.ui.topic.bean.ContentType
import com.tongtong.lib_router.TTRouter
import com.tongtong.lib_util.ToastUtils
import org.json.JSONException
import org.json.JSONObject
import timber.log.Timber/*** @date: 2024/11/7* desc:* author: shuhuai* version:*/
class ContentView @JvmOverloads constructor(context: Context,attrs: AttributeSet? = null,defStyleAttr: Int = 0
) :FrameLayout(context, attrs, defStyleAttr) {//文本颜色private var textColor: Int//字体大小private var textSize: Float//折叠后显示的行数private val showLines: Intprivate var isBold: Booleanprivate var urltextColor: Int//是否可折叠private var expandEnable: Booleanprivate lateinit var binding: ViewRichBindingprivate var moreBack: (v: TextView?) -> Unit = {}init {val typedArray = context.obtainStyledAttributes(attrs, R.styleable.ExpandTextView)textColor = typedArray.getColor(R.styleable.ExpandTextView_contentTextColor,context.resources.getColor(R.color.color_131314))urltextColor = typedArray.getColor(R.styleable.ExpandTextView_urlTextColor,context.resources.getColor(R.color.color_5D34D0))showLines = typedArray.getColor(R.styleable.ExpandTextView_showLines, Int.MAX_VALUE)textSize = typedArray.getDimension(R.styleable.ExpandTextView_contentTextSize, 16f)expandEnable = typedArray.getBoolean(R.styleable.ExpandTextView_expandEnable, false)isBold = typedArray.getBoolean(R.styleable.ExpandTextView_isBold, false)typedArray.recycle()initView()}private fun initView() {binding = ViewRichBinding.inflate(LayoutInflater.from(context), this, true)bindContent()}fun bindContent(s: String = "",array: List<ContentBean> = arrayListOf(),moreback: (view: TextView?) -> Unit = {}): ContentView {try {if (!::binding.isInitialized) {return this}this.moreBack = morebackif (s?.isEmpty() == true && array.isNullOrEmpty()) isVisible = truebuild(content(s, array))} catch (e: Exception) {Timber.e(e)}return this;}private fun content(str: String = "", array: List<ContentBean>): String {var s = ""if (array.isEmpty()) {s = "$str"} else {array.forEach {when (it.type()) {ContentType.TEXT -> s += "${it.content}"ContentType.TOPIC -> s += "<a href='{type:1,desc:\"话题详情\", id:${it.id}}'>#${it.content}</a>"ContentType.USER -> s += "<a href='{type:0, desc:\"用户\", id:${it.id}}'>@${it.content}</a>"}}}return s;}fun build(content: String? = "") {binding.content.setUrlColor(urltextColor).setUrlBlod(isBold).setText(content).setTextColor(textColor).setTextSize(textSize).setShowLines(showLines).setExpandEnable(expandEnable).setSpanClickable(true, object :CustomTextView.TextSpanClickListener {//设置有标签或@某人的点击, 默认为falseoverride fun onTextSpanClick(data: String?) {try {val jsonObject = JSONObject(data)val type = jsonObject.getInt("type")val id = jsonObject.getString("id")when (type) {0 -> {if (id.isNotEmpty()) {TTRouter.goOtherUser(id.toLong())}}1 -> {if (id.isNotEmpty()) {TTRouter.goTopicManagerN(id)}}}} catch (e: JSONException) {e.printStackTrace()}}}).setMore(moreBack).requestLayout()}fun setTextColor(textColor: Int): ContentView {binding.content.setTextColor(textColor)return this}fun setTextSize(textSize: Float): ContentView {binding.content.setTextSize(textSize)return this}fun setUrlColor(color: Int): ContentView {this.urltextColor = colorreturn this}fun setUrlBlod(bold: Boolean): ContentView {this.isBold = boldreturn this}}
package com.tongtong.feat_watch.viewimport android.annotation.SuppressLint
import android.content.Context
import android.graphics.Color
import android.text.Html
import android.text.SpannableStringBuilder
import android.text.TextPaint
import android.text.TextUtils
import android.text.method.LinkMovementMethod
import android.text.style.ClickableSpan
import android.text.style.URLSpan
import android.text.util.Linkify
import android.util.AttributeSet
import android.view.View
import android.view.ViewGroup
import android.widget.TextView
import com.google.android.material.chip.ChipGroup
import com.tongtong.feat_watch.R/*** @author: shuhuai* @desc:* @date: 2024/12/4* @version:* @remark*/
class CustomTextView @JvmOverloads constructor(context: Context,attrs: AttributeSet? = null,defStyleAttr: Int = 0
) :ChipGroup(context, attrs, defStyleAttr) {//是否可折叠private var expandEnable = false//文本内容private var text: String? = null//文本颜色private var textColor: Intprivate var mtextColor: Intprivate var urltextColor: Int//字体大小private var textSize: Floatprivate var mtextSize: Float//折叠后显示的行数private var showLines: Int//内容文本private var mTextView: NonScrollingTextView? = nullprivate var moreTextView: NonScrollingTextView? = null//true, 表示拦截标签span点击事件//false, 表示普通文本private var spanClickable: Booleanprivate var isBold: Boolean//spanString点击的回调interface TextSpanClickListener {fun onTextSpanClick(data: String?)}private var mTextSpanClick: TextSpanClickListener? = nullprivate fun init(context: Context) {removeAllViews()chipSpacingVertical = 0chipSpacingHorizontal = 0val params =LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT)layoutParams = params//内容显示文本mTextView = NonScrollingTextView(context)// mTextView.setText(text);mTextView!!.textSize = textSizemTextView!!.setTextColor(textColor)mTextView!!.ellipsize = TextUtils.TruncateAt.ENDmTextView!!.layoutParams = paramsmTextView!!.maxLines = showLines//根据SpanClickable状态来设置文本setTextBySpanClickableStatus()mTextView!!.movementMethod = LinkMovementMethod.getInstance()addView(mTextView)moreTextView = NonScrollingTextView(context)moreTextView!!.setText("...展开");moreTextView!!.textSize = mtextSizemoreTextView!!.setTextColor(mtextColor)addView(moreTextView)}init {val typedArray = context.obtainStyledAttributes(attrs, R.styleable.ExpandTextView)textColor = typedArray.getColor(R.styleable.ExpandTextView_contentTextColor,context.resources.getColor(R.color.color_131314))mtextColor = typedArray.getColor(R.styleable.ExpandTextView_moreTextColor,context.resources.getColor(R.color.extended_color))urltextColor = typedArray.getColor(R.styleable.ExpandTextView_urlTextColor,context.resources.getColor(R.color.color_5D34D0))textSize = typedArray.getDimension(R.styleable.ExpandTextView_contentTextSize, 16f)mtextSize = typedArray.getDimension(R.styleable.ExpandTextView_moreTextSize, 16f)spanClickable = typedArray.getBoolean(R.styleable.ExpandTextView_spanClickable, false)showLines = typedArray.getColor(R.styleable.ExpandTextView_showLines, Int.MAX_VALUE)expandEnable = typedArray.getBoolean(R.styleable.ExpandTextView_expandEnable, false)isBold = typedArray.getBoolean(R.styleable.ExpandTextView_isBold, false)typedArray.recycle()init(context)}@SuppressLint("RestrictedApi")override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {super.onMeasure(widthMeasureSpec, heightMeasureSpec)if (expandEnable) {moreTextView!!.visibility = if (mTextView!!.lineCount > showLines) VISIBLE else GONE}else {moreTextView!!.visibility = GONE}}/*** 将dip或dp值转换为px值,保证尺寸大小不变*/private fun dip2px(context: Context, dipValue: Float): Int {val scale = context.resources.displayMetrics.densityreturn (dipValue * scale + 0.5f).toInt()}fun setText(text: String?): CustomTextView {this.text = textsetTextBySpanClickableStatus()return this}fun setTextColor(textColor: Int): CustomTextView {this.textColor = textColormTextView!!.setTextColor(textColor)return this}fun setTextSize(textSize: Float): CustomTextView {this.textSize = textSizemTextView!!.textSize = textSizereturn this}fun setExpandEnable(able: Boolean): CustomTextView {this.expandEnable = ablereturn this}fun setShowLines(lines: Int): CustomTextView {this.showLines = linesmTextView?.maxLines = linesreturn this}fun setSpanClickable(spanClickable: Boolean,textSpanClick: TextSpanClickListener): CustomTextView {this.spanClickable = spanClickablemTextSpanClick = textSpanClicksetTextBySpanClickableStatus()return this}fun setUrlColor(color: Int): CustomTextView {this.urltextColor = colorreturn this}fun setUrlBlod(bold: Boolean): CustomTextView {this.isBold = boldreturn this}/*** 格式化超链接文本内容并设置点击处理*/private fun getClickableHtml(html: String?): CharSequence {val spannedHtml = Html.fromHtml(html)val clickableHtmlBuilder = SpannableStringBuilder(spannedHtml)val urls = clickableHtmlBuilder.getSpans(0, spannedHtml.length,URLSpan::class.java)for (span in urls) {setLinkClickable(clickableHtmlBuilder, span)}return clickableHtmlBuilder}/*** 设置点击超链接对应的处理内容*/private fun setLinkClickable(clickableHtmlBuilder: SpannableStringBuilder, urlSpan: URLSpan) {val start = clickableHtmlBuilder.getSpanStart(urlSpan)val end = clickableHtmlBuilder.getSpanEnd(urlSpan)val flags = clickableHtmlBuilder.getSpanFlags(urlSpan)clickableHtmlBuilder.setSpan(object : ClickableSpan() {override fun onClick(view: View) {if (mTextSpanClick != null) {//取出a标签的href携带的数据, 并回调到调用处//href的数据类型根据个人业务来定, demo是传的json字符串mTextSpanClick!!.onTextSpanClick(urlSpan.url)}}override fun updateDrawState(ds: TextPaint) {super.updateDrawState(ds)ds.linkColor = Color.TRANSPARENTds.color = urltextColords.isUnderlineText = falseds.isFakeBoldText = isBold}}, start, end, flags)}/*** 根据SpanClickable的状态来设置文本*/private fun setTextBySpanClickableStatus() {if (!TextUtils.isEmpty(text)) {if (spanClickable) {mTextView!!.autoLinkMask = Linkify.ALLmTextView!!.text = getClickableHtml(text)} else {mTextView!!.text = text}}}fun setMore(moreback: (view: TextView?) -> Unit = {}): CustomTextView {moreTextView?.setOnClickListener{moreback.invoke(moreTextView)}return this}}
attrs.xml
<?xml version="1.0" encoding="utf-8"?>
<resources><declare-styleable name="ExpandTextView"><!--内容文本颜色--><attr name="contentTextColor" format="reference|color"/><attr name="moreTextColor" format="reference|color"/><attr name="urlTextColor" format="reference|color"/><!--内容文本字体大小--><attr name="contentTextSize" format="dimension"/><attr name="moreTextSize" format="dimension"/><!--按钮文本颜色--><attr name="btnTextColor" format="reference|color"/><!--按钮折叠的描述--><attr name="btnExpandText" format="string"/><!--按钮展开的描述--><attr name="btnSpreadText" format="string"/><!--按钮字体大小--><attr name="btnTextSize" format="dimension"/><!--最大显示行数--><attr name="showLines" format="integer"/><!--是否显示箭头图标--><attr name="showIcon" format="boolean"/><!--箭头资源--><attr name="iconRes" format="reference"/><!--动画时长--><attr name="animationDuration" format="integer"/><!--@或者#话题#是否自定义点击事件--><attr name="spanClickable" format="boolean"/><!--是否可折叠--><attr name="expandEnable" format="boolean"/><attr name="isBold" format="boolean"/></declare-styleable>
</resources>
这个组件还有一点问题, 就是 富文本形式下,限制行数,不展示… 需要优化下,可以考虑下从html标签入手,或者直接自己画一个,这个就稍微麻烦点
后面有时间再优化吧