Skip to content

Instantly share code, notes, and snippets.

@vkay94
Created August 13, 2019 18:05
Show Gist options
  • Save vkay94/52578f5aee1781695d2f2bd293b6f416 to your computer and use it in GitHub Desktop.
Save vkay94/52578f5aee1781695d2f2bd293b6f416 to your computer and use it in GitHub Desktop.
Custom TextView class which shows a fading end on the last line if maxLines is set
package de.vkay.customviews.widget;
import android.animation.ObjectAnimator;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.LinearGradient;
import android.graphics.Matrix;
import android.graphics.Paint;
import android.graphics.PorterDuff;
import android.graphics.PorterDuffXfermode;
import android.graphics.Rect;
import android.graphics.Shader;
import android.text.Layout;
import android.util.AttributeSet;
import android.view.View;
import androidx.appcompat.widget.AppCompatTextView;
/**
* TextView with fading end if maxLines attribute is set
* Option for animation while expanding and collapsing
*
* Based on Mike M.'s implementation:
* https://stackoverflow.com/a/49299770/5199098
*/
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;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment