1

I'd like to implement a fading edge behavior to TextView like the Google Play Movie does:

enter image description here

As you can see the last letters of the third line have a fading edge effect. Is there a way to achieve this for a specific line defined via android:maxLines? (For example android:maxLines="3")

I've tried the following but it works with the attribute android:singleLine only which is not my goal:

<TextView
    ...
    android:requiresFadingEdge="horizontal"
    android:fadingEdgeLength="30dp"
    android:ellipsize="none"
    android:singleLine="true" />

Setting android:maxLines here instead results in no fading at all.

Edit/Additional:

Previously I also tried a Shader with LinearGradient while extending TextView like here, but the described solution applies a background/foreground (and there were also some other issues with it ...).

I'd like to apply the Gradient to the last 3-4 characters of the maxLine line. Could this be possible?

Edit:

With Mike M.'s help (take a look into the comments) I could modify his answer to reach my wanted behavior. The final implementation with additions (or here as java file):

public class FadingTextView extends AppCompatTextView {

    // Length
    private static final float PERCENTAGE = .9f;
    private static final int CHARACTERS = 6;

    // Attribute for ObjectAnimator
    private static final String MAX_HEIGHT_ATTR = "maxHeight";

    private final Shader shader;
    private final Matrix matrix;
    private final Paint paint;
    private final Rect bounds;

    private int mMaxLines;
    private boolean mExpanded = false;

    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);

        matrix = new Matrix();
        paint = new Paint();
        bounds = new Rect();
        shader = new LinearGradient(0f, 0f, PERCENTAGE, 0f, Color.TRANSPARENT, Color.BLACK, Shader.TileMode.CLAMP);
        paint.setShader(shader);
        paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_OUT));

        mMaxLines = getMaxLines();
    }

    @Override
    protected void onDraw(Canvas canvas) {

        if (getLineCount() > getMaxLines() && !mExpanded
                && getRootView() != null && getText() != null
        ) {

            final Matrix m = matrix;
            final Rect b = bounds;
            final Layout l = getLayout();

            int fadeLength = (int) (getPaint().measureText(getText(), getText().length() - CHARACTERS, getText().length()));

            final int line = mMaxLines - 1;

            getLineBounds(line, b);

            final int lineStart = l.getLineStart(line);
            final int lineEnd = l.getLineEnd(line);
            final CharSequence text = getText().subSequence(lineStart, lineEnd);
            final int measure = (int) (getPaint().measureText(text, 0, text.length()));

            b.right = b.left + measure;

            b.left = b.right - fadeLength;
            final int saveCount = canvas.saveLayer(0, 0, getWidth(), getHeight(), null);

            super.onDraw(canvas);

            m.reset();
            m.setScale(fadeLength, 1f);
            m.postTranslate(b.left, 0f);
            shader.setLocalMatrix(matrix);
            canvas.drawRect(b, paint);

            canvas.restoreToCount(saveCount);

        } else {
            super.onDraw(canvas);
        }
    }

    /**
     * Makes the TextView expanding without any animation.
     */
    public void expandCollapse() {
        setMaxLines(mExpanded ? mMaxLines : getLineCount());
        mExpanded = !mExpanded;
    }

    /**
     * Makes the TextView expanding/collapsing with sliding animation (vertically)
     *
     * @param duration Duration in milliseconds from beginning to end of the animation
     */
    public void expandCollapseAnimated(final int duration) {
        // Height before the animation (either maxLine or lineCount, depending on current state)
        final int startHeight = getMeasuredHeight();

        // Set new maxLine value depending on current state
        setMaxLines(mExpanded ? mMaxLines : getLineCount());
        mExpanded = !mExpanded;

        // Measuring new height
        measure(View.MeasureSpec.makeMeasureSpec(
                getWidth(), View.MeasureSpec.EXACTLY),
                View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED)
        );
        final int endHeight = getMeasuredHeight();

        ObjectAnimator animation = ObjectAnimator.ofInt(
                this,               // TextView
                MAX_HEIGHT_ATTR,    // maxHeight
                startHeight,        // height before animation
                endHeight           // height after animation
        );
        animation.setDuration(duration).start();
    }

    /**
     * Sets maxLine value programmatically
     *
     * @param newValue new value for maxLines
     */
    public void setNewMaxLine(int newValue) {
        mMaxLines = newValue;
    }
}
Vkay
  • 495
  • 1
  • 6
  • 21
  • 1
    The answer on the post you've linked does not apply any background/foreground in the draw routine. The example layout element has a background color and a text color set, just to match the image given by the OP., but you can set those to anything you like. Here's a quick test with that text (mostly), black text on a white background, an adjusted `FADE_LENGTH_FACTOR`, and a little ad hoc redo on the `Canvas` stuff for recent API changes: https://i.stack.imgur.com/6V7wL.jpg. – Mike M. Apr 24 '19 at 13:20
  • @MikeM. When I tried the approach I've also noticed the recent change in API 28 but I could not find any way to fix. My modifications result either in this (https://i.imgur.com/KPAbys4.jpg) or the main text is visible but the Rect has a black gradient. If you post your modifications into an answer I could sign it as solution since your pic matches my goal ;) – Vkay Apr 24 '19 at 16:11
  • 1
    Well, that's my answer on the linked post, so I should really just update that for the new API changes. Anyway, for that test, I simply changed the `saveLayer()` to do the whole `View` – `canvas.saveLayer(0, 0, getWidth(), getHeight(), null)`. Since we can't narrow the save flags anymore, it automatically includes `CLIP_TO_LAYER_SAVE_FLAG`, which is why you get the result shown in your image. `View` itself actually still uses a hidden call internally which omits that flag, so that's kinda unfair, I'd say. I'm still investigating whether there's a better way to do this, with the recent changes. – Mike M. Apr 25 '19 at 00:45
  • Hi, Vkay. I'd completely forgotten about updating that answer, until there was some recent activity there I was notified about. I'm curious if you were able to implement the modifications I mentioned here without issue. I just want to make sure there aren't any unforeseen problems that I could avoid before I commit an edit. Thanks! – Mike M. Aug 09 '19 at 08:49
  • Hey Mike M., yes, I've implemented it the way I liked with your advices. I also made the class dynamically and with some animation stuff :). My final implementation can be found here on GitHub gist: https://gist.github.com/vkay94/52578f5aee1781695d2f2bd293b6f416 . It would be nice if you could link it in your edited answer ;) – Vkay Aug 13 '19 at 18:09
  • Even this solution still has bug when the text alignment is center (the fade out part missed the last character), this is the only answer that really fading out last characters of sentences. It should have much more upvotes. Thanks for your solution & Mike's also. – Hoang Nguyen Huu Jul 04 '23 at 07:05

1 Answers1

0

Try this out .. Hope this will work

public class ShadowSpan : Android.Text.Style.CharacterStyle
{
   public float Dx;
   public float Dy;
   public float Radius;
   public Android.Graphics.Color Color;
   public ShadowSpan(float radius, float dx, float dy, Android.Graphics.Color color)
   {
    Radius = radius; Dx = dx; Dy = dy; Color = color;
   }

   public override void UpdateDrawState (TextPaint tp)
   {
     tp.SetShadowLayer(Radius, Dx, Dy, Color);
    }
}
Mini Chip
  • 949
  • 10
  • 12
  • I don't think that a shadow will work since the color of the characters won't change from black to transparent/white. – Vkay Apr 24 '19 at 09:34
  • I have used this link to give shadow to my text view . and it worked for me .... there you can the method setShadowLayer() https://developer.android.com/reference/android/widget/TextView.html#setShadowLayer(float,%2520float,%2520float,%2520int) – Mini Chip Apr 24 '19 at 10:44