Как добиться эффекта затухания на последней строке TextView, как в разделе «ЧТО НОВОГО» в приложении Play Store?

Этот эффект затухания может быть достигнут путем создания подкласса класса TextView для перехвата его рисования и выполнения чего-то вроде того, что класс View делает для размытия краев, но только на последнем отрезке последней текстовой строки.
В этом примере мы создаем единичный горизонтальный линейный градиент, который изменяется от прозрачного до сплошного черного. Когда мы готовимся к рисованию, этот единичный градиент масштабируется до длины, рассчитанной как простая часть конечной длины линии TextView, а затем позиционируется соответствующим образом.
Создается внеэкранный буфер, и мы позволяем TextView рисовать в нем свое содержимое. Затем мы рисуем градиент затухания поверх него с помощью режима передачи PorterDuff.Mode.DST_OUT, который по существу очищает нижележащий контент до степени относительно непрозрачности градиента в данной точке. Отрисовка этого буфера обратно на экран приводит к желаемому затуханию независимо от того, что находится в фоновом режиме.
public class FadingTextView extends AppCompatTextView {
private static final float FADE_LENGTH_FACTOR = .4f;
private final RectF drawRect = new RectF();
private final Rect realRect = new Rect();
private final Path selection = new Path();
private final Matrix matrix = new Matrix();
private final Paint paint = new Paint();
private final Shader shader =
new LinearGradient(0f, 0f, 1f, 0f, 0x00000000, 0xFF000000, Shader.TileMode.CLAMP);
public FadingTextView(Context context) {
this(context, null);
}
public FadingTextView(Context context, AttributeSet attrs) {
this(context, attrs, android.R.attr.textViewStyle);
}
public FadingTextView(Context context, AttributeSet attrs, int defStyleAttribute) {
super(context, attrs, defStyleAttribute);
paint.setShader(shader);
paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_OUT));
}
@Override
protected void onDraw(Canvas canvas) {
// Locals
final RectF drawBounds = drawRect;
final Rect realBounds = realRect;
final Path selectionPath = selection;
final Layout layout = getLayout();
// Figure last line index, and text offsets there
final int lastLineIndex = getLineCount() - 1;
final int lastLineStart = layout.getLineStart(lastLineIndex);
final int lastLineEnd = layout.getLineEnd(lastLineIndex);
// Let the Layout figure a Path that'd cover the last line text
layout.getSelectionPath(lastLineStart, lastLineEnd, selectionPath);
// Convert that Path to a RectF, which we can more easily modify
selectionPath.computeBounds(drawBounds, false);
// Naive text direction determination; may need refinement
boolean isRtl =
layout.getParagraphDirection(lastLineIndex) == Layout.DIR_RIGHT_TO_LEFT;
// Narrow the bounds to just the fade length
if (isRtl) {
drawBounds.right = drawBounds.left + drawBounds.width() * FADE_LENGTH_FACTOR;
} else {
drawBounds.left = drawBounds.right - drawBounds.width() * FADE_LENGTH_FACTOR;
}
// Adjust for drawables and paddings
drawBounds.offset(getTotalPaddingLeft(), getTotalPaddingTop());
// Convert drawing bounds to real bounds to determine
// if we need to do the fade, or a regular draw
drawBounds.round(realBounds);
realBounds.offset(-getScrollX(), -getScrollY());
boolean needToFade = realBounds.intersects(getTotalPaddingLeft(), getTotalPaddingTop(),
getWidth() - getTotalPaddingRight(), getHeight() - getTotalPaddingBottom());
if (needToFade) {
// Adjust and set the Shader Matrix
final Matrix shaderMatrix = matrix;
shaderMatrix.reset();
shaderMatrix.setScale(drawBounds.width(), 1f);
if (isRtl) {
shaderMatrix.postRotate(180f, drawBounds.width() / 2f, 0f);
}
shaderMatrix.postTranslate(drawBounds.left, drawBounds.top);
shader.setLocalMatrix(shaderMatrix);
// Save, and start drawing to an off-screen buffer
final int saveCount;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
saveCount = canvas.saveLayer(null, null);
} else {
saveCount = canvas.saveLayer(null, null, Canvas.ALL_SAVE_FLAG);
}
// Let TextView draw itself to the buffer
super.onDraw(canvas);
// Draw the fade to the buffer, over the TextView content
canvas.drawRect(drawBounds, paint);
// Restore, and draw the buffer back to the Canvas
canvas.restoreToCount(saveCount);
} else {
// Regular draw
super.onDraw(canvas);
}
}
}
Это прямая замена TextView, и вы можете использовать ее в своем макете аналогичным образом.
<com.example.app.FadingTextView
android:layout_width = "match_parent"
android:layout_height = "wrap_content"
android:background = "#e2f3eb"
android:textColor = "#0b8043"
android:lineSpacingMultiplier = "1.2"
android:text = "@string/umang" />

Заметки:
Вычисление длины затухания основано на постоянной части длины текста последней строки, определяемой здесь FADE_LENGTH_FACTOR. Это похоже на ту же базовую методологию компонента Play Store, поскольку абсолютная длина затухания зависит от длины строки. Значение FADE_LENGTH_FACTOR можно изменить по желанию.
FadingTextView в настоящее время является расширением AppCompatTextView, но он отлично работает как обычный TextView, если он вам нужен. Я думаю, что он будет работать и как MaterialTextView, хотя я еще не тестировал это полностью.
Этот пример ориентирован в основном на относительно простое использование; то есть как простая статическая метка в оболочке. Хотя я попытался учесть и протестировать каждую настройку TextView, о которой я мог подумать, которая может повлиять на это - например, составные чертежи, отступы, выбираемый текст, прокрутка, направление и выравнивание текста и т. д. - я не могу гарантировать, что я ' я думал обо всем.