title: How to apply horizontal fading edge on the last line of a maxLine TextView
body: <p>I'd like to implement a fading edge behavior to <code>TextView</code> like the Google Play Movie does:</p>
  
  <p><a href="https://i.stack.imgur.com/WhHzN.jpg" rel="nofollow noreferrer"><img src="https://i.stack.imgur.com/WhHzN.jpg" alt="enter image description here"></a></p>
  
  <p>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 <code>android:maxLines</code>? (For example <code>android:maxLines="3"</code>)</p>
  
  <p>I've tried the following but it works with the attribute <code>android:singleLine</code> only which is not my goal:</p>
  
  <pre><code>&lt;TextView
      ...
      android:requiresFadingEdge="horizontal"
      android:fadingEdgeLength="30dp"
      android:ellipsize="none"
      android:singleLine="true" /&gt;
  </code></pre>
  
  <p>Setting <code>android:maxLines</code> here instead results in no fading at all.</p>
  
  <p><strong>Edit/Additional:</strong></p>
  
  <p>Previously I also tried a <code>Shader</code> with <code>LinearGradient</code> while extending <code>TextView</code> like <a href="https://stackoverflow.com/questions/49295526/how-to-fade-out-the-end-of-the-last-line-in-a-textview">here</a>, but the described solution applies a background/foreground (and there were also some other issues with it ...).</p>
  
  <p>I'd like to apply the <code>Gradient</code> to the last 3-4 characters of the maxLine line. Could this be possible?</p>
  
  <p><strong>Edit:</strong></p>
  
  <p>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 <a href="https://gist.github.com/vkay94/52578f5aee1781695d2f2bd293b6f416" rel="nofollow noreferrer">here</a> as java file):</p>
  
  <pre><code>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() &gt; getMaxLines() &amp;&amp; !mExpanded
                  &amp;&amp; getRootView() != null &amp;&amp; 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;
      }
  }
  </code></pre>