It is still a github open source project: WaitingDots
This project does not have many codes, and the implementation is very simple, but it is very interesting because the basic elements of animation are not drawn, but are implemented using spannableString.
- DotsTextView.java
- JumpingSpan.java
- MainActivity.java
DotstextView is the main body of animation.
JumpingSpan is a basic element and a plug-in in animation.
In MainActivity, you only need to introduce DotsTextView in the layout.
The following is the dividing line, show code:
package pl.tajchert.sample;
import android. graphics. Canvas;
import android. graphics. Paint;
import android.graphics.Paint.FontMetricsInt;
import android.text.style.ReplacementSpan;
/* ReplacementSpan is really a magical thing, and it is rarely introduced on the official api * Several main functions are also do nothing. * In this example, the custom translationX and translationY have no effect . If you assign values to two variables *, the distance between the first JumpingSpan and the previous element will increase. This is used here to make each "." a separate unit for independent operations */
public class JumpingSpan extends ReplacementSpan {
private float translateOnX= 0;
private float translateOnY= 0;
@Override
public int getSize(Paint paint, CharSequence text, int start, int end, FontMetricsInt fontMetricsInt) {
return (int) paint.measureText(text, start, end);
}
@Override
public void draw(Canvas canvas, CharSequence text, int start, int end, float x, int top, int y, int span> bottom, Paint paint) {
canvas.drawText(text, start, end, x + translationX, y + translationY, paint);
}
public void setTranslationX(float translationX) {
this.translatiOnX= translationX;
}
public void setTranslationY(float translationY) {
this.translatiOnY= translationY;
}
}
package pl.tajchert.sample;
import android.animation.Animator;
import android.animation.AnimatorSet;
import android.animation.ObjectAnimator;
import android.animation.TypeEvaluator;
import android.animation.ValueAnimator;
import android.animation.ValueAnimator.AnimatorUpdateListener;
import android.content.Context;
import android.content.res.TypedArray;
import android.os.Handler;
import android.os.Looper;
import android.text.SpannableString;
import android.text.Spanned;
import android.util.AttributeSet;
import android.widget.TextView;
import pl.tajchert.waitingdots.R;
public class DotsTextView extends TextView {
private JumpingSpan dotOne;
private JumpingSpan dotTwo;
private JumpingSpan dotThree;
private int showSpeed = 700;
private int jumpHeight;
private boolean autoPlay;
private boolean isPlaying;
private boolean isHide;
private int period;
private long startTime;
private boolean lockDotOne;
private boolean lockDotTwo;
private boolean lockDotThree;
private Handler handler;
private AnimatorSet mAnimatorSet = new AnimatorSet();
private float textWidth;
public DotsTextView(Context context) {
super(context);
init(context, null);
}
public DotsTextView(Context context, AttributeSet attrs) {
super(context, attrs);
init(context, attrs);
}
public DotsTextView(Context context, AttributeSet attrs, int span > defStyleAttr) {
super(context, attrs, defStyleAttr);
init(context, attrs);
}
private void init(Context context, AttributeSet attrs) {
handler = new Handler(Looper.getMainLooper());
//custom attribute
if (attrs != null) {
TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.WaitingDots);
period = typedArray.getInt(R.styleable.WaitingDots_period, 6000);
jumpHeight = typedArray.getInt(R.styleable.WaitingDots_jumpHeight, (int) (getTextSize() / 4) );
autoPlay = typedArray.getBoolean(R.styleable.WaitingDots_autoplay, true);
typedArray. recycle();
}
dotOne= new JumpingSpan();
dotTwo = new JumpingSpan();
dotThree = new JumpingSpan();
//Set each point to jumpingSpan type
SpannableString spannable = new SpannableString("...");
spannable.setSpan(dotOne, 0, 1, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
spannable.setSpan(dotTwo, 1, 2, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
spannable.setSpan(dotThree, 2, 3, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
setText(spannable, BufferType. SPANNABLE);
textWidth = getPaint().measureText(".", 0, 1);
//The following two are to add updateListener to point 1, and use it to perform refresh actions
ObjectAnimator dotOneJumpAnimator= createDotJumpAnimator(dotOne, 0);
dotOneJumpAnimator.addUpdateListener(new AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator valueAnimator) {
invalidate();
}
});
//The combined action of three points is controlled by animationSet
mAnimatorSet.playTogether(dotOneJumpAnimator, createDotJumpAnimator(dotTwo,
period / 6), createDotJumpAnimator(dotThree, period * 2 / 6));
isPlaying = autoPlay;
if(autoPlay) {
start();
}
}
public void start() {
isPlaying = true;
//INFINITE once started
setAllAnimationsRepeatCount(ValueAnimator. INFINITE);
mAnimatorSet.start();
}
/*The implementation core of animation *@param jumpingSpan input point, * @delay animation running delay, through this parameter, three points can move with time difference */
private ObjectAnimator createDotJumpAnimator(JumpingSpan jumpingSpan, long delay) {
ObjectAnimator jumpAnimator = ObjectAnimator.ofFloat(jumpingSpan, "translationY", 0, -jumpHeight);
/*setEvaluator This is important, the function is to smoothly realize the "rhythm" of the point movement through the equation, you can try to remove this section, you will find that the point will be at the default speed Up and down movement, especially blunt, the evaluate in TypeEvaluator can calculate the current position of the point. Through the calculation of the current point, the trajectory movement of the point is indirectly designed, which achieves the same effect as the time interpolation TimeInterpolator, just like you don't know the speed but you know the position per second is equivalent to the speed. This calculation method is as follows: You can refer to this blog post http://blog.csdn.net/serapme/article/details/47006049 ValueAnimator also encapsulates a TypeAnimator, and calculates the attribute value based on the value calculated by the start and end values and TimeIniterpolator . ValueAnimator calculates a time factor (0~1) based on the ratio of the animation's elapsed time to the animation's total duration (duration), then calculates another factor based on TimeInterpolator, and finally TypeAnimator calculates the attribute value through this factor, such as 10ms in the above example Time: First calculate the time factor, that is, the percentage of elapsed time: t=10ms/40ms=0.25 The interpolation factor after interpolation calculation (inteplator): about 0.15, the above example uses AccelerateDecelerateInterpolator, the calculation formula is (input is Time factor): (Math.cos((input + 1) * Math.PI) / 2.0f) + 0.5f; Finally, calculate the property value at 10ms according to the TypeEvaluator: 0.15*(40-0)=6pixel. In the above example, the TypeEvaluator is FloatEvaluator, and the calculation method is: public Float evaluate(float fraction, Number startValue, Number endValue) { float startFloat = startValue.floatValue(); return startFloat + fraction * (endValue.floatValue() - startFloat); } */
jumpAnimator.setEvaluator(new TypeEvaluator() {
@Override
public Number evaluate(float fraction, Number from, Number to) {
return Math.max(0, Math.sin(fraction * Math.PI * 2)) * (to. floatValue() - from. floatValue());
}
});
jumpAnimator. setDuration(period);
jumpAnimator.setStartDelay(delay);
jumpAnimator.setRepeatCount(ValueAnimator.INFINITE);
jumpAnimator.setRepeatMode(ValueAnimator.RESTART);
return jumpAnimator;
}
//The following non-core functions are not difficult and will not be commented~mainly because of laziness~
public void stop() {
isPlaying = false;
setAllAnimationsRepeatCount(0);
}
private void setAllAnimationsRepeatCount(int repeatCount) {
for (Animator animator : mAnimatorSet. getChildAnimations()) {
if (animator instanceof ObjectAnimator) {
((ObjectAnimator) animator).setRepeatCount(repeatCount);
}
}
}
public void hide() {
createDotHideAnimator(dotThree, 2).start();
ObjectAnimator dotTwoMoveRightToLeft = createDotHideAnimator(dotTwo, 1);
dotTwoMoveRightToLeft.addotOneJumpAnimator, createDotJumpAnimator(dotTwo,
period / 6), createDotJumpAnimator(dotThree, period * 2 / 6));
isPlaying = autoPlay;
if(autoPlay) {
start();
}
}
public void start() {
isPlaying = true;
//INFINITE once started
setAllAnimationsRepeatCount(ValueAnimator. INFINITE);
mAnimatorSet.start();
}
/*The implementation core of animation *@param jumpingSpan input point, * @delay animation running delay, through this parameter, three points can move with time difference */
private ObjectAnimator createDotJumpAnimator(JumpingSpan jumpingSpan, long delay) {
ObjectAnimator jumpAnimator = ObjectAnimator.ofFloat(jumpingSpan, "translationY", 0, -jumpHeight);
/*setEvaluator This is important, the function is to smoothly realize the "rhythm" of the point movement through the equation, you can try to remove this section, you will find that the point will be at the default speed Up and down movement, especially blunt, the evaluate in TypeEvaluator can calculate the current position of the point. Through the calculation of the current point, the trajectory movement of the point is indirectly designed, which achieves the same effect as the time interpolation TimeInterpolator, just like you don't know the speed but you know the position per second is equivalent to the speed. This calculation method is as follows: You can refer to this blog post http://blog.csdn.net/serapme/article/details/47006049 ValueAnimator also encapsulates a TypeAnimator, and calculates the attribute value based on the value calculated by the start and end values and TimeIniterpolator . ValueAnimator calculates a time factor (0~1) based on the ratio of the animation's elapsed time to the animation's total duration (duration), then calculates another factor based on TimeInterpolator, and finally TypeAnimator calculates the attribute value through this factor, such as 10ms in the above example Time: First calculate the time factor, that is, the percentage of elapsed time: t=10ms/40ms=0.25 The interpolation factor after interpolation calculation (inteplator): about 0.15, the above example uses AccelerateDecelerateInterpolator, the calculation formula is (input is Time factor): (Math.cos((input + 1) * Math.PI) / 2.0f) + 0.5f; Finally, calculate the property value at 10ms according to the TypeEvaluator: 0.15*(40-0)=6pixel. In the above example, the TypeEvaluator is FloatEvaluator, and the calculation method is: public Float evaluate(float fraction, Number startValue, Number endValue) { float startFloat = startValue.floatValue(); return startFloat + fraction * (endValue.floatValue() - startFloat); } */
jumpAnimator.setEvaluator(new TypeEvaluator() {
@Override
public Number evaluate(float fraction, Number from, Number to) {
return Math.max(0, Math.sin(fraction * Math.PI * 2)) * (to. floatValue() - from. floatValue());
}
});
jumpAnimator. setDuration(period);
jumpAnimator.setStartDelay(delay);
jumpAnimator.setRepeatCount(ValueAnimator.INFINITE);
jumpAnimator.setRepeatMode(ValueAnimator.RESTART);
return jumpAnimator;
}
//The following non-core functions are not difficult and will not be commented~mainly because of laziness~
public void stop() {
isPlaying = false;
setAllAnimationsRepeatCount(0);
}
private void setAllAnimationsRepeatCount(int repeatCount) {
for (Animator animator : mAnimatorSet. getChildAnimations()) {
if (animator instanceof ObjectAnimator) {
((ObjectAnimator) animator).setRepeatCount(repeatCount);
}
}
}
public void hide() {
createDotHideAnimator(dotThree, 2).start();
ObjectAnimator dotTwoMoveRightToLeft = createDotHideAnimator(dotTwo, 1);
dotTwoMoveRightToLeft.addUpdateListener(new AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator valueAnimator) {
invalidate();
}
});
dotTwoMoveRightToLeft. start();
isHide = true;
}
public void show() {
ObjectAnimator dotThreeMoveRightToLeft = createDotShowAnimator(dotThree, 2);
dotThreeMoveRightToLeft. start();
ObjectAnimator dotTwoMoveRightToLeft = createDotShowAnimator(dotTwo, 1);
dotTwoMoveRightToLeft.addUpdateListener(new AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator valueAnimator) {
invalidate();
}
});
dotTwoMoveRightToLeft. start();
isHide = false;
}
private ObjectAnimator createDotHideAnimator(JumpingSpan span, float widthMultiplier) {
return createDotHorizontalAnimator(span, 0, -textWidth * widthMultiplier);
}
private ObjectAnimator createDotShowAnimator(JumpingSpan span, int widthMultiplier) {
return createDotHorizontalAnimator(span, -textWidth * widthMultiplier, 0);
}
private ObjectAnimator createDotHorizontalAnimator(JumpingSpan span, float from, float to) {
ObjectAnimator dotThreeMoveRightToLeft = ObjectAnimator.ofFloat(span, "translationX", from, to);
dotThreeMoveRightToLeft.setDuration(showSpeed);
return dotThreeMoveRightToLeft;
}
public void showAndPlay() {
show();
start();
}
public void hideAndStop() {
hide();
stop();
}
public boolean isHide() {
return isHide;
}
public boolean isPlaying() {
return isPlaying;
}
public void setJumpHeight(int jumpHeight) {
this.jumpHeight = jumpHeight;
}
public void setPeriod(int period) {
this. period = period;
}
}
There is no doubt that it can be introduced into the layout in the activity.
dotsTextView = (DotsTextView) findViewById(R.id.dots);
A simple to use loadingview.
UpdateListener(new AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator valueAnimator) {
invalidate();
}
});
dotTwoMoveRightToLeft. start();
isHide = true;
}
public void show() {
ObjectAnimator dotThreeMoveRightToLeft = createDotShowAnimator(dotThree, 2);
dotThreeMoveRightToLeft. start();
ObjectAnimator dotTwoMoveRightToLeft = createDotShowAnimator(dotTwo, 1);
dotTwoMoveRightToLeft.addUpdateListener(new AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator valueAnimator) {
invalidate();
}
});
dotTwoMoveRightToLeft. start();
isHide = false;
}
private ObjectAnimator createDotHideAnimator(JumpingSpan span, float widthMultiplier) {
return createDotHorizontalAnimator(span, 0, -textWidth * widthMultiplier);
}
private ObjectAnimator createDotShowAnimator(JumpingSpan span, int widthMultiplier) {
return createDotHorizontalAnimator(span, -textWidth * widthMultiplier, 0);
}
private ObjectAnimator createDotHorizontalAnimator(JumpingSpan span, float from, float to) {
ObjectAnimator dotThreeMoveRightToLeft = ObjectAnimator.ofFloat(span, “translationX”, from, to);
dotThreeMoveRightToLeft.setDuration(showSpeed);
return dotThreeMoveRightToLeft;
}
public void showAndPlay() {
show();
start();
}
public void hideAndStop() {
hide();
stop();
}
public boolean isHide() {
return isHide;
}
public boolean isPlaying() {
return isPlaying;
}
public void setJumpHeight(int jumpHeight) {
this.jumpHeight = jumpHeight;
}
public void setPeriod(int period) {
this. period = period;
}
}
There is no doubt that it can be introduced into the layout in the activity.
dotsTextView = (DotsTextView) findViewById(R.id.dots);
A simple to use loadingview.