效果
前言
之前在一个国外大牛的博客上看到的,自己模仿实现了一下,应用在上一个项目的splash里面。
原理
为每个字符设置样式,使用MutableForegroundColorSpan。MutableForegroundColorSpan是自定义的可设置透明度的ForegroundColorSpan。然后,利用FireworksSpanGroup类将这些MutableForegroundColorSpan管理起来,方便设置透明度。
动效通过ObjectAnimator实现,这里相应的自定义了一个FIREWORKS_GROUP_PROGRESS_PROPERTY属性,其实没大必要。可以直接利用ValueAnimator实现动效,在updateListener里面直接调用spanGroup.setAlpha(value);
。ObjectAnimator对ValueAnimator进行了封装,会调用属性的set的方法,可以看到我们在set方法里面也是调用spanGroup.setAlpha(value);
。所以可以直接使用ValueAnimator,就不用自定义属性了。
在动画过程中,从左到右去增加MutableForegroundColorSpan的alpha值。比如,如果有5个字符,动画进行到30%,那么0.3*5->1.5。此时第一个字符alpha值为1*255,完全不透明;第二个字符alpha值为0.5*255,50%透明。
源码
public class TyperTextView {
private TextView textView;
private ObjectAnimator objectAnimator;
private long duration = 1000;
private int animationTextBeg = -1;
private int animationTextEnd = -1;
private static final Property<FireworksSpanGroup, Float> FIREWORKS_GROUP_PROGRESS_PROPERTY =
new Property<FireworksSpanGroup, Float>(Float.class, "FIREWORKS_GROUP_PROGRESS_PROPERTY") {
@Override
public void set(FireworksSpanGroup spanGroup, Float value) {
spanGroup.setAlpha(value);
}
@Override
public Float get(FireworksSpanGroup spanGroup) {
return spanGroup.getAlpha();
}
};
public TyperTextView(TextView textView) {
this.textView = textView;
}
public void setDuration(long duration) {
this.duration = duration;
}
public void setAnimationTextScope(int beg, int end) {
this.animationTextBeg = beg;
this.animationTextEnd = end;
}
private void setUpAnimation() {
final FireworksSpanGroup spanGroup = new FireworksSpanGroup(0);
//init the group with multiple spans
//set spans on the ActionBar spannable title
CharSequence plainString = null;
if (textView != null) {
plainString = textView.getText();
}
final SpannableString spannableString = new SpannableString(plainString);
if (animationTextBeg == -1) {
animationTextBeg = 0;
}
if (animationTextEnd == -1) {
animationTextEnd = plainString.length();
}
for (int i = animationTextBeg; i < animationTextEnd; i++) {
int color = Color.parseColor("#ffffff");
if (textView != null) {
color = textView.getTextColors().getDefaultColor();
}
MutableForegroundColorSpan span = new MutableForegroundColorSpan(0, color);
spanGroup.addSpan(span);
spannableString.setSpan(span, i, i + 1, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
}
spanGroup.init();
objectAnimator = ObjectAnimator.ofFloat(spanGroup, FIREWORKS_GROUP_PROGRESS_PROPERTY, 0.0f, 1.0f);
objectAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
if (textView != null) {
textView.setText(spannableString);
}
}
});
objectAnimator.setDuration(duration);
}
public void startAnimation() {
setUpAnimation();
objectAnimator.start();
}
public void startAnimationIndefinitely() {
setUpAnimation();
objectAnimator.setRepeatCount(ObjectAnimator.INFINITE);
objectAnimator.setRepeatMode(ObjectAnimator.RESTART);
objectAnimator.start();
}
public void stopAnimation() {
objectAnimator.end();
textView = null;
}
private static final class FireworksSpanGroup {
private final float mAlpha;
private final ArrayList<MutableForegroundColorSpan> mSpans;
private FireworksSpanGroup(float alpha) {
mAlpha = alpha;
mSpans = new ArrayList<MutableForegroundColorSpan>();
}
public void addSpan(MutableForegroundColorSpan span) {
span.setAlpha((int) (mAlpha * 255));
mSpans.add(span);
}
public void init() {
// Collections.shuffle(mSpans);
}
public void setAlpha(float alpha) {
int size = mSpans.size();
float total = 1.0f * size * alpha;
for (int index = 0; index < size; index++) {
MutableForegroundColorSpan span = mSpans.get(index);
if (total >= 1.0f) {
span.setAlpha(255);
total -= 1.0f;
} else {
span.setAlpha((int) (total * 255));
total = 0.0f;
}
}
}
public float getAlpha() {
return mAlpha;
}
}
private static class MutableForegroundColorSpan extends ForegroundColorSpan {
private int mAlpha = 255;
private int mForegroundColor;
public MutableForegroundColorSpan(int alpha, int color) {
super(color);
mAlpha = alpha;
mForegroundColor = color;
}
public MutableForegroundColorSpan(Parcel src) {
super(src);
mForegroundColor = src.readInt();
mAlpha = src.readInt();
}
public void writeToParcel(Parcel dest, int flags) {
super.writeToParcel(dest, flags);
dest.writeInt(mForegroundColor);
dest.writeFloat(mAlpha);
}
@Override
public void updateDrawState(TextPaint ds) {
ds.setColor(getForegroundColor());
}
/**
* @param alpha from 0 to 255
*/
public void setAlpha(int alpha) {
mAlpha = alpha;
}
public void setForegroundColor(int foregroundColor) {
mForegroundColor = foregroundColor;
}
public float getAlpha() {
return mAlpha;
}
@Override
public int getForegroundColor() {
return Color.argb(mAlpha, Color.red(mForegroundColor), Color.green(mForegroundColor), Color.blue(mForegroundColor));
}
}
}