Question

I have been searching a lot on how to animate the strike-through affect on a TextView to no results. Only thing I am getting on forums and StackOverflow is:

some_text_view.setPaintFlags(some_text_view.getPaintFlags() | Paint.STRIKE_THRU_TEXT_FLAG)

What I want to do is, animate the strike-through affect like in todo apps on Play Store e.g. Any.do has it on an item left-to-right swipe.

Was it helpful?

Solution

You have a couple of options:

  • Extend TextView and make a custom view which checks if the STRIKE_THRU_TEXT_FLAG is set and fires off an animation that will draw a small line on the text incrementing it's width on each frame of the animation.

  • Use an empty view and place it on your TextView (using RelativeLayout, FrameLayout etc). Make sure the dimensions of this view match exactly with your TextView. Then animate this view following the same strategy as before: Draw a horizontal line at the center of the view whose width is incremented at each frame of the animation.

If you want to know how to the animation itself, then you can look up Animator, AnimatorSet etc and their related guides.

OTHER TIPS

I used this approach to make strikethrough animation:

private void animateStrikeThrough1(final TextView tv) {
    final int ANIM_DURATION = 1000;              //duration of animation in millis
    final int length = tv.getText().length();
    new CountDownTimer(ANIM_DURATION, ANIM_DURATION/length) {
        Spannable span = new SpannableString(tv.getText());
        StrikethroughSpan strikethroughSpan = new StrikethroughSpan();

        @Override
        public void onTick(long millisUntilFinished) {
            //calculate end position of strikethrough in textview
            int endPosition = (int) (((millisUntilFinished-ANIM_DURATION)*-1)/(ANIM_DURAT [ION/length));
            endPosition = endPosition > length ?
                    length : endPosition;
            span.setSpan(strikethroughSpan, 0, endPosition,
                    Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
            tv.setText(span);
        }

        @Override
        public void onFinish() {

        }
    }.start();
}
private fun TextView.startStrikeThroughAnimation(): ValueAnimator {
val span = SpannableString(text)
val strikeSpan = StrikethroughSpan()
val animator = ValueAnimator.ofInt(text.length)
animator.addUpdateListener {
    span.setSpan(strikeSpan, 0, it.animatedValue as Int, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)
    text = span
    invalidate()
}
animator.start()
return animator

}

private fun TextView.reverseStrikeThroughAnimation(): ValueAnimator {
val span = SpannableString(text.toString())
val strikeSpan = StrikethroughSpan()
val animator = ValueAnimator.ofInt(text.length, 0)
animator.addUpdateListener {
    span.setSpan(strikeSpan, 0, it.animatedValue as Int, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)
    text = span
    invalidate()
}
animator.start()
return animator

}

// Created by kot32 on 2017/10/26.

public class AnimationText extends TextView {

private boolean isAnimationStarted;
private float targetLength;
private float totalLength;

private Paint strikePaint;
private float startY;

//should always show Strike-Through
private boolean isDeleted;

public AnimationText(Context context, AttributeSet attrs) {
    super(context, attrs);
    strikePaint = new Paint();
    strikePaint.setColor(Color.BLACK);
    strikePaint.setAntiAlias(true);
    strikePaint.setStyle(Paint.Style.FILL_AND_STROKE);
    strikePaint.setStrokeWidth(5);
}

public AnimationText(Context context) {
    super(context);
    strikePaint = new Paint();
    strikePaint.setColor(Color.BLACK);
    strikePaint.setAntiAlias(true);
    strikePaint.setStyle(Paint.Style.FILL_AND_STROKE);
    strikePaint.setStrokeWidth(5);
}

@Override
protected void onDraw(Canvas canvas) {
    super.onDraw(canvas);
    if (isAnimationStarted) {
        //画线
        canvas.drawLine(0, startY, targetLength, startY, strikePaint);
    }
    if (isDeleted && !isAnimationStarted) {
        canvas.drawLine(0, startY, totalLength, startY, strikePaint);
    }
}

public void startStrikeThroughAnimation() {
    totalLength = getWidth();
    startY = (float) getHeight() / 2;
    isAnimationStarted = true;
    //利用动画逐渐画出一条删除线
    ObjectAnimator objectAnimator = ObjectAnimator.ofFloat(this, "targetLength", 0, totalLength);
    objectAnimator.setInterpolator(new AccelerateInterpolator());
    objectAnimator.addListener(new Animator.AnimatorListener() {
        @Override
        public void onAnimationStart(Animator animation) {

        }

        @Override
        public void onAnimationEnd(Animator animation) {
            isAnimationStarted = false;
        }

        @Override
        public void onAnimationCancel(Animator animation) {

        }

        @Override
        public void onAnimationRepeat(Animator animation) {

        }
    });
    objectAnimator.setDuration(300);
    objectAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
        @Override
        public void onAnimationUpdate(ValueAnimator animation) {
            invalidate();
        }
    });
    objectAnimator.start();
    postInvalidate();
}

public void setDeleted(boolean deleted) {
    isDeleted = deleted;
    totalLength = getWidth();
}

public float getTargetLength() {
    return targetLength;
}

public void setTargetLength(float targetLength) {
    this.targetLength = targetLength;
}
}
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top