0

I'm trying to achieve a expandable textview which allow user to expand our collapse text longer than specified lines.
However, when the text is expanded, requestLayout of the TextView was called, but LinearLayout instead of expanding itself, it cut the TextView and the other child inside LinearLayout becomes 0dp. (Measured in Layout Insepctor)

The code I use to achieve ExpandableTextView is down below:
ExpandTextView.kt

class ExpandTextView @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0) : AppCompatTextView(context, attrs, defStyleAttr) {

    /**
     * true:展开,false:收起
     */
    private var mExpanded = false

    private var mCallback: ExpandTextViewCallback? = null

    /**
     * 源文字内容
     */
    private var mText = ""
    private var maxLineCount = 3
    private var ellipsizeText = "..."

    fun setMaxLineCount(maxLineCount: Int) {
        this.maxLineCount = maxLineCount
    }

    fun setEllipsizeText(ellipsizeText: String) {
        this.ellipsizeText = ellipsizeText
    }

    override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec)

        val staticLayout = if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
            StaticLayout(mText, paint, measuredWidth - paddingLeft - paddingRight, Layout.Alignment.ALIGN_CENTER, 1f, 0f, true)
        } else {
            val builder = StaticLayout.Builder.obtain(mText, 0, text.length, paint, measuredWidth - paddingLeft - paddingRight)
                .setAlignment(Layout.Alignment.ALIGN_CENTER)
                .setLineSpacing(0f, 1f)
                .setIncludePad(true)

            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
                builder.setUseLineSpacingFromFallbacks(true)
            }

            builder.build()
        }

        /**
         * 总计行数
         */
        var lineCount = staticLayout.lineCount
        if (lineCount > maxLineCount) {
            when (mExpanded) {
                true -> {
                    mCallback?.onExpand()
                    text = mText
                }
                false -> {
                    mCallback?.onCollapse()

                    lineCount = maxLineCount

                    val dotWidth = paint.measureText(ellipsizeText)

                    val start = staticLayout.getLineStart(lineCount - 1)
                    val end = staticLayout.getLineEnd(lineCount - 1)
                    val lineText = mText.substring(start, end)

                    var endIndex = 0
                    for (i in lineText.length - 1 downTo 0) {
                        val str = lineText.substring(i, lineText.length)
                        if (paint.measureText(str) >= dotWidth) {
                            endIndex = i
                            break
                        }
                    }

                    val newEndLineText = lineText.substring(0, endIndex) + ellipsizeText
                    text = mText.substring(0, start) + newEndLineText
                }
            }
        } else {
            //mCallback?.onLoss()
            text = mText
        }

        var lineHeight = 0
        for (i in 0 until lineCount) {
            val lienBound = Rect()
            staticLayout.getLineBounds(i, lienBound)
            lineHeight += lienBound.height()
        }

        lineHeight += paddingTop + paddingBottom
        setMeasuredDimension(measuredWidth, lineHeight)
    }

    fun toggleExpand() {
        mExpanded = !mExpanded
        requestLayout()
    }

    fun setText(text: String, callback: ExpandTextViewCallback) {
        mText = text
        mCallback = callback

        setText(text)
    }
}

ExpandLayout.kt

class ExpandLayout @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0) : LinearLayout(context, attrs, defStyleAttr) {

    private var binding: LayoutExpandViewBinding

    private var maxCollapsedLines = 3
    private var contentTextSize = 18f
    private var contentTextColor = 0
    private var expandText = ""
    private var collapseText = ""
    private var expandCollapseTextSize = 18f
    private var expandCollapseTextColor = 0
    private var expandCollapseTextGravity = 0
    private var ellipsizeText = "..."
    private var middlePadding = 0f

    init {
        initAttrs(context, attrs, defStyleAttr)
        orientation = VERTICAL
        binding = LayoutExpandViewBinding.inflate(LayoutInflater.from(context), this, true)
        binding.etvContent.setMaxLineCount(maxCollapsedLines)
        binding.etvContent.textSize = DensityUtil.px2sp(context, contentTextSize)
        binding.etvContent.setTextColor(contentTextColor)
        binding.etvContent.setEllipsizeText(ellipsizeText)

        val layoutParams = binding.tvTip.layoutParams as LayoutParams
        layoutParams.topMargin = middlePadding.toInt()
        binding.tvTip.layoutParams = layoutParams

        binding.tvTip.textSize = DensityUtil.px2sp(context, expandCollapseTextSize)
        binding.tvTip.setTextColor(expandCollapseTextColor)
        binding.tvTip.gravity = when (expandCollapseTextGravity) {
            0 -> Gravity.LEFT
            1 -> Gravity.CENTER
            2 -> Gravity.RIGHT
            else -> Gravity.LEFT
        }

        binding.etvContent.requestLayout()
        binding.tvTip.requestLayout()
    }

    private fun initAttrs(context: Context, attrs: AttributeSet?, defStyleAttr: Int) {
        val typedArray = context.obtainStyledAttributes(attrs, R.styleable.ExpandLayout)
        maxCollapsedLines = typedArray.getInt(R.styleable.ExpandLayout_maxCollapsedLines, 3)
        contentTextSize = typedArray.getDimension(R.styleable.ExpandLayout_contentTextSize, DensityUtil.sp2px(context, 18f).toFloat())
        contentTextColor = typedArray.getColor(R.styleable.ExpandLayout_contentTextColor, resources.getColor(R.color.text_black))
        expandText = if (typedArray.getString(R.styleable.ExpandLayout_expandText).isNullOrEmpty()) "全文" else typedArray.getString(R.styleable.ExpandLayout_expandText).toString()
        collapseText = if (typedArray.getString(R.styleable.ExpandLayout_collapseText).isNullOrEmpty()) "收起" else typedArray.getString(R.styleable.ExpandLayout_collapseText).toString()
        expandCollapseTextSize = typedArray.getDimension(R.styleable.ExpandLayout_expandCollapseTextSize, DensityUtil.sp2px(context, 18f).toFloat())
        expandCollapseTextColor = typedArray.getColor(R.styleable.ExpandLayout_expandCollapseTextColor, resources.getColor(R.color.text_blue))
        expandCollapseTextGravity = typedArray.getColor(R.styleable.ExpandLayout_expandCollapseTextGravity, 0)
        ellipsizeText = if (typedArray.getString(R.styleable.ExpandLayout_ellipsizeText).isNullOrEmpty()) "..." else typedArray.getString(R.styleable.ExpandLayout_ellipsizeText).toString()
        middlePadding = typedArray.getDimension(R.styleable.ExpandLayout_middlePadding, 0f)
        typedArray.recycle()
    }

    fun setContent(text: String) {
        binding.tvTip.setOnClickListener {
            binding.etvContent.toggleExpand()
        }
        binding.etvContent.setText(text, object : ExpandTextViewCallback {
            override fun onExpand() {
                binding.tvTip.visibility = View.VISIBLE
                binding.tvTip.text = collapseText
            }

            override fun onCollapse() {
                binding.tvTip.visibility = View.VISIBLE
                binding.tvTip.text = expandText
            }

            override fun onLoss() {
                binding.tvTip.visibility = View.GONE
            }
        })
    }
}

The ExpandLayout layout xml code:
layout_expand_view.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/ll_expand_view"
    android:orientation="vertical"
    android:layout_width="match_parent"
    android:layout_height="wrap_content">

    <com.zld.expandlayout.ExpandTextView
        android:id="@+id/etv_content"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content" />

    <TextView
        android:id="@+id/tv_tip"
        android:gravity="left"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="查看全文"
        android:textColor="#39a4d2" />

</LinearLayout>

RecyclerView Item Xml:

<androidx.cardview.widget.CardView xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:layout_marginBottom="14dp"
    app:cardBackgroundColor="@color/white"
    app:cardCornerRadius="8dp"
    app:cardElevation="0dp">

    <LinearLayout
        android:orientation="vertical"
        android:layout_width="match_parent"
        android:layout_height="wrap_content">

        <!-- Many other views here -->

        <com.zld.expandlayout.ExpandLayout
            android:id="@+id/message_content"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginTop="12dp"
            android:layout_marginLeft="12dp"
            android:layout_marginRight="12dp"
            app:contentTextColor="#313131"
            app:contentTextSize="12sp"
            app:expandText="@string/forum_text_expand"
            app:collapseText="@string/forum_text_collapse"
            app:maxCollapsedLines="5"
            app:expandCollapseTextColor="@color/theme_color"
            app:expandCollapseTextGravity="left"
            app:expandCollapseTextSize="12sp" />

    </LinearLayout>

</androidx.cardview.widget.CardView>

As you can here, when I click the expand or collapse button, the content did expand, but the LinearLayout cut the content, while the expand or collapse button (the other textview) height becomes 0dp.

enter image description here enter image description here enter image description here

Hhry
  • 823
  • 1
  • 8
  • 19
  • Changing from LinearLayout to RelativeLayout solve this issue, but I don't know why – Hhry Dec 24 '22 at 11:07

0 Answers0