一.背景介绍
(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 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"><TextViewandroid: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" /><TextViewandroid: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的方案
五.代码实现
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 Paintval paint = paint.apply {color = currentTextColortextSize = textSize}val text = text.toString()// get text content widthval width = width - paddingLeft - paddingRight// create StaticLayout to deal with text which has ellipsisval 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 verticalval y = (height - layout.height) / 2canvas.save()if (layoutDirection == View.LAYOUT_DIRECTION_RTL) {// Move the canvas so the text starts drawing from the rightcanvas.translate(paddingLeft.toFloat(), y.toFloat())}layout.draw(canvas)canvas.restore()} else {super.onDraw(canvas)}}private fun needEllipsis(): Boolean {val availableWidth = width - paddingStart - paddingEndvar currentWidth = 0fvar lineCount = 0text.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 > maxLinesreturn lineCount > maxLines}
}