textview可以有跑马灯的效果,如何让textview字体可以像波浪形状跳动起来呢?先上个图
偶尔看了一个跳动的textview的开源项目,了解其原理,得到了一个很好用的类。还是很span有关,感觉span这个类好强大。
1. 下面介绍一下原理,利用SuperscriptSpan这个类,有两个函数
updateDrawState函数,还有一个updateMeasureState函数,可以获取textview的baslineshift.这个是字体的底部的基线值。然后利用属性动画的valueAnimator动态改变baslineshift,就可以让textview的字体跳动起来。是不是很简单。
下面看一下代码:
public class JumpingSpan extends SuperscriptSpan implements ValueAnimator.AnimatorUpdateListener{
private final WeakReference<TextView> textView;
private final int delay;//每一个字体的间隔的时间
private final int loopDuration;//设置字体跳动的循环的时间
private final float animatedRange;//字体摆动的速度范围0,1
private int shift;//字体摆动的偏移量
private ValueAnimator jumpAnimator;
public JumpingSpan(@NonNull TextView textView,
@IntRange(from = 1) int loopDuration,
@IntRange(from = 0) int position,
@IntRange(from = 0) int waveCharOffset,//单个字体偏移量时间间隔
@FloatRange(from = 0, to = 1, fromInclusive = false) float animatedRange) {
this.textView = new WeakReference<>(textView);
this.delay = waveCharOffset * position;
this.loopDuration = loopDuration;
this.animatedRange = animatedRange;
}
@Override
public void updateDrawState(TextPaint tp) {
super.updateDrawState(tp);
initIfNecessary(tp.ascent());
tp.baselineShift = shift;//更新字体底部基线的偏移量
}
@Override
public void updateMeasureState(TextPaint tp) {
super.updateMeasureState(tp);
initIfNecessary(tp.ascent());
tp.baselineShift = shift;
}
private void initIfNecessary(float ascent) {
if (jumpAnimator != null) {
return;
}
this.shift = 0;
int maxShift = (int) ascent / 2;
//设置最大字体偏移量 maxshift*(0,1)
jumpAnimator = ValueAnimator.ofInt(0, maxShift);
jumpAnimator
.setDuration(loopDuration)
.setStartDelay(delay);
jumpAnimator.setInterpolator(new JumpInterpolator(animatedRange));
jumpAnimator.setRepeatCount(ValueAnimator.INFINITE);
jumpAnimator.setRepeatMode(ValueAnimator.RESTART);
jumpAnimator.addUpdateListener(this);
jumpAnimator.start();
}
@Override
public void onAnimationUpdate(ValueAnimator animation) {
// No need for synchronization as this always runs on main thread anyway
TextView v = textView.get();
if (v != null) {
updateAnimationFor(animation, v);
}
}
private void updateAnimationFor(ValueAnimator animation, TextView v) {
if (isAttachedToHierarchy(v)) {
//设置最大字体偏移量 maxshift*(0,1)
shift = (int) animation.getAnimatedValue();//
Log.e("gac","shift:"+shift);
v.invalidate();
}
}
private static boolean isAttachedToHierarchy(View v) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
return v.isAttachedToWindow();
}
return v.getParent() != null; // Best-effort fallback (without adding support-v4 just for this...)
}
private static class JumpInterpolator implements TimeInterpolator {
private final float animRange;
public JumpInterpolator(float animatedRange) {
animRange = Math.abs(animatedRange);//字体摆动的速度 0-1
Log.e("gac","animRange:"+animatedRange);
}
@Override
public float getInterpolation(float input) {
// We want to map the [0, PI] sine range onto [0, animRange]
if (input > animRange) {
return 0f;
}//(0,animRange)//在这个速度范围内摆动的弧度(0,pi)
double radians = (input / animRange) * Math.PI;
Log.e("gac","radians:"+ Math.sin(radians));
return (float) Math.sin(radians);//(0,1)
}
}
}
如何利用上面的类实现效果呢?
tv = (TextView) findViewById(R.id.tv);
SpannableStringBuilder sbb = new SpannableStringBuilder(tv.getText());
//buildSingleSpan(sbb);
buildWavingSpans(sbb,tv);
tv.setText(sbb);
private JumpingSpan[] buildWavingSpans(SpannableStringBuilder sbb,TextView tv) {
JumpingSpan[] spans;
int loopDuration = 1300;
int startPos = 0;//textview字体的开始位置
int endPos = tv.getText().length();//结束位置
int waveCharDelay = loopDuration / (3 * (endPos - startPos));//每个字体延迟的时间
spans = new JumpingSpan[endPos - startPos];
for (int pos = startPos; pos < endPos; pos++) {//设置每个字体的jumpingspan
JumpingSpan jumpingBean =
new JumpingSpan(tv, loopDuration, pos - startPos, waveCharDelay, 0.65f);
sbb.setSpan(jumpingBean, pos, pos + 1, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
spans[pos - startPos] = jumpingBean;
}
return spans;
}
当然了这个不是我想出来的,原项目在这里https://github.com/frakbot/JumpingBeans,我只是稍微理解一下,不能光会用,需要知道人家大概是怎么实现的,万一发现好玩的有用的东西呢?