ExpandTextView

312 阅读1分钟

package com.xing.expandabletextview

import android.content.Context
import android.util.AttributeSet
import android.util.Log
import android.view.View
import android.widget.RelativeLayout
import android.widget.TextView

class ExpandableTextView @JvmOverloads constructor(
    context: Context,
    attributeSet: AttributeSet? = null,
    defStyleAttr: Int = 0,
) : RelativeLayout(context, attributeSet, defStyleAttr) {

    private var isExpand = true
    private var mMaxLineWhenCollapsed = 3
    var mContentTextView: TextView = TextView(context)
    private lateinit var mExpandCollapseView: View
    private var shouldCollapseInNewLine = false      // 收起是否开启新行
    private var mOnExpandCollapseChangeListener: ((Boolean) -> Unit)? = null

    init {
        val typedArray = context.obtainStyledAttributes(attributeSet, R.styleable.ExpandableTextView)
        mMaxLineWhenCollapsed = typedArray.getInt(R.styleable.ExpandableTextView_maxLinesWhenCollapsed, 1)
        if (mMaxLineWhenCollapsed < 1) {
            mMaxLineWhenCollapsed = 1
        }
        typedArray.recycle()
    }

    override fun onFinishInflate() {
        super.onFinishInflate()
        if (childCount > 1) {
            throw IllegalArgumentException("the ExpandableTextView's children should not more than 1")
        }
        mExpandCollapseView = getChildAt(0)
        mExpandCollapseView.setOnClickListener {
            toggle()
        }
        addView(mContentTextView, 0)
    }

    override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec)
        val mode = MeasureSpec.getMode(heightMeasureSpec)
        var height = MeasureSpec.getSize(heightMeasureSpec)
        val lineCount = mContentTextView.layout.lineCount
        mExpandCollapseView.visibility = if (lineCount > mMaxLineWhenCollapsed) View.VISIBLE else View.GONE
        if (lineCount > mMaxLineWhenCollapsed) {
            if (isExpand) {
                if (mode != MeasureSpec.EXACTLY) {
                    val lastLineWidth = mContentTextView.layout.getLineWidth(lineCount - 1)
                    val lastLineEmpty = measuredWidth - lastLineWidth
                    shouldCollapseInNewLine = lastLineEmpty < mExpandCollapseView.measuredWidth
                    height = if (shouldCollapseInNewLine) {  // 开启新行
                        mContentTextView.measuredHeight + mExpandCollapseView.measuredHeight
                    } else {
                        mContentTextView.measuredHeight
                    }
                }
                setMeasuredDimension(widthMeasureSpec, MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY))
            } else {
                setMeasuredDimension(widthMeasureSpec, mContentTextView.layout.getLineBottom(mMaxLineWhenCollapsed - 1))

            }
        }
    }

    override fun onLayout(changed: Boolean, l: Int, t: Int, r: Int, b: Int) {
        super.onLayout(changed, l, t, r, b)
        if (changed) {
            val lastLine = if (isExpand) mContentTextView.lineCount - 1 else mMaxLineWhenCollapsed - 1
            val lastLineTop = mContentTextView.layout.getLineTop(lastLine)
            val lastLineBottom = mContentTextView.layout.getLineBottom(lastLine)
            // 兼容收起文字大小和content文字大小不一致时,能够相对居中
            val offsetY = (lastLineBottom - lastLineTop - mExpandCollapseView.measuredHeight) / 2
            if (isExpand) {
                val bottom = if (shouldCollapseInNewLine) {
                    mContentTextView.bottom + mExpandCollapseView.height - offsetY
                } else {
                    mContentTextView.bottom - offsetY
                }
                mExpandCollapseView.layout(
                    measuredWidth - mExpandCollapseView.measuredWidth,
                    bottom - mExpandCollapseView.measuredHeight,
                    measuredWidth,
                    bottom
                )
            } else {
                mExpandCollapseView.layout(
                    measuredWidth - mExpandCollapseView.measuredWidth,
                    lastLineBottom - mExpandCollapseView.measuredHeight - offsetY,
                    measuredWidth,
                    lastLineBottom - offsetY
                )

                // 获取最后一行最后一个字符在 TextView 中的索引
                for (line in 0 until mMaxLineWhenCollapsed) {
                    val lineend = mContentTextView.layout.getLineEnd(line)
                    Log.e("debug", "lineEnd = $lineend")
                }
                var lastEndIndex = mContentTextView.layout.getLineEnd(mMaxLineWhenCollapsed - 1) - 1
                val lastStartIndex = mContentTextView.layout.getLineStart(mMaxLineWhenCollapsed - 1)
                Log.e("dcsdcs", "c----->" + mContentTextView.layout.getPrimaryHorizontal(lastEndIndex - 1))
                Log.e("ddd", "lastIndex = $lastEndIndex")


                var endIndex = lastEndIndex
                while (endIndex > lastStartIndex) {
                    // 获取字符右边 X 坐标,相对于 textview
                    val xAxis = mContentTextView.layout.getSecondaryHorizontal(endIndex)
                    if (xAxis < mExpandCollapseView.left) {
                        break
                    }
                    endIndex--
                }
                for (i in endIndex until lastEndIndex) {
                    mContentTextView.text.get(i)
                }
                Log.e("ddd", "lastIndex2 = $lastEndIndex")

            }
        }
    }


    private fun toggle() {
        isExpand = !isExpand
        mOnExpandCollapseChangeListener?.invoke(isExpand)
        requestLayout()
    }

    fun setText(text: CharSequence) {
        mContentTextView.text = text
    }

    fun setOnExpandCollapseChangedListener(listener: (Boolean) -> Unit) {
        this.mOnExpandCollapseChangeListener = listener
    }
}