一.背景介绍
(Android10 11目前有这个问题 Android15似乎有新的属性 但是没有可用的环境 没有验证)
简介
android:maxLines="1"
android:textAlignment="viewStart"
android:letterSpacing="0.04"
多个属性同时作用情况下 在系统为阿拉伯语情况下,显示英文文本字符串出现截断问题
UX需求:
1.有两个控件 address displayName
address自适应宽度 displayName在address之后占满剩余空间
2.系统为阿拉伯语情况下控件位置需要调换(父控件 android:layoutDirection="locale" 实现)
3.系统为英语情况下 如果控件有多余的宽度,文本要在Textview的内部靠左显示。
系统为阿拉伯语情况下 如果控件有多余的宽度,文本要在Textview的内部靠右显示。(android:textAlignment="viewStart"实现)
4.控件都只显示一行文本,当文本过长时,尾部显示...(android:maxLines="1" android:ellipsize="end"实现)
5.UX提供了字体文件 但是字体本身字符间距太小 需要通过代码设置letterSpacing(android:letterSpacing="0.04"实现)
二.xml实现
XML
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="528dp"
android:layout_height="140dp"
android:background="#55DDDDDD"
android:layoutDirection="locale"
tools:context=".MainActivity">
<TextView
android:id="@+id/address"
android:layout_width="50dp"
android:layout_height="wrap_content"
android:text="adress"
android:textColor="#F00"
app:layout_constraintEnd_toStartOf="@+id/displayName"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/displayName"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:ellipsize="end"
android:letterSpacing="0.04"
android:maxLines="1"
android:text="This is long text This is long text This is long text This is long text This is long text This is long textThis is long text"
android:textAlignment="viewStart"
android:textDirection="locale"
android:textSize="36sp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@+id/address"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
三. 现象分析
在系统语言切换到阿拉伯的情况下 英文出现截断现象
当我们修改如下属性时 截断问题可以被解决
例如删除 android:maxLines="1"
例如 把android:textAlignment="viewStart" 改成textStart
例如删除android:letterSpacing="0.04"
但是为了满足UX的需求 这些属性我们都不能修改
另外 如果把displayName的文本改成阿拉伯语 也不会出现问题
还有一个现象 displayName的文本越长 被截断的文本就越多
还有我们将letterSpacing改的越小 被截断的现象越不明显
从以上的各种现象 可以推测,是Android系统在系统语言是阿拉伯语情况下 对于英文文本的letterSpacing计算有问题 导致其判断显示省略号的位置出错
后面调查发现 Android系统在系统语言是英文情况下 对于阿拉伯语文本的letterSpacing计算也有问题
四.解决思路
如果条件变化 我们可以改动如下情况任意一个
* 1.view textAlignment is TEXT_ALIGNMENT_VIEW_START
* 2.text is too long to display in textview
* 3.letterSpacing is not 0
* 4.ellipsize="end"
截断问题都不会发生
但如果无法修改上面的这几个状态 那么我们可以在这种情况下重新绘制text
或者动态修改TEXT_ALIGNMENT的属性值
下面介绍重新绘制text的方案
五.代码实现
Kotlin
import android.content.Context
import android.graphics.Canvas
import android.text.Layout
import android.text.StaticLayout
import android.text.TextUtils
import android.util.AttributeSet
import android.view.View
import androidx.appcompat.widget.AppCompatTextView
import com.telenav.transformerhmi.uiframework.R
/**
*
* it will re draw text
* when all of following conditions happen together
* 1.view textAlignment is TEXT_ALIGNMENT_VIEW_START
* 2.text is too long to display in textview
* 3.letterSpacing is not 0
* 4.ellipsize="end"
* otherwise this is just a normal textview
*/
class AutoAlignmentViewStartTextView @JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null,
defStyleAttr: Int = R.style.TextAppearance
) : AppCompatTextView(context, attrs, defStyleAttr) {
override fun onDraw(canvas: Canvas) {
if (textAlignment == View.TEXT_ALIGNMENT_VIEW_START && needEllipsis() && letterSpacing != 0f && ellipsize == TextUtils.TruncateAt.END) {
// get Paint
val paint = paint.apply {
color = currentTextColor
textSize = textSize
}
val text = text.toString()
// get text content width
val width = width - paddingLeft - paddingRight
// create StaticLayout to deal with text which has ellipsis
val layout = StaticLayout.Builder.obtain(text, 0, text.length, paint, width)
.setMaxLines(maxLines)
.setEllipsize(TextUtils.TruncateAt.END) // Display ellipsis when the view size is exceeded
.setAlignment(Layout.Alignment.ALIGN_NORMAL) // Left-align text, but right-align by canvas movement
.build()
// text center vertical
val y = (height - layout.height) / 2
canvas.save()
if (layoutDirection == View.LAYOUT_DIRECTION_RTL) {
// Move the canvas so the text starts drawing from the right
canvas.translate(paddingLeft.toFloat(), y.toFloat())
}
layout.draw(canvas)
canvas.restore()
} else {
super.onDraw(canvas)
}
}
private fun needEllipsis(): Boolean {
val availableWidth = width - paddingStart - paddingEnd
var currentWidth = 0f
var lineCount = 0
text.forEach { char ->
currentWidth += paint.measureText(char.toString())
if (currentWidth > availableWidth) {// Text occupies the entire line.
lineCount++
if (lineCount > maxLines) {
return true
}
currentWidth = paint.measureText(char.toString())
}
}
if (currentWidth > 0) {
lineCount++
}
// measured line count > maxLines
return lineCount > maxLines
}
}