今天撸一个 文字显示不下换行显示的view
首先聊天页面显示文本 有一个最低高度 和最大宽度,这里直接就写死,或者写屏幕尺寸比例均可。
先定义需要的变量如:最大宽度、 view的宽高、画笔、间距、x轴边距等等
// 显示聊天内容的画笔
private lateinit var mTextPaint: TextPaint
// 显示时间 和 绘制图标的画笔
private lateinit var mPaint: Paint
// 显示文本内容
private lateinit var staticLayout: StaticLayout
// 点击的文本类型
companion object {
const val TEXT_TYPE_LINK = 1
const val TEXT_TYPE_AT = 2
// const val TEXT_TYPE_PHONE = 3
}
private lateinit var onClickListener: (str: String?, textType: Int) -> Unit
// view的宽高
private var mWidth = 0
private var mHeight = 0
private var textWidth = 0
// private var textHeight = 0
// 最大总宽度
private val mMaxWidth = 242.dp2Px()
// 绘制时间两侧图标的间隔
private val space = 5.dp2Px()
// 距离左边X轴的边距
private var leftX = 0
// 距离上边Y轴的边距
private var topY = 0
// 发送状态的图标
private lateinit var readStateBitmap: Bitmap
// 发送的状态
private var sendState = 0
// 显示已读的状态
private var readState = 0
// 置顶的图标
private lateinit var topBitmap: Bitmap
// 是否置顶
private var isTopMsg: Boolean = false
// 如果是true 隐藏
private var isTopReadState = false
// 绘制的文本
private var textContent: CharSequence = ""
private var textContentClick: CharSequence = ""
// 最后一行文本的宽度
private var lineWidth: Float = 0f
在设置显示内容时,处理一下表情显示异常问题,还有特殊文本显示问题例如 @某某某,链接等,在绘制的时候还要处理字符加粗还是正常显示,画笔需要自己实现
fun setTimePaint(paint: Paint): ChatTextViewLayout {
this.mPaint = paint
return this
}
fun setTextPaint(textPaint: TextPaint): ChatTextViewLayout {
this.mTextPaint = textPaint
return this
}
fun setTextContent(text: CharSequence): ChatTextViewLayout {
val spannableStringBuilder = SpannableStringBuilder(text.trim())
// 判断是否包含表情
if (EmojiUtils.containsEmoji(spannableStringBuilder.toString())) {
val fontMetrics: Paint.FontMetrics = mTextPaint.fontMetrics
val defaultEmojiSize = fontMetrics.descent - fontMetrics.ascent
// 表情符号大小为55f
EmojiManager.getInstance().replaceWithImages(context, spannableStringBuilder, 55f)
// EmojiManager.getInstance().replaceWithImages(context, spannableStringBuilder, defaultEmojiSize)
}
this.textContentClick = spannableStringBuilder
this.textContent = AtUserHelper.parseAtUserLinkJx(spannableStringBuilder,
ContextCompat.getColor(context, R.color.color_at), object : AtUserLinkOnClickListener {
override fun ulrLinkClick(str: String?) {
// onClickListener.invoke(str, TEXT_TYPE_LINK)
}
override fun atUserClick(str: String?) {
// onClickListener.invoke(str, TEXT_TYPE_AT)
}
override fun phoneClick(str: String?) {
}
}).trim()
return this
}
然后是测量文本内容的宽高,在这里用的是StaticLayout,如果一行可以显示下,就正常显示 在右侧绘制出显示的时间和状态图标,如果显示不下,那么添加一行高度,在最右侧绘制;如果是多行,就计算出最后一行的文本宽度,逻辑如此。
private fun createLayout() {
val textWidthRect = mTextPaint.measureText(textContent.toString())
val staticLayoutWidth =
(if (textWidthRect >= mMaxWidth) mMaxWidth else textWidthRect).toInt()
// 先计算发送状态的宽度
val sendStateWidth =
if (!isTopReadState && sendState == 1) readStateBitmap.width + space else 0
// 右侧时间发送状态布局的宽度 = 发送状态的宽度 + 时间宽度 + 间距 + 置顶宽度
val timeLayoutWidth =
sendStateWidth + timeWidth + space * 2 + if (isTopMsg) topBitmap.width + space else 0
// 字符串不包含换行 并且宽度小于等于最大宽度 那么就是一行
staticLayout = StaticLayout.Builder
.obtain(textContent, 0, textContent.length, mTextPaint, mMaxWidth)
.setText(textContent)
.setAlignment(Layout.Alignment.ALIGN_NORMAL)
.setLineSpacing(0.0f, 1.0f)
.setIncludePad(false)
.build()
try {
textWidth = 0
for (i in 0 until staticLayout.lineCount) {
try {
lineWidth = staticLayout.getLineWidth(i)
if (lineWidth >= staticLayoutWidth) {
lineWidth = staticLayoutWidth.toFloat()
}
} catch (e: Exception) {
e.printStackTrace()
break
}
textWidth = max(textWidth.toDouble(), ceil(lineWidth.toDouble())).toInt()
}
} catch (e: Exception) {
e.printStackTrace()
}
/**
* 总宽度 如果超出一行 那么取最大宽度
* 如果是一行 那么计算 总宽度 = 文本 + 右侧时间发送状态布局的宽度
*/
mWidth = if (staticLayout.lineCount > 1) {
// 取最大宽度
val width = max(textWidth.toFloat(), lineWidth)
// 如果最后一行 加上时间宽度 小于最大宽度
if (lineWidth + timeLayoutWidth <= mMaxWidth) {
// 如果最后一行 加上时间宽度 小于最大宽度
if (lineWidth + timeLayoutWidth < width) {
// 文本宽度小于时间宽度
if (staticLayoutWidth <= timeLayoutWidth) {
(staticLayoutWidth + timeLayoutWidth).toInt()
} else {
staticLayoutWidth
}
} else {
(lineWidth + timeLayoutWidth).toInt()
}
} else {
width.toInt()
}
} else {
if (lineWidth > mMaxWidth - timeLayoutWidth) {
staticLayoutWidth
} else {
(lineWidth + timeLayoutWidth).toInt()
}
}
/**
* 高度取决于最后一行文本的宽度 如果时间和图标显示不下 那么就添加一行高度
* 显示不下:
* 高度 = 文本高度 + + 上下边距 + (单行文本高度和间距)
* 一行显示:
* 高度 = 文本高度 + 上下边距
*/
// 先判断最后一行文本宽度是否能显示下,总宽度 - 左右间距 - 右侧时间和左右图标的宽度间距
mHeight = if (lineWidth > mMaxWidth - timeLayoutWidth) {
staticLayout.height / staticLayout.lineCount + staticLayout.height - space
} else {
staticLayout.height
}
leftX = 0
topY = 0
}
剩下的就简单了,计算绘制就可以了
override fun onDraw(canvas: Canvas) {
super.onDraw(canvas)
createLayout()
// 先绘制文本
canvas.save()
canvas.translate(leftX.toFloat(), topY.toFloat())
staticLayout.draw(canvas)
if (!isTopReadState && sendState == 1) {
// 绘制右侧发送状态的图标
leftX = mWidth - readStateBitmap.width
// 右侧发送状态图标较大 稍微偏下一点点
topY = mHeight - readStateBitmap.height + space / 2
canvas.drawBitmap(readStateBitmap, leftX.toFloat(), topY.toFloat(), mPaint)
}
// 绘制时间
leftX = if (leftX == 0) {
mWidth - timeWidth.toInt() - space
} else {
leftX - timeWidth.toInt() - space
}
topY = mHeight
canvas.drawText(time, 0, time.length, leftX.toFloat(), topY.toFloat(), mPaint)
// 如果置顶绘制置顶
if (isTopMsg) {
leftX = leftX - topBitmap.width - space
topY = mHeight - topBitmap.height
canvas.drawBitmap(topBitmap, leftX.toFloat(), topY.toFloat(), mPaint)
}
}
最后处理点击事件,因为StaticLayout绘制,SpannableStringBuilder样式可以显示,但点击事件并不行(这里我试过好多次,也换好几种方式,都不支持点击事件,不知道是不是我的姿势不对,如果有人实现了那么请@我,留下代码,让我学习学习),因为显示的时候是SpannableStringBuilder,但是点击的时候计算的位置,所以点击处理用的是原始没有处理过的文本数据,然后拆分判断点击的是某个@或链接,(当时都要吐血了) 先正则判断是什么,在进行替换,然后计算字符,响应点击事件。
override fun onTouchEvent(event: MotionEvent): Boolean {
when (event.action) {
MotionEvent.ACTION_UP -> {
if (event.x >= 0f && event.x <= staticLayout.width && event.y >= 0f && event.y <= staticLayout.height) {
val line: Int = staticLayout.getLineForVertical(event.y.toInt())
val off: Int = staticLayout.getOffsetForHorizontal(line, event.x)
// 进行正则匹配[文字](链接)
val spannableString = SpannableStringBuilder(textContentClick)
clickTextContentUrl(
clickTextContentAt(textContentClick, off, spannableString),
off,
spannableString
)
}
}
}
return super.onTouchEvent(event)
}
/**
* 处理点击的是At
*/
private fun clickTextContentAt(
text: CharSequence,
off: Int,
spannableString: SpannableStringBuilder
): SpannableStringBuilder {
try {
val matcherAt = Pattern.compile(AT_PATTERN).matcher(text)
var replaceOffsetAt = 0 //每次替换之后matcher的偏移量
while (matcherAt.find()) {
// 解析链接 格式是[文字](链接)
val name = matcherAt.group(0)
val uid = name?.substring(2, name.length - 1)
// 把匹配成功的串append进结果串中, 并设置点击效果
val groupMemberBean = uid?.let { getGroupDb().getAllMemberById(it) }
if (groupMemberBean != null) {
val atName = "@" + groupMemberBean.name + " "
val clickSpanStartAt = matcherAt.start() - replaceOffsetAt
val clickSpanEndAt = clickSpanStartAt + atName.length
spannableString.replace(
matcherAt.start() - replaceOffsetAt,
matcherAt.end() - replaceOffsetAt,
atName
)
replaceOffsetAt += matcherAt.end() - matcherAt.start() - atName.length
val clickableSpan = object : ClickableSpan() {
override fun onClick(view: View) {
// 点击事件并不灵
}
}
spannableString.setSpan(
clickableSpan,
clickSpanStartAt,
clickSpanEndAt,
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE
)
// 点击回调
if (clickSpanStartAt <= off && off <= clickSpanEndAt) {
postDelayed({ onClickListener.invoke(uid, TEXT_TYPE_AT) }, 100)
break
}
}
}
} catch (e: java.lang.Exception) {
e.printStackTrace()
}
return spannableString
}
/**
* 处理点击的是链接
*/
private fun clickTextContentUrl(
text: CharSequence,
off: Int,
spannableString: SpannableStringBuilder
) {
try {
//超链接转化
val matcher = Pattern.compile(AtUserHelper.URL_PATTERN).matcher(text)
var replaceOffset = 0 //每次替换之后matcher的偏移量
while (matcher.find()) {
// 解析链接 格式是[文字](链接)
val name = matcher.group(0)
val clickSpanStart = matcher.start() - replaceOffset
val clickSpanEnd = clickSpanStart + (name?.length ?: 0)
spannableString.replace(
matcher.start() - replaceOffset,
matcher.end() - replaceOffset,
name
)
replaceOffset += matcher.end() - matcher.start() - (name?.length ?: 0)
val clickableSpan = object : ClickableSpan() {
override fun onClick(view: View) {
}
}
spannableString.setSpan(
clickableSpan,
clickSpanStart,
clickSpanEnd,
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE
)
if (clickSpanStart <= off && off <= clickSpanEnd) {
postDelayed({ onClickListener.invoke(name, TEXT_TYPE_LINK) }, 100)
break
}
}
} catch (e: java.lang.Exception) {
e.printStackTrace()
}
}
下面是完整代码
import android.annotation.SuppressLint
import android.content.Context
import android.graphics.*
import android.text.*
import android.text.style.ClickableSpan
import android.util.AttributeSet
import android.view.*
import androidx.core.content.ContextCompat
import com.blankj.utilcode.util.StringUtils
import com.vanniktech.emoji.EmojiManager
import com.ym.base.ext.dp2Px
import com.ym.chat.R
import com.ym.chat.db.ChatDao.getGroupDb
import com.ym.chat.ext.ORIENTATION_LEFT
import com.ym.chat.utils.EmojiUtils
import com.ym.chat.utils.StringExt.AT_PATTERN
import com.ym.chat.widget.ateditview.AtUserHelper
import com.ym.chat.widget.ateditview.AtUserLinkOnClickListener
import java.util.regex.Pattern
import kotlin.math.ceil
import kotlin.math.max
/**
* description:
*
* @author Db_z
* @Date 2023/1/16 13:12
*/
class ChatTextViewLayout @JvmOverloads constructor(
context: Context, attrs: AttributeSet? = null,
defStyleAttr: Int = 0,
) : ViewGroup(context, attrs, defStyleAttr) {
// 显示聊天内容的画笔
private lateinit var mTextPaint: TextPaint
// 显示时间 和 绘制图标的画笔
private lateinit var mPaint: Paint
// 显示文本内容
private lateinit var staticLayout: StaticLayout
// 点击的文本类型
companion object {
const val TEXT_TYPE_LINK = 1
const val TEXT_TYPE_AT = 2
// const val TEXT_TYPE_PHONE = 3
}
private lateinit var onClickListener: (str: String?, textType: Int) -> Unit
// view的宽高
private var mWidth = 0
private var mHeight = 0
private var textWidth = 0
// private var textHeight = 0
// 最大总宽度
private val mMaxWidth = 242.dp2Px()
// 绘制时间两侧图标的间隔
private val space = 5.dp2Px()
// 距离左边X轴的边距
private var leftX = 0
// 距离上边Y轴的边距
private var topY = 0
// 发送状态的图标
private lateinit var readStateBitmap: Bitmap
// 发送的状态
private var sendState = 0
// 显示已读的状态
private var readState = 0
// 置顶的图标
private lateinit var topBitmap: Bitmap
// 是否置顶
private var isTopMsg: Boolean = false
// 如果是true 隐藏
private var isTopReadState = false
// 绘制的文本
private var textContent: CharSequence = ""
private var textContentClick: CharSequence = ""
// 最后一行文本的宽度
private var lineWidth: Float = 0f
// 绘制的时间
private var time: String = "00:00"
// 时间的文本宽度
private var timeWidth: Float = 0f
fun setSendState(sendState: Int): ChatTextViewLayout {
this.sendState = sendState
return this
}
fun setReadState(readState: Int, isTop: Boolean): ChatTextViewLayout {
this.readState = readState
isTopReadState = isTop
readStateBitmap = if (readState == 1) {
BitmapFactory.decodeResource(context.resources, R.drawable.iv_text_read)
} else {
BitmapFactory.decodeResource(context.resources, R.drawable.iv_text_unread)
}
return this
}
fun setTimePaint(paint: Paint): ChatTextViewLayout {
this.mPaint = paint
return this
}
fun setTextPaint(textPaint: TextPaint): ChatTextViewLayout {
this.mTextPaint = textPaint
return this
}
fun setTime(time: String): ChatTextViewLayout {
this.time = time
return this
}
fun showTopMsg(isTopMsg: Boolean, orientation: Int = ORIENTATION_LEFT): ChatTextViewLayout {
this.isTopMsg = isTopMsg
topBitmap = if (orientation == ORIENTATION_LEFT) {
BitmapFactory.decodeResource(context.resources, R.drawable.icon_top_grey)
} else {
BitmapFactory.decodeResource(context.resources, R.drawable.icon_top_blue)
}
return this
}
fun setOnClickListener(onClickListener: (str: String?, textType: Int) -> Unit): ChatTextViewLayout {
this.onClickListener = onClickListener
return this
}
fun setTextContent(text: CharSequence): ChatTextViewLayout {
val spannableStringBuilder = SpannableStringBuilder(text.trim())
// 判断是否包含表情
if (EmojiUtils.containsEmoji(spannableStringBuilder.toString())) {
val fontMetrics: Paint.FontMetrics = mTextPaint.fontMetrics
val defaultEmojiSize = fontMetrics.descent - fontMetrics.ascent
// 表情符号大小为55f
EmojiManager.getInstance().replaceWithImages(context, spannableStringBuilder, 55f)
// EmojiManager.getInstance().replaceWithImages(context, spannableStringBuilder, defaultEmojiSize)
}
this.textContentClick = spannableStringBuilder
this.textContent = AtUserHelper.parseAtUserLinkJx(spannableStringBuilder,
ContextCompat.getColor(context, R.color.color_at), object : AtUserLinkOnClickListener {
override fun ulrLinkClick(str: String?) {
// onClickListener.invoke(str, TEXT_TYPE_LINK)
}
override fun atUserClick(str: String?) {
// onClickListener.invoke(str, TEXT_TYPE_AT)
}
override fun phoneClick(str: String?) {
}
}).trim()
return this
}
fun build() {
if (StringUtils.isEmpty(time) || StringUtils.isEmpty(textContent)) return
timeWidth = mPaint.measureText(time)
createLayout()
setWillNotDraw(false)
requestLayout()
}
/**
* 处理点击的是At
*/
private fun clickTextContentAt(
text: CharSequence,
off: Int,
spannableString: SpannableStringBuilder,
): SpannableStringBuilder {
try {
val matcherAt = Pattern.compile(AT_PATTERN).matcher(text)
var replaceOffsetAt = 0 //每次替换之后matcher的偏移量
while (matcherAt.find()) {
// 解析链接 格式是[文字](链接)
val name = matcherAt.group(0)
val uid = name?.substring(2, name.length - 1)
// 把匹配成功的串append进结果串中, 并设置点击效果
val groupMemberBean = uid?.let { getGroupDb().getAllMemberById(it) }
if (groupMemberBean != null) {
val atName = "@" + groupMemberBean.name + " "
val clickSpanStartAt = matcherAt.start() - replaceOffsetAt
val clickSpanEndAt = clickSpanStartAt + atName.length
spannableString.replace(
matcherAt.start() - replaceOffsetAt,
matcherAt.end() - replaceOffsetAt,
atName
)
replaceOffsetAt += matcherAt.end() - matcherAt.start() - atName.length
val clickableSpan = object : ClickableSpan() {
override fun onClick(view: View) {
}
}
spannableString.setSpan(
clickableSpan,
clickSpanStartAt,
clickSpanEndAt,
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE
)
if (off in clickSpanStartAt..clickSpanEndAt) {
postDelayed({ onClickListener.invoke(uid, TEXT_TYPE_AT) }, 100)
break
}
}
}
} catch (e: java.lang.Exception) {
e.printStackTrace()
}
return spannableString
}
/**
* 处理点击的是链接
*/
private fun clickTextContentUrl(
text: CharSequence,
off: Int,
spannableString: SpannableStringBuilder,
) {
try {
//超链接转化
val matcher = Pattern.compile(AtUserHelper.URL_PATTERN).matcher(text)
var replaceOffset = 0 //每次替换之后matcher的偏移量
while (matcher.find()) {
// 解析链接 格式是[文字](链接)
val name = matcher.group(0)
val clickSpanStart = matcher.start() - replaceOffset
val clickSpanEnd = clickSpanStart + (name?.length ?: 0)
spannableString.replace(
matcher.start() - replaceOffset,
matcher.end() - replaceOffset,
name
)
replaceOffset += matcher.end() - matcher.start() - (name?.length ?: 0)
val clickableSpan = object : ClickableSpan() {
override fun onClick(view: View) {
}
}
spannableString.setSpan(
clickableSpan,
clickSpanStart,
clickSpanEnd,
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE
)
if (off in clickSpanStart..clickSpanEnd) {
postDelayed({ onClickListener.invoke(name, TEXT_TYPE_LINK) }, 100)
break
}
}
} catch (e: java.lang.Exception) {
e.printStackTrace()
}
}
@SuppressLint("ClickableViewAccessibility")
override fun onTouchEvent(event: MotionEvent): Boolean {
when (event.action) {
MotionEvent.ACTION_UP -> {
if (event.x >= 0f && event.x <= staticLayout.width && event.y >= 0f && event.y <= staticLayout.height) {
val line: Int = staticLayout.getLineForVertical(event.y.toInt())
val off: Int = staticLayout.getOffsetForHorizontal(line, event.x)
// 进行正则匹配[文字](链接)
val spannableString = SpannableStringBuilder(textContentClick)
clickTextContentUrl(
clickTextContentAt(textContentClick, off, spannableString),
off,
spannableString
)
}
}
}
return super.onTouchEvent(event)
}
private fun createLayout() {
val textWidthRect = mTextPaint.measureText(textContent.toString())
val staticLayoutWidth =
(if (textWidthRect >= mMaxWidth) mMaxWidth else textWidthRect).toInt()
// 先计算发送状态的宽度
val sendStateWidth =
if (!isTopReadState && sendState == 1) readStateBitmap.width + space else 0
// 右侧时间发送状态布局的宽度 = 发送状态的宽度 + 时间宽度 + 间距 + 置顶宽度
val timeLayoutWidth =
sendStateWidth + timeWidth + space * 2 + if (isTopMsg) topBitmap.width + space else 0
// 字符串不包含换行 并且宽度小于等于最大宽度 那么就是一行
staticLayout = StaticLayout.Builder
.obtain(textContent, 0, textContent.length, mTextPaint, mMaxWidth)
.setText(textContent)
.setAlignment(Layout.Alignment.ALIGN_NORMAL)
.setLineSpacing(0.0f, 1.0f)
.setIncludePad(false)
.build()
try {
textWidth = 0
for (i in 0 until staticLayout.lineCount) {
try {
lineWidth = staticLayout.getLineWidth(i)
if (lineWidth >= staticLayoutWidth) {
lineWidth = staticLayoutWidth.toFloat()
}
} catch (e: Exception) {
e.printStackTrace()
break
}
textWidth = max(textWidth.toDouble(), ceil(lineWidth.toDouble())).toInt()
}
} catch (e: Exception) {
e.printStackTrace()
}
/**
* 总宽度 如果超出一行 那么取最大宽度
* 如果是一行 那么计算 总宽度 = 文本 + 右侧时间发送状态布局的宽度
*/
mWidth = if (staticLayout.lineCount > 1) {
// 取最大宽度
val width = max(textWidth.toFloat(), lineWidth)
// 如果最后一行 加上时间宽度 小于最大宽度
if (lineWidth + timeLayoutWidth <= mMaxWidth) {
// 如果最后一行 加上时间宽度 小于最大宽度
if (lineWidth + timeLayoutWidth < width) {
// 文本宽度小于时间宽度
if (staticLayoutWidth <= timeLayoutWidth) {
(staticLayoutWidth + timeLayoutWidth).toInt()
} else {
staticLayoutWidth
}
} else {
(lineWidth + timeLayoutWidth).toInt()
}
} else {
width.toInt()
}
} else {
if (lineWidth > mMaxWidth - timeLayoutWidth) {
staticLayoutWidth
} else {
(lineWidth + timeLayoutWidth).toInt()
}
}
/**
* 高度取决于最后一行文本的宽度 如果时间和图标显示不下 那么就添加一行高度
* 显示不下:
* 高度 = 文本高度 + + 上下边距 + (单行文本高度和间距)
* 一行显示:
* 高度 = 文本高度 + 上下边距
*/
// 先判断最后一行文本宽度是否能显示下,总宽度 - 左右间距 - 右侧时间和左右图标的宽度间距
mHeight = if (lineWidth > mMaxWidth - timeLayoutWidth) {
staticLayout.height / staticLayout.lineCount + staticLayout.height - space
} else {
staticLayout.height
}
leftX = 0
topY = 0
}
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec)
setMeasuredDimension(mWidth, mHeight)
}
override fun onDraw(canvas: Canvas) {
super.onDraw(canvas)
createLayout()
// 先绘制文本
canvas.save()
canvas.translate(leftX.toFloat(), topY.toFloat())
staticLayout.draw(canvas)
if (!isTopReadState && sendState == 1) {
// 绘制右侧发送状态的图标
leftX = mWidth - readStateBitmap.width
// 右侧发送状态图标较大 稍微偏下一点点
topY = mHeight - readStateBitmap.height + space / 2
canvas.drawBitmap(readStateBitmap, leftX.toFloat(), topY.toFloat(), mPaint)
}
// 绘制时间
leftX = if (leftX == 0) {
mWidth - timeWidth.toInt() - space
} else {
leftX - timeWidth.toInt() - space
}
topY = mHeight
canvas.drawText(time, 0, time.length, leftX.toFloat(), topY.toFloat(), mPaint)
// 如果置顶绘制置顶
if (isTopMsg) {
leftX = leftX - topBitmap.width - space
topY = mHeight - topBitmap.height
canvas.drawBitmap(topBitmap, leftX.toFloat(), topY.toFloat(), mPaint)
}
}
override fun onLayout(changed: Boolean, l: Int, t: Int, r: Int, b: Int) {
}
}
基本上就是全部代码了,其中有自己不需要的进行剔除。
好久没更新,等有时间会进行整理,然后在给出git。
网友评论