@@ -5,6 +5,7 @@ import android.os.Build
5
5
import android.text.Layout
6
6
import android.text.StaticLayout
7
7
import android.text.TextDirectionHeuristics
8
+ import android.text.TextUtils
8
9
import android.util.TypedValue
9
10
import android.view.View
10
11
import android.view.View.TEXT_ALIGNMENT_CENTER
@@ -14,8 +15,6 @@ import android.view.ViewGroup
14
15
import android.widget.TextView
15
16
import androidx.annotation.AttrRes
16
17
import androidx.annotation.ColorInt
17
- import androidx.core.text.TextDirectionHeuristicsCompat
18
- import androidx.core.widget.TextViewCompat
19
18
20
19
@ColorInt
21
20
fun Context.getColorFromAttr (
@@ -27,51 +26,59 @@ fun Context.getColorFromAttr(
27
26
return typedValue.data
28
27
}
29
28
30
- inline fun <reified LP : ViewGroup.LayoutParams > View.modifyLayoutParams (function : LP .() -> Unit ) {
29
+ inline fun <reified LP : ViewGroup.LayoutParams > View.modifyLayoutParams (function : LP .() -> Unit ) {
31
30
layoutParams = (layoutParams as LP ).apply { function() }
32
31
}
33
32
34
33
fun TextView.needsCollapsing (
35
34
availableWidthPx : Int ,
36
35
maxLines : Int
37
36
): Boolean {
38
- if (availableWidthPx <= 0 || text.isNullOrEmpty()) return false
37
+ // Pick the width the TextView will actually respect before draw
38
+ val rawWidth = when {
39
+ measuredWidth > 0 -> measuredWidth
40
+ // if maxWidth is set, we check it
41
+ maxWidth in 1 until Int .MAX_VALUE -> minOf(availableWidthPx, maxWidth)
42
+ else -> availableWidthPx
43
+ }
44
+ val contentWidth = (rawWidth - paddingLeft - paddingRight).coerceAtLeast(0 )
45
+ if (contentWidth <= 0 || text.isNullOrEmpty()) return false
39
46
40
- // The exact text that will be drawn (all-caps, password dots …)
41
47
val textForLayout = transformationMethod?.getTransformation(text, this ) ? : text
42
48
43
- // Build a StaticLayout that mirrors this TextView’s wrap rules
49
+ val alignment = when (textAlignment) {
50
+ TEXT_ALIGNMENT_CENTER -> Layout .Alignment .ALIGN_CENTER
51
+ TEXT_ALIGNMENT_VIEW_END , TEXT_ALIGNMENT_TEXT_END -> Layout .Alignment .ALIGN_OPPOSITE
52
+ else -> Layout .Alignment .ALIGN_NORMAL
53
+ }
54
+ val direction = when (textDirection) {
55
+ View .TEXT_DIRECTION_FIRST_STRONG_RTL -> TextDirectionHeuristics .FIRSTSTRONG_RTL
56
+ View .TEXT_DIRECTION_RTL -> TextDirectionHeuristics .RTL
57
+ View .TEXT_DIRECTION_LTR -> TextDirectionHeuristics .LTR
58
+ else -> TextDirectionHeuristics .FIRSTSTRONG_LTR
59
+ }
60
+
44
61
val builder = StaticLayout .Builder
45
- .obtain(textForLayout, 0 , textForLayout.length, paint, availableWidthPx )
62
+ .obtain(textForLayout, 0 , textForLayout.length, paint, contentWidth )
46
63
.setIncludePad(includeFontPadding)
47
64
.setLineSpacing(lineSpacingExtra, lineSpacingMultiplier)
48
- .setBreakStrategy(breakStrategy) // API 23+
65
+ .setBreakStrategy(breakStrategy)
49
66
.setHyphenationFrequency(hyphenationFrequency)
50
- .setMaxLines(Int .MAX_VALUE )
51
-
52
- // Alignment (honours RTL if textAlignment is END/VIEW_END)
53
- builder.setAlignment(
54
- when (textAlignment) {
55
- TEXT_ALIGNMENT_CENTER -> Layout .Alignment .ALIGN_CENTER
56
- TEXT_ALIGNMENT_VIEW_END ,
57
- TEXT_ALIGNMENT_TEXT_END -> Layout .Alignment .ALIGN_OPPOSITE
58
- else -> Layout .Alignment .ALIGN_NORMAL
59
- }
60
- )
61
-
62
- // Direction heuristic
63
- val dir = when (textDirection) {
64
- View .TEXT_DIRECTION_FIRST_STRONG_RTL -> TextDirectionHeuristics .FIRSTSTRONG_RTL
65
- View .TEXT_DIRECTION_RTL -> TextDirectionHeuristics .RTL
66
- View .TEXT_DIRECTION_LTR -> TextDirectionHeuristics .LTR
67
- else -> TextDirectionHeuristics .FIRSTSTRONG_LTR
68
- }
69
- builder.setTextDirection(dir)
70
-
71
- builder.setEllipsize(ellipsize)
67
+ .setAlignment(alignment)
68
+ .setTextDirection(direction)
69
+ .setMaxLines(maxLines) // cap at maxLines
70
+ .setEllipsize(ellipsize ? : TextUtils .TruncateAt .END ) // compute ellipsis
72
71
73
72
builder.setJustificationMode(justificationMode)
74
73
75
74
val layout = builder.build()
76
- return layout.lineCount > maxLines
75
+
76
+ // Fewer than maxLines: definitely no truncation
77
+ if (layout.lineCount < maxLines) return false
78
+ // (Defensive) more than maxLines: truncated
79
+ if (layout.lineCount > maxLines) return true
80
+
81
+ // Exactly maxLines: truncated if last line is ellipsized or characters were cut
82
+ val last = (maxLines - 1 ).coerceAtMost(layout.lineCount - 1 )
83
+ return layout.getEllipsisCount(last) > 0 || layout.getLineEnd(last) < textForLayout.length
77
84
}
0 commit comments