How to get line count of textview before rendering?

后端 未结 6 613
南笙
南笙 2020-12-02 20:29

How can I get the number of lines a string will take up in a TextView before it is rendered.

A ViewTreeObserver will not work because thos

6条回答
  •  囚心锁ツ
    2020-12-02 20:44

    Thanks to Eugene Popovich I got:

    import android.os.Build
    import android.text.Layout
    import android.text.StaticLayout
    import android.text.TextDirectionHeuristic
    import android.text.TextPaint
    import android.widget.TextView
    
    
    object TextMeasurementUtil {
        /**
         * Split text into lines using specified parameters and the same algorithm
         * as used by the [TextView] component
         *
         * @param text   the text to split
         * @param params the measurement parameters
         * @return
         */
        fun getTextLines(text: CharSequence, params: TextViewParams): List {
            val layout = getStaticLayout(text, params)
            return (0 until layout.lineCount).map {
                layout.text.subSequence(layout.getLineStart(it), layout.getLineEnd(it))
            }
        }
    
        fun getTextLineCount(text: CharSequence, params: TextViewParams): Int {
            val layout = getStaticLayout(text, params)
            return layout.lineCount
        }
    
        fun getTextLines(textView: TextView): List {
            val layout = getStaticLayout(textView)
            return (0 until layout.lineCount).map {
                layout.text.subSequence(layout.getLineStart(it), layout.getLineEnd(it))
            }
        }
    
        fun getTextLineCount(textView: TextView): Int {
            val layout = getStaticLayout(textView)
            return layout.lineCount
        }
    
        /**
         * The text measurement parameters
         */
        fun getTextViewParams(textView: TextView): TextViewParams {
            val layout = textView.layout
            val width = textView.width - textView.compoundPaddingLeft - textView.compoundPaddingRight
            var lineSpacingExtra = 0f
            var lineSpacingMultiplier = 1.0f
            var includeFontPadding = true
            var breakStrategy = 0
            var hyphenationFrequency = 0
            var justificationMode = 0
            var useFallbackLineSpacing = false
            var textDirectionHeuristic: TextDirectionHeuristic? = null
    
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
                lineSpacingExtra = textView.lineSpacingExtra
                lineSpacingMultiplier = textView.lineSpacingMultiplier
                includeFontPadding = textView.includeFontPadding
                if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
                    breakStrategy = textView.breakStrategy
                    hyphenationFrequency = textView.hyphenationFrequency
                }
                if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
                    justificationMode = textView.justificationMode
                }
                if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
                    useFallbackLineSpacing = textView.isFallbackLineSpacing
                }
                if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
                    textDirectionHeuristic = textView.textDirectionHeuristic
                }
            }
    
            return TextViewParams(
                textPaint = layout.paint,
                alignment = layout.alignment,
                lineSpacingExtra = lineSpacingExtra,
                lineSpacingMultiplier = lineSpacingMultiplier,
                includeFontPadding = includeFontPadding,
                breakStrategy = breakStrategy,
                hyphenationFrequency = hyphenationFrequency,
                justificationMode = justificationMode,
                useFallbackLineSpacing = useFallbackLineSpacing,
                textDirectionHeuristic = textDirectionHeuristic,
                width = width
            )
        }
    
        private fun getStaticLayout(text: CharSequence,
                                    params: TextViewParams): StaticLayout =
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
                val builder = StaticLayout.Builder
                    .obtain(text, 0, text.length, params.textPaint, params.width)
                    .setAlignment(params.alignment)
                    .setLineSpacing(params.lineSpacingExtra, params.lineSpacingMultiplier)
                    .setIncludePad(params.includeFontPadding)
                    .setBreakStrategy(params.breakStrategy)
                    .setHyphenationFrequency(params.hyphenationFrequency)
                if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
                    builder.setJustificationMode(params.justificationMode)
                }
                if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
                    builder.setUseLineSpacingFromFallbacks(params.useFallbackLineSpacing)
                }
                if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
                    builder.setTextDirection(params.textDirectionHeuristic!!)
                }
                builder.build()
            } else {
                @Suppress("DEPRECATION")
                StaticLayout(
                    text,
                    params.textPaint,
                    params.width,
                    params.alignment,
                    params.lineSpacingMultiplier,
                    params.lineSpacingExtra,
                    params.includeFontPadding)
            }
    
        private fun getStaticLayout(textView: TextView): StaticLayout =
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
                val builder = StaticLayout.Builder
                    .obtain(textView.text, 0, textView.text.length, textView.layout.paint,
                        textView.width)
                    .setAlignment(textView.layout.alignment)
                    .setLineSpacing(textView.lineSpacingExtra, textView.lineSpacingMultiplier)
                    .setIncludePad(textView.includeFontPadding)
                    .setBreakStrategy(textView.breakStrategy)
                    .setHyphenationFrequency(textView.hyphenationFrequency)
                if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
                    builder.setJustificationMode(textView.justificationMode)
                }
                if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
                    builder.setUseLineSpacingFromFallbacks(textView.isFallbackLineSpacing)
                }
                if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
                    builder.setTextDirection(textView.textDirectionHeuristic)
                }
                builder.build()
            } else {
                @Suppress("DEPRECATION")
                StaticLayout(
                    textView.text,
                    textView.layout.paint,
                    textView.width,
                    textView.layout.alignment,
                    textView.lineSpacingMultiplier,
                    textView.lineSpacingExtra,
                    textView.includeFontPadding)
            }
    
        data class TextViewParams(
            val textPaint: TextPaint,
            val alignment: Layout.Alignment,
            val lineSpacingExtra: Float,
            val lineSpacingMultiplier: Float,
            val includeFontPadding: Boolean,
            val breakStrategy: Int,
            val hyphenationFrequency: Int,
            val justificationMode: Int,
            val useFallbackLineSpacing: Boolean,
            val textDirectionHeuristic: TextDirectionHeuristic?,
            val width: Int
        )
    }
    

    Usage:

    1. If you want to print different texts in equal TextViews (for instance, in RecyclerView with one or similar ViewHolders):

       val params = TextMeasurementUtil.getTextViewParams(textView)
      
       val lines = TextMeasurementUtil.getTextLines(textView.text, params)
       val count = TextMeasurementUtil.getTextLineCount(textView.text, params)
      
    2. In any other case:

       val lines = TextMeasurementUtil.getTextLines(textView)
       val count = TextMeasurementUtil.getTextLineCount(textView)
      

    In RecyclerView you won't know parameters of TextView until you call post or doOnPreDraw method, so use:

    textView.doOnPreDraw {
        val lines = TextMeasurementUtil.getTextLines(textView)
        val count = TextMeasurementUtil.getTextLineCount(textView)
    }
    

提交回复
热议问题