0

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
  • 465
  • 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

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
  • 891
  • 7
  • 11
  • 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