Created
August 13, 2019 18:05
-
-
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
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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