@matiks/rn-stroke-text 0.1.3 → 0.1.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
|
@@ -14,9 +14,7 @@ import android.view.View
|
|
|
14
14
|
import kotlin.math.ceil
|
|
15
15
|
import kotlin.math.max
|
|
16
16
|
|
|
17
|
-
class StrokeTextView(
|
|
18
|
-
context: Context
|
|
19
|
-
) : View(context) {
|
|
17
|
+
class StrokeTextView(context: Context) : View(context) {
|
|
20
18
|
|
|
21
19
|
// ─────────────────────────────────────────────
|
|
22
20
|
// Props
|
|
@@ -37,15 +35,14 @@ class StrokeTextView(
|
|
|
37
35
|
// Paints
|
|
38
36
|
// ─────────────────────────────────────────────
|
|
39
37
|
|
|
40
|
-
private val textPaint = TextPaint(Paint.ANTI_ALIAS_FLAG).apply {
|
|
41
|
-
style = Paint.Style.FILL
|
|
42
|
-
}
|
|
38
|
+
private val textPaint = TextPaint(Paint.ANTI_ALIAS_FLAG).apply { style = Paint.Style.FILL }
|
|
43
39
|
|
|
44
|
-
private val strokePaint =
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
40
|
+
private val strokePaint =
|
|
41
|
+
TextPaint(Paint.ANTI_ALIAS_FLAG).apply {
|
|
42
|
+
style = Paint.Style.STROKE
|
|
43
|
+
strokeJoin = Paint.Join.ROUND
|
|
44
|
+
strokeCap = Paint.Cap.ROUND
|
|
45
|
+
}
|
|
49
46
|
|
|
50
47
|
// ─────────────────────────────────────────────
|
|
51
48
|
// Layout
|
|
@@ -54,6 +51,7 @@ class StrokeTextView(
|
|
|
54
51
|
private var textLayout: StaticLayout? = null
|
|
55
52
|
private var strokeLayout: StaticLayout? = null
|
|
56
53
|
private var layoutDirty = true
|
|
54
|
+
private var lastLayoutWidth: Int = -1 // Track width used for layout
|
|
57
55
|
|
|
58
56
|
private val fontCache = HashMap<String, Typeface?>()
|
|
59
57
|
|
|
@@ -69,10 +67,7 @@ class StrokeTextView(
|
|
|
69
67
|
|
|
70
68
|
val desiredHeight = (textLayout?.height ?: 0).coerceAtLeast(1)
|
|
71
69
|
|
|
72
|
-
setMeasuredDimension(
|
|
73
|
-
measuredWidth,
|
|
74
|
-
resolveSize(desiredHeight, heightMeasureSpec)
|
|
75
|
-
)
|
|
70
|
+
setMeasuredDimension(measuredWidth, resolveSize(desiredHeight, heightMeasureSpec))
|
|
76
71
|
}
|
|
77
72
|
|
|
78
73
|
private fun resolveMeasuredWidth(spec: Int): Int {
|
|
@@ -120,76 +115,100 @@ class StrokeTextView(
|
|
|
120
115
|
}
|
|
121
116
|
|
|
122
117
|
private fun ensureLayout(width: Int) {
|
|
123
|
-
if (!layoutDirty && textLayout != null) return
|
|
124
|
-
|
|
125
118
|
val safeWidth = width.coerceAtLeast(1)
|
|
119
|
+
if (!layoutDirty && textLayout != null && lastLayoutWidth == safeWidth) return
|
|
126
120
|
updatePaints()
|
|
127
121
|
|
|
128
122
|
var displayText: CharSequence =
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
123
|
+
if (ellipsis) {
|
|
124
|
+
TextUtils.ellipsize(
|
|
125
|
+
text,
|
|
126
|
+
textPaint,
|
|
127
|
+
safeWidth.toFloat(),
|
|
128
|
+
TextUtils.TruncateAt.END
|
|
129
|
+
)
|
|
130
|
+
} else {
|
|
131
|
+
text
|
|
132
|
+
}
|
|
139
133
|
|
|
140
134
|
// Use StaticLayout.Builder for API 23+
|
|
141
|
-
val builder =
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
135
|
+
val builder =
|
|
136
|
+
StaticLayout.Builder.obtain(
|
|
137
|
+
displayText,
|
|
138
|
+
0,
|
|
139
|
+
displayText.length,
|
|
140
|
+
textPaint,
|
|
141
|
+
safeWidth
|
|
142
|
+
)
|
|
143
|
+
.setAlignment(alignment)
|
|
144
|
+
.setLineSpacing(0f, 1f)
|
|
145
|
+
.setIncludePad(false)
|
|
145
146
|
|
|
146
147
|
var layout = builder.build()
|
|
147
148
|
|
|
148
149
|
if (numberOfLines > 0 && layout.lineCount > numberOfLines) {
|
|
149
150
|
val end = layout.getLineEnd(numberOfLines - 1)
|
|
150
151
|
displayText = displayText.subSequence(0, end)
|
|
151
|
-
|
|
152
|
-
val truncatedBuilder =
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
152
|
+
|
|
153
|
+
val truncatedBuilder =
|
|
154
|
+
StaticLayout.Builder.obtain(
|
|
155
|
+
displayText,
|
|
156
|
+
0,
|
|
157
|
+
displayText.length,
|
|
158
|
+
textPaint,
|
|
159
|
+
safeWidth
|
|
160
|
+
)
|
|
161
|
+
.setAlignment(alignment)
|
|
162
|
+
.setLineSpacing(0f, 1f)
|
|
163
|
+
.setIncludePad(false)
|
|
164
|
+
|
|
157
165
|
layout = truncatedBuilder.build()
|
|
158
166
|
}
|
|
159
167
|
|
|
160
168
|
textLayout = layout
|
|
161
|
-
|
|
169
|
+
|
|
162
170
|
// For stroke layout, we use the same text
|
|
163
|
-
val strokeBuilder =
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
171
|
+
val strokeBuilder =
|
|
172
|
+
StaticLayout.Builder.obtain(
|
|
173
|
+
displayText,
|
|
174
|
+
0,
|
|
175
|
+
displayText.length,
|
|
176
|
+
strokePaint,
|
|
177
|
+
safeWidth
|
|
178
|
+
)
|
|
179
|
+
.setAlignment(alignment)
|
|
180
|
+
.setLineSpacing(0f, 1f)
|
|
181
|
+
.setIncludePad(false)
|
|
182
|
+
|
|
168
183
|
strokeLayout = strokeBuilder.build()
|
|
169
184
|
|
|
185
|
+
lastLayoutWidth = safeWidth // Remember the width used
|
|
170
186
|
layoutDirty = false
|
|
171
187
|
}
|
|
172
188
|
|
|
173
189
|
fun getTextDimensions(): Pair<Double, Double> {
|
|
174
190
|
updatePaints()
|
|
175
|
-
|
|
176
|
-
// Ensure we have a valid layout for measurement.
|
|
191
|
+
|
|
192
|
+
// Ensure we have a valid layout for measurement.
|
|
177
193
|
// If width is 0/unspecified, we measure intrinsic width.
|
|
178
|
-
val measureWidth =
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
194
|
+
val measureWidth =
|
|
195
|
+
if (customWidthPx > 0f) {
|
|
196
|
+
ceil(customWidthPx).toInt()
|
|
197
|
+
} else {
|
|
198
|
+
ceil(measureTextWidth()).toInt()
|
|
199
|
+
}
|
|
200
|
+
|
|
184
201
|
// We create a temporary layout if needed to get accurate height for the given width
|
|
185
|
-
// But since ensureLayout caches the layout based on last width, we might want to just
|
|
186
|
-
//
|
|
187
|
-
|
|
202
|
+
// But since ensureLayout caches the layout based on last width, we might want to just
|
|
203
|
+
// ensure layout exists.
|
|
204
|
+
// For accurate "intrinsic" measurement, we usually want the width to be the text width if
|
|
205
|
+
// not constrained.
|
|
206
|
+
|
|
188
207
|
ensureLayout(measureWidth)
|
|
189
208
|
|
|
190
209
|
val wPx = textLayout?.width?.toFloat() ?: 0f
|
|
191
210
|
val hPx = textLayout?.height?.toFloat() ?: 0f
|
|
192
|
-
|
|
211
|
+
|
|
193
212
|
return Pair(pxToDp(wPx).toDouble(), pxToDp(hPx).toDouble())
|
|
194
213
|
}
|
|
195
214
|
|
|
@@ -224,7 +243,7 @@ class StrokeTextView(
|
|
|
224
243
|
}
|
|
225
244
|
|
|
226
245
|
fun setFontSize(value: Float) {
|
|
227
|
-
val px =
|
|
246
|
+
val px = dp(value)
|
|
228
247
|
if (fontSizePx != px) {
|
|
229
248
|
fontSizePx = px
|
|
230
249
|
invalidateLayout()
|
|
@@ -263,12 +282,13 @@ class StrokeTextView(
|
|
|
263
282
|
}
|
|
264
283
|
|
|
265
284
|
fun setTextAlignment(value: String) {
|
|
266
|
-
val newAlignment =
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
285
|
+
val newAlignment =
|
|
286
|
+
when (value) {
|
|
287
|
+
"left" -> Layout.Alignment.ALIGN_NORMAL
|
|
288
|
+
"right" -> Layout.Alignment.ALIGN_OPPOSITE
|
|
289
|
+
"center" -> Layout.Alignment.ALIGN_CENTER
|
|
290
|
+
else -> alignment
|
|
291
|
+
}
|
|
272
292
|
if (alignment != newAlignment) {
|
|
273
293
|
alignment = newAlignment
|
|
274
294
|
invalidateLayout()
|
|
@@ -308,35 +328,27 @@ class StrokeTextView(
|
|
|
308
328
|
}
|
|
309
329
|
|
|
310
330
|
private fun sp(v: Float): Float =
|
|
311
|
-
|
|
312
|
-
TypedValue.COMPLEX_UNIT_SP,
|
|
313
|
-
v,
|
|
314
|
-
resources.displayMetrics
|
|
315
|
-
)
|
|
331
|
+
TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, v, resources.displayMetrics)
|
|
316
332
|
|
|
317
333
|
private fun dp(v: Float): Float =
|
|
318
|
-
|
|
319
|
-
TypedValue.COMPLEX_UNIT_DIP,
|
|
320
|
-
v,
|
|
321
|
-
resources.displayMetrics
|
|
322
|
-
)
|
|
334
|
+
TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, v, resources.displayMetrics)
|
|
323
335
|
|
|
324
|
-
private fun pxToDp(px: Float): Float =
|
|
325
|
-
px / resources.displayMetrics.density
|
|
336
|
+
private fun pxToDp(px: Float): Float = px / resources.displayMetrics.density
|
|
326
337
|
|
|
327
338
|
private fun parseColor(color: String): Int =
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
339
|
+
try {
|
|
340
|
+
Color.parseColor(color)
|
|
341
|
+
} catch (_: Exception) {
|
|
342
|
+
Color.BLACK
|
|
343
|
+
}
|
|
333
344
|
|
|
334
345
|
private fun getFont(fontFamily: String): Typeface? {
|
|
335
|
-
return fontCache[fontFamily]
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
346
|
+
return fontCache[fontFamily]
|
|
347
|
+
?: run {
|
|
348
|
+
val tf = FontUtil.getFont(context, fontFamily)
|
|
349
|
+
fontCache[fontFamily] = tf
|
|
350
|
+
tf
|
|
351
|
+
}
|
|
340
352
|
}
|
|
341
353
|
|
|
342
354
|
init {
|