我一直在搜索如何对 TextView 上的删除线效果进行动画处理,但没有结果。我在论坛和 StackOverflow 上得到的唯一信息是:
some_text_view.setPaintFlags(some_text_view.getPaintFlags() | Paint.STRIKE_THRU_TEXT_FLAG)
我想要做的是,像 Play 商店上的待办事项应用程序一样设置删除线效果的动画,例如Any.do 在从左到右滑动的项目上有它。
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
}
您有几个选择:
扩展 TextView 并创建一个自定义视图,检查是否设置了 STRIKE_THRU_TEXT_FLAG 并触发一个动画,该动画将在文本上绘制一条小线,并在动画的每一帧上增加其宽度。
使用一个空视图并将其放置在 TextView 上(使用relativelayout、framelayout等)。确保该视图的尺寸与您的 TextView 完全匹配。然后按照与之前相同的策略对该视图进行动画处理:在视图中心绘制一条水平线,其宽度在动画的每一帧处递增。
如果你想了解动画本身,那么你可以查找Animator,AnimatorSet等及其相关指南。
我用这种方法制作了删除线动画:
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();
}
// 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;
}
}
基于@oferiko的回答,罢工动画:
private static ValueAnimator startStrikeThruAnimation(TextView textView, int duration){
final SpannableString span = new SpannableString(textView.getText());
final StrikethroughSpan strikeSpan = new StrikethroughSpan();
final ValueAnimator animator = ValueAnimator.ofInt(textView.getText().length());
animator.setDuration(duration);
animator.addUpdateListener(valueAnimator -> {
span.setSpan(strikeSpan, 0, (int) valueAnimator.getAnimatedValue(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
textView.setText(span);
textView.invalidate();
});
animator.start();
return animator;
}
对于反向动画:
private static ValueAnimator reverseStrikeThruAnimation(TextView textView, int duration){
final CharSequence originalText = textView.getText();
final Spannable span = new SpannableString(originalText);
final StrikethroughSpan strikeSpan = new StrikethroughSpan();
final ValueAnimator animator = ValueAnimator.ofInt(originalText.length(), 0);
// Remove existing strike-through span if present
StrikethroughSpan[] existingSpans = span.getSpans(0, span.length(), StrikethroughSpan.class);
for (StrikethroughSpan existingSpan : existingSpans) {
span.removeSpan(existingSpan);
}
animator.setDuration(duration);
animator.addUpdateListener(animation -> {
int animatedValue = (int) animation.getAnimatedValue();
span.setSpan(strikeSpan, 0, animatedValue, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
textView.setText(span);
textView.invalidate();
});
animator.start();
return animator;
}
用途:
startStrikeThruAnimation(sampleTextView, 150);
reverseStrikeThruAnimation(nicknameTextView, 150);