先看GIF,三个问题:
- 停顿位置不对,应该在“这”停顿,实际在“是”停顿
- 渐变效果出现的位置不对,应该在“这”显示,实际在“是”显示
- "幽灵文本"显示时机错误,出现得时候,已经走到“这是”两个字了,应该是从“这”慢慢出来
看代码
<com.test.textmarqueequestion.MarqueeTextview
android:layout_width="200dp"
android:layout_height="wrap_content"
android:text="这是一段超长文本"
android:singleLine="true"
android:textSize="30sp"
android:textStyle="bold"
android:ellipsize="marquee"
android:gravity="right"
android:marqueeRepeatLimit="marquee_forever"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
问题原因:
Marquee 效果的计算逻辑与文本对齐方式的冲突, Marquee 效果的核心计算是基于以下假设:
-
文本默认是左对齐的
-
滚动从文本最左端开始
-
所有位置计算都基于左对齐坐标系
Textview中初始化Marquee基本参数的代码:
final int textWidth = textView.getWidth() - textView.getCompoundPaddingLeft()
- textView.getCompoundPaddingRight();//TextView 的实际可用宽度(去除左右 padding)
final float lineWidth = textView.mLayout.getLineWidth(0);//文本第一行的实际宽度(可能超出 textWidth)
final float gap = textWidth / 3.0f; //文本滚动时的间隔区域(设为可用宽度的 1/3)
mGhostStart = lineWidth - textWidth + gap; // 幽灵文本(重复文本)开始出现的位置 = 文本超长部分 + 间隔
mMaxScroll = mGhostStart + textWidth; //最大滚动距离 = 幽灵起点 + 可用宽度
mGhostOffset = lineWidth + gap; // 幽灵文本的绘制偏移量 = 文本总宽度 + 间隔
mFadeStop = lineWidth + textWidth / 6.0f; // 渐变效果结束位置 = 文本总宽度 + 可用宽度的 1/6
mMaxFadeScroll = mGhostStart + lineWidth + lineWidth; //最大渐变滚动距离 = 幽灵起点 + 2倍文本宽度(用于控制淡出范围)
当 gravity = left/start时: 文本起点 = 0, 滚动从0开始;
当 gravity = right/end时: 文本起点 = textWidth - lineWidth; 滚动从textWidth - lineWidth开始
数学关系对比如下 假设: 视图宽度 textWidth = 200px 文本宽度 lineWidth = 300px 间隔 gap = 200/3 ≈ 66.67px
参数 | 左对齐计算结果 | 右对齐实际坐标 | 偏差描述 |
---|---|---|---|
文本起点 | 0 | 200 - 300 = -100 | 基准点偏移 100px |
mGhostStart | 300-200+66.67=166.67 | 166.67 - (-100) = 266.67 | 比预期晚了 100px |
mMaxScroll | 166.67+200=366.67 | 366.67 - (-100) = 466.67 | 滚动距离多算 100px |
解决方案
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="200dp"
android:layout_height="wrap_content"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
>
<com.test.textmarqueequestion.MarqueeTextview
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:maxWidth="200dp"
android:text="这是一段超长文本"
android:singleLine="true"
android:textSize="30sp"
android:textStyle="bold"
android:ellipsize="marquee"
android:gravity="start"
android:marqueeRepeatLimit="marquee_forever"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintEnd_toEndOf="parent"
/>
</androidx.constraintlayout.widget.ConstraintLayout>