折叠文本控件FoldTextView
作者:互联网
说明
本来使用这个项目,但里面有个bug,修复一下,特此记录。
属性
<declare-styleable name="FoldTextView">
<attr name="showMaxLine" format="integer" />
<attr name="tipGravity" format="integer" />
<attr name="tipColor" format="reference|color" />
<attr name="tipClickable" format="boolean" />
<attr name="foldText" format="string" />
<attr name="expandText" format="string" />
<attr name="showTipAfterExpand" format="boolean" />
<attr name="isSetParentClick" format="boolean" />
</declare-styleable>
实现
class FoldTextView @JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null,
defStyle: Int = 0
) :
AppCompatTextView(context, attrs, defStyle) {
companion object {
val ELLIPSIZE_END = "..."
val MAX_LINE = 4
val EXPAND_TIP_TEXT = "收起全文"
val FOLD_TIP_TEXT = "查看全文"
val TIP_COLOR = -0x1
val END = 0
}
var logEnable = false
/**
* 显示最大行数
*/
var mShowMaxLine: Int = 0
/**
* 折叠文本
*/
var mFoldText: String = ""
/**
* 展开文本
*/
var mExpandText: String = ""
/**
* 原始文本
*/
var mOriginalText: String = ""
/**
* 是否展开
*/
var isExpand = false
set(value) {
if (field != value) {
field = value
if (!field) {
isOverMaxLine = false
}
text = mOriginalText
}
}
/**
* 全文显示的位置 0末尾 1下一行
*/
var mTipGravity = 0
/**
* 提示文字颜色
*/
var mTipColor: Int = 0
/**
* 提示是否可点击
*/
var mTipClickable = false
var flag = false
var mPaint: Paint = Paint()
/**
* 展开后是否显示文字提示
*/
var isShowTipAfterExpand = false
/**
* 提示文字坐标范围
*/
var minX: Float = 0f
var maxX: Float = 0f
var minY: Float = 0f
var maxY: Float = 0f
/**
* 收起全文不在同一行时,增加一个变量记录坐标
*/
var middleY: Float = 0f
/**
* 原始文本行数
*/
var originalLineCount = 0
/**
* 是否超过最大行数
*/
var isOverMaxLine = false
/**
* 点击时间
*/
var clickTime = 0L
init {
mShowMaxLine = MAX_LINE
if (attrs != null) {
val arr = context.obtainStyledAttributes(attrs, R.styleable.FoldTextView)
mShowMaxLine = arr.getInt(R.styleable.FoldTextView_showMaxLine, MAX_LINE)
mTipGravity = arr.getInt(R.styleable.FoldTextView_tipGravity, FoldTextView.END)
mTipColor = arr.getColor(R.styleable.FoldTextView_tipColor, FoldTextView.TIP_COLOR)
mTipClickable = arr.getBoolean(R.styleable.FoldTextView_tipClickable, false)
mFoldText = arr.getString(R.styleable.FoldTextView_foldText) ?: ""
mExpandText = arr.getString(R.styleable.FoldTextView_expandText) ?: ""
isShowTipAfterExpand =
arr.getBoolean(R.styleable.FoldTextView_showTipAfterExpand, false)
arr.recycle()
}
if (TextUtils.isEmpty(mExpandText)) {
mExpandText = EXPAND_TIP_TEXT
}
if (TextUtils.isEmpty(mFoldText)) {
mFoldText = FOLD_TIP_TEXT
}
if (mTipGravity == END) {
mFoldText = " " + mFoldText
}
mPaint.textSize = textSize
mPaint.color = mTipColor
}
override fun setText(text: CharSequence?, type: BufferType?) {
if (TextUtils.isEmpty(text) || mShowMaxLine == 0) {
super.setText(text, type)
} else if (isExpand) {
//文字展开
val spannable = SpannableStringBuilder(mOriginalText)
if (isShowTipAfterExpand) {
spannable.append(mExpandText)
spannable.setSpan(
ForegroundColorSpan(mTipColor),
spannable.length - mExpandText.length,
spannable.length,
Spanned.SPAN_INCLUSIVE_EXCLUSIVE
)
}
super.setText(spannable, type)
val mLieCount = lineCount
val layout = layout
minX =
paddingLeft + layout.getPrimaryHorizontal(spannable.lastIndexOf(mExpandText[0]) - 1)
maxX =
paddingLeft + layout.getPrimaryHorizontal(spannable.lastIndexOf(mExpandText[mExpandText.length - 1]) + 1)
val bound = Rect()
layout.getLineBounds(originalLineCount - 1, bound)
if (mLieCount > originalLineCount) {
//不在同一行
minY = (paddingTop + bound.top).toFloat()
middleY = minY + paint.fontMetrics.descent - paint.fontMetrics.ascent
maxY = middleY + paint.fontMetrics.descent - paint.fontMetrics.ascent
} else {
//同一行
minY = (paddingTop + bound.top).toFloat()
maxY = minY + paint.fontMetrics.descent - paint.fontMetrics.ascent
}
} else {
if (!flag) {
viewTreeObserver.addOnPreDrawListener(object : ViewTreeObserver.OnPreDrawListener {
override fun onPreDraw(): Boolean {
viewTreeObserver.removeOnPreDrawListener(this)
flag = true
formatText(text, type)
return true
}
})
} else {
formatText(text, type)
}
}
}
fun formatText(text: CharSequence?, type: BufferType?) {
mOriginalText = text.toString()
var l = layout
if (l == null || !l.text.equals(mOriginalText)) {
super.setText(mOriginalText, type)
l = layout
}
if (l == null) {
viewTreeObserver.addOnGlobalLayoutListener(object :
ViewTreeObserver.OnGlobalLayoutListener {
override fun onGlobalLayout() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
viewTreeObserver.removeOnGlobalLayoutListener(this)
}
translateText(layout, type)
}
})
} else {
translateText(l, type)
}
}
private val TAG = "FoldTextView"
fun log(msg: String) {
if (logEnable) {
Log.i(TAG, "log: $msg")
}
}
fun translateText(l: Layout, type: BufferType?) {
//记录原始行数
originalLineCount = l.lineCount
log("lineCount:$originalLineCount,maxLine:$mShowMaxLine")
if (l.lineCount > mShowMaxLine) {
isOverMaxLine = true
val span = SpannableStringBuilder()
val start = l.getLineStart(mShowMaxLine - 1)
var end = l.getLineVisibleEnd(mShowMaxLine - 1)
if (mTipGravity == END) {
val builder = StringBuilder(ELLIPSIZE_END).append(" ").append(mFoldText)
end -= paint.breakText(
mOriginalText,
start,
end,
false,
paint.measureText(builder.toString()),
null
)
} else {
end--;
}
val ellipsize = mOriginalText.subSequence(0, end)
span.append(ellipsize).append(ELLIPSIZE_END)
if (mTipGravity != END) {
span.append("\n")
}
super.setText(span, type)
}else{
isOverMaxLine = false
}
}
override fun onDraw(canvas: Canvas?) {
super.onDraw(canvas)
log("onDraw:isOverMaxLine $isOverMaxLine ,isExpand$isExpand")
if (isOverMaxLine && !isExpand) {
//折叠
if (mTipGravity == END) {
minX = width - paddingLeft - paddingRight - paint.measureText(mFoldText)
maxX = (width - paddingLeft - paddingRight).toFloat()
} else {
minX = paddingLeft.toFloat()
maxX = minX + paint.measureText(mFoldText)
}
minY = height - (paint.fontMetrics.descent - paint.fontMetrics.ascent) - paddingBottom
maxY = (height - paddingBottom).toFloat()
canvas?.drawText(
mFoldText,
minX,
height - paint.fontMetrics.descent - paddingBottom,
mPaint
)
}
}
override fun onTouchEvent(event: MotionEvent?): Boolean {
if (mTipClickable) {
when (event?.actionMasked) {
MotionEvent.ACTION_DOWN -> {
clickTime = System.currentTimeMillis()
if (!isClickable && isInRange(event.x, event.y)) {
return true
}
}
MotionEvent.ACTION_CANCEL,
MotionEvent.ACTION_UP -> {
val delTime = System.currentTimeMillis() - clickTime
clickTime = 0L
if (delTime < ViewConfiguration.getTapTimeout() && isInRange(
event.x,
event.y
)
) {
isExpand = !isExpand
text = mOriginalText
return true
}
}
}
}
return super.onTouchEvent(event)
}
private fun isInRange(x: Float, y: Float): Boolean {
return if (minX < maxX) {
//同一行
x in minX..maxX && y in minY..maxY
} else {
//两行
x <= maxX && y in middleY..maxY || x >= minX && y in minY..middleY
}
}
}
标签:控件,false,val,paint,var,文本,type,FoldTextView 来源: https://www.cnblogs.com/xunevermore/p/16095746.html