模拟微博 #热贴 和 @用户 的这种 富文本形式组件,不说了, 直接上代码
package com.tongtong.feat_watch.view
import 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: Int
//是否可折叠
private var expandEnable: Boolean
private lateinit var binding: ViewRichBinding
private 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)
)
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)
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 = moreback
if (s?.isEmpty() == true && array.isNullOrEmpty()) isVisible = true
build(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
.setText(content)
.setTextColor(textColor)
.setTextSize(textSize)
.setShowLines(showLines)
.setExpandEnable(expandEnable)
.setSpanClickable(true, object :
CustomTextView.TextSpanClickListener {
//设置有标签或@某人的点击, 默认为false
override 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
}
}
package com.tongtong.feat_watch.view
import 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: Int
private var mtextColor: Int
//字体大小
private var textSize: Float
private var mtextSize: Float
//折叠后显示的行数
private var showLines: Int
//内容文本
private var mTextView: NonScrollingTextView? = null
private var moreTextView: NonScrollingTextView? = null
//true, 表示拦截标签span点击事件
//false, 表示普通文本
private var spanClickable: Boolean
//spanString点击的回调
interface TextSpanClickListener {
fun onTextSpanClick(data: String?)
}
private var mTextSpanClick: TextSpanClickListener? = null
private fun init(context: Context) {
removeAllViews()
chipSpacingVertical = 0
chipSpacingHorizontal = 0
val params =
LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT)
layoutParams = params
//内容显示文本
mTextView = NonScrollingTextView(context)
// mTextView.setText(text);
mTextView!!.textSize = textSize
mTextView!!.setTextColor(textColor)
mTextView!!.ellipsize = TextUtils.TruncateAt.END
mTextView!!.layoutParams = params
mTextView!!.maxLines = showLines
//根据SpanClickable状态来设置文本
setTextBySpanClickableStatus()
mTextView!!.movementMethod = LinkMovementMethod.getInstance()
addView(mTextView)
moreTextView = NonScrollingTextView(context)
moreTextView!!.setText("...展开");
moreTextView!!.textSize = mtextSize
moreTextView!!.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)
)
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)
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.density
return (dipValue * scale + 0.5f).toInt()
}
fun setText(text: String?): CustomTextView {
this.text = text
setTextBySpanClickableStatus()
return this
}
fun setTextColor(textColor: Int): CustomTextView {
this.textColor = textColor
mTextView!!.setTextColor(textColor)
return this
}
fun setTextSize(textSize: Float): CustomTextView {
this.textSize = textSize
mTextView!!.textSize = textSize
return this
}
fun setExpandEnable(able: Boolean): CustomTextView {
this.expandEnable = able
return this
}
fun setShowLines(lines: Int): CustomTextView {
this.showLines = lines
mTextView?.maxLines = lines
return this
}
fun setSpanClickable(
spanClickable: Boolean,
textSpanClick: TextSpanClickListener
): CustomTextView {
this.spanClickable = spanClickable
mTextSpanClick = textSpanClick
setTextBySpanClickableStatus()
return 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.TRANSPARENT
ds.color = context.getColor(R.color.color_17006C)
ds.isUnderlineText = false
}
}, start, end, flags)
}
/**
* 根据SpanClickable的状态来设置文本
*/
private fun setTextBySpanClickableStatus() {
if (!TextUtils.isEmpty(text)) {
if (spanClickable) {
mTextView!!.autoLinkMask = Linkify.ALL
mTextView!!.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="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"/>
</declare-styleable>
</resources>
这个组件还有一点问题, 就是 富文本形式下,限制行数,不展示… 需要优化下, 后面有时间再优化吧