
今天擼一個(gè) 文字顯示不下?lián)Q行顯示的view
首先聊天頁面顯示文本 有一個(gè)最低高度 和最大寬度,這里直接就寫死,或者寫屏幕尺寸比例均可。
先定義需要的變量如:最大寬度、 view的寬高、畫筆、間距、x軸邊距等等
// 顯示聊天內(nèi)容的畫筆
private lateinit var mTextPaint: TextPaint
// 顯示時(shí)間 和 繪制圖標(biāo)的畫筆
private lateinit var mPaint: Paint
// 顯示文本內(nèi)容
private lateinit var staticLayout: StaticLayout
// 點(diǎn)擊的文本類型
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()
// 繪制時(shí)間兩側(cè)圖標(biāo)的間隔
private val space = 5.dp2Px()
// 距離左邊X軸的邊距
private var leftX = 0
// 距離上邊Y軸的邊距
private var topY = 0
// 發(fā)送狀態(tài)的圖標(biāo)
private lateinit var readStateBitmap: Bitmap
// 發(fā)送的狀態(tài)
private var sendState = 0
// 顯示已讀的狀態(tài)
private var readState = 0
// 置頂?shù)膱D標(biāo)
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
在設(shè)置顯示內(nèi)容時(shí),處理一下表情顯示異常問題,還有特殊文本顯示問題例如 @某某某,鏈接等,在繪制的時(shí)候還要處理字符加粗還是正常顯示,畫筆需要自己實(shí)現(xiàn)
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
// 表情符號(hào)大小為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
}
然后是測(cè)量文本內(nèi)容的寬高,在這里用的是StaticLayout,如果一行可以顯示下,就正常顯示 在右側(cè)繪制出顯示的時(shí)間和狀態(tài)圖標(biāo),如果顯示不下,那么添加一行高度,在最右側(cè)繪制;如果是多行,就計(jì)算出最后一行的文本寬度,邏輯如此。
private fun createLayout() {
val textWidthRect = mTextPaint.measureText(textContent.toString())
val staticLayoutWidth =
(if (textWidthRect >= mMaxWidth) mMaxWidth else textWidthRect).toInt()
// 先計(jì)算發(fā)送狀態(tài)的寬度
val sendStateWidth =
if (!isTopReadState && sendState == 1) readStateBitmap.width + space else 0
// 右側(cè)時(shí)間發(fā)送狀態(tài)布局的寬度 = 發(fā)送狀態(tài)的寬度 + 時(shí)間寬度 + 間距 + 置頂寬度
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()
}
/**
* 總寬度 如果超出一行 那么取最大寬度
* 如果是一行 那么計(jì)算 總寬度 = 文本 + 右側(cè)時(shí)間發(fā)送狀態(tài)布局的寬度
*/
mWidth = if (staticLayout.lineCount > 1) {
// 取最大寬度
val width = max(textWidth.toFloat(), lineWidth)
// 如果最后一行 加上時(shí)間寬度 小于最大寬度
if (lineWidth + timeLayoutWidth <= mMaxWidth) {
// 如果最后一行 加上時(shí)間寬度 小于最大寬度
if (lineWidth + timeLayoutWidth < width) {
// 文本寬度小于時(shí)間寬度
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()
}
}
/**
* 高度取決于最后一行文本的寬度 如果時(shí)間和圖標(biāo)顯示不下 那么就添加一行高度
* 顯示不下:
* 高度 = 文本高度 + + 上下邊距 + (單行文本高度和間距)
* 一行顯示:
* 高度 = 文本高度 + 上下邊距
*/
// 先判斷最后一行文本寬度是否能顯示下,總寬度 - 左右間距 - 右側(cè)時(shí)間和左右圖標(biāo)的寬度間距
mHeight = if (lineWidth > mMaxWidth - timeLayoutWidth) {
staticLayout.height / staticLayout.lineCount + staticLayout.height - space
} else {
staticLayout.height
}
leftX = 0
topY = 0
}
剩下的就簡(jiǎn)單了,計(jì)算繪制就可以了
override fun onDraw(canvas: Canvas) {
super.onDraw(canvas)
createLayout()
// 先繪制文本
canvas.save()
canvas.translate(leftX.toFloat(), topY.toFloat())
staticLayout.draw(canvas)
if (!isTopReadState && sendState == 1) {
// 繪制右側(cè)發(fā)送狀態(tài)的圖標(biāo)
leftX = mWidth - readStateBitmap.width
// 右側(cè)發(fā)送狀態(tài)圖標(biāo)較大 稍微偏下一點(diǎn)點(diǎn)
topY = mHeight - readStateBitmap.height + space / 2
canvas.drawBitmap(readStateBitmap, leftX.toFloat(), topY.toFloat(), mPaint)
}
// 繪制時(shí)間
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)
}
}
最后處理點(diǎn)擊事件,因?yàn)镾taticLayout繪制,SpannableStringBuilder樣式可以顯示,但點(diǎn)擊事件并不行(這里我試過好多次,也換好幾種方式,都不支持點(diǎn)擊事件,不知道是不是我的姿勢(shì)不對(duì),如果有人實(shí)現(xiàn)了那么請(qǐng)@我,留下代碼,讓我學(xué)習(xí)學(xué)習(xí)),因?yàn)轱@示的時(shí)候是SpannableStringBuilder,但是點(diǎn)擊的時(shí)候計(jì)算的位置,所以點(diǎn)擊處理用的是原始沒有處理過的文本數(shù)據(jù),然后拆分判斷點(diǎn)擊的是某個(gè)@或鏈接,(當(dāng)時(shí)都要吐血了) 先正則判斷是什么,在進(jìn)行替換,然后計(jì)算字符,響應(yīng)點(diǎn)擊事件。
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)
// 進(jìn)行正則匹配[文字](鏈接)
val spannableString = SpannableStringBuilder(textContentClick)
clickTextContentUrl(
clickTextContentAt(textContentClick, off, spannableString),
off,
spannableString
)
}
}
}
return super.onTouchEvent(event)
}
/**
* 處理點(diǎn)擊的是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)
// 把匹配成功的串a(chǎn)ppend進(jìn)結(jié)果串中, 并設(shè)置點(diǎn)擊效果
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) {
// 點(diǎn)擊事件并不靈
}
}
spannableString.setSpan(
clickableSpan,
clickSpanStartAt,
clickSpanEndAt,
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE
)
// 點(diǎn)擊回調(diào)
if (clickSpanStartAt <= off && off <= clickSpanEndAt) {
postDelayed({ onClickListener.invoke(uid, TEXT_TYPE_AT) }, 100)
break
}
}
}
} catch (e: java.lang.Exception) {
e.printStackTrace()
}
return spannableString
}
/**
* 處理點(diǎn)擊的是鏈接
*/
private fun clickTextContentUrl(
text: CharSequence,
off: Int,
spannableString: SpannableStringBuilder
) {
try {
//超鏈接轉(zhuǎn)化
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) {
// 顯示聊天內(nèi)容的畫筆
private lateinit var mTextPaint: TextPaint
// 顯示時(shí)間 和 繪制圖標(biāo)的畫筆
private lateinit var mPaint: Paint
// 顯示文本內(nèi)容
private lateinit var staticLayout: StaticLayout
// 點(diǎn)擊的文本類型
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()
// 繪制時(shí)間兩側(cè)圖標(biāo)的間隔
private val space = 5.dp2Px()
// 距離左邊X軸的邊距
private var leftX = 0
// 距離上邊Y軸的邊距
private var topY = 0
// 發(fā)送狀態(tài)的圖標(biāo)
private lateinit var readStateBitmap: Bitmap
// 發(fā)送的狀態(tài)
private var sendState = 0
// 顯示已讀的狀態(tài)
private var readState = 0
// 置頂?shù)膱D標(biāo)
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
// 繪制的時(shí)間
private var time: String = "00:00"
// 時(shí)間的文本寬度
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
// 表情符號(hào)大小為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()
}
/**
* 處理點(diǎn)擊的是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)
// 把匹配成功的串a(chǎn)ppend進(jìn)結(jié)果串中, 并設(shè)置點(diǎn)擊效果
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
}
/**
* 處理點(diǎn)擊的是鏈接
*/
private fun clickTextContentUrl(
text: CharSequence,
off: Int,
spannableString: SpannableStringBuilder,
) {
try {
//超鏈接轉(zhuǎn)化
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)
// 進(jìn)行正則匹配[文字](鏈接)
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()
// 先計(jì)算發(fā)送狀態(tài)的寬度
val sendStateWidth =
if (!isTopReadState && sendState == 1) readStateBitmap.width + space else 0
// 右側(cè)時(shí)間發(fā)送狀態(tài)布局的寬度 = 發(fā)送狀態(tài)的寬度 + 時(shí)間寬度 + 間距 + 置頂寬度
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()
}
/**
* 總寬度 如果超出一行 那么取最大寬度
* 如果是一行 那么計(jì)算 總寬度 = 文本 + 右側(cè)時(shí)間發(fā)送狀態(tài)布局的寬度
*/
mWidth = if (staticLayout.lineCount > 1) {
// 取最大寬度
val width = max(textWidth.toFloat(), lineWidth)
// 如果最后一行 加上時(shí)間寬度 小于最大寬度
if (lineWidth + timeLayoutWidth <= mMaxWidth) {
// 如果最后一行 加上時(shí)間寬度 小于最大寬度
if (lineWidth + timeLayoutWidth < width) {
// 文本寬度小于時(shí)間寬度
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()
}
}
/**
* 高度取決于最后一行文本的寬度 如果時(shí)間和圖標(biāo)顯示不下 那么就添加一行高度
* 顯示不下:
* 高度 = 文本高度 + + 上下邊距 + (單行文本高度和間距)
* 一行顯示:
* 高度 = 文本高度 + 上下邊距
*/
// 先判斷最后一行文本寬度是否能顯示下,總寬度 - 左右間距 - 右側(cè)時(shí)間和左右圖標(biāo)的寬度間距
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) {
// 繪制右側(cè)發(fā)送狀態(tài)的圖標(biāo)
leftX = mWidth - readStateBitmap.width
// 右側(cè)發(fā)送狀態(tài)圖標(biāo)較大 稍微偏下一點(diǎn)點(diǎn)
topY = mHeight - readStateBitmap.height + space / 2
canvas.drawBitmap(readStateBitmap, leftX.toFloat(), topY.toFloat(), mPaint)
}
// 繪制時(shí)間
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) {
}
}
基本上就是全部代碼了,其中有自己不需要的進(jìn)行剔除。
好久沒更新,等有時(shí)間會(huì)進(jìn)行整理,然后在給出git。