前言
最近在學習開源彈幕引擎源碼,其中對重繪的控制的很好,值得學習,我把一部分技術應用到我的這個動畫裏面。第一,在這個彈幕引擎裏面,各個彈幕相互獨立,獨立計算獨立繪製。第二,不過度繪製,這種情況是丟幀的反面。簡單來說就是不要在16ms內繪製兩次,因爲當fps大於60之後,人眼就看不出差別來了。
效果圖
改進
- 面向對象,讓每個點自己計算顯示位置,自己繪製
每個點的運動軌跡是一樣的,只是啓動時間,啓動位置不同,而這些可以通過時間偏移、位置偏移實現。獨立之後邏輯變的簡單。 - 參照系的問題
所有的點都一直有一個平移速度 - 不再16ms內重繪兩次
計算兩次更新繪製參數之間的時間間隔,如果小於16ms則讓工作線程休眠,得到間隔到了16ms再更新繪製參數。 - 上篇博客中的uiHander沒有必要,用postInvalidate()即可
Demo
源碼
public class LoadingView extends View {
/**
* The cy of every point is the same.
*/
private float cy;
/**
* The radius of every point is the same.
*/
private float radius;
/**
* Used in animation.
*/
private long startMillis = -1;
private long lastMills = -1;
/**
* Used to make translation more smooth
*/
private Interpolator enterInterpolator, exitInterpolator;
/**
* The moving velocity of the point which is not entering or exiting
*/
private float v;
/**
* The number of points
*/
private int pointNum;
private HandlerThread workerThread;
private Handler workerHandler;
private long enterDuration = 600;
private long moveDuration = 1800;
private long exitDuraion = 600;
private long cycle;
private float enterDistance;
private float exitDistance;
private float moveVelocity;
private float offsetBetweenPoint;
private List<LoadingPoint> points;
private float uniformCxOffset;
public LoadingView(Context context) {
super(context);
init();
}
public LoadingView(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}
public LoadingView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
}
private void init() {
pointNum = 4;
enterInterpolator = new DecelerateInterpolator(1.6F);
exitInterpolator = new AccelerateInterpolator(1.2F);
cycle = enterDuration + moveDuration + exitDuraion + enterDuration * (pointNum - 1);
points = new ArrayList<>(pointNum);
for (int i = 0; i < pointNum; i++) {
LoadingPoint point = new LoadingPoint(i);
points.add(point);
}
workerThread = new HandlerThread("workerThread");
workerThread.start();
workerHandler = new Handler(workerThread.getLooper(), new Handler.Callback() {
@Override
public boolean handleMessage(Message message) {
updateDrawParams();
postInvalidate();
return true;
}
});
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
initSize();
}
private void initSize() {
cy = getMeasuredHeight() / 2;
radius = getMeasuredHeight() / 3;
enterDistance = getMeasuredWidth() * 0.425F;
float moveDistance = getMeasuredWidth() * 0.15F;
exitDistance = getMeasuredWidth() * 0.425F;
moveVelocity = moveDistance / moveDuration;
uniformCxOffset = - moveDistance * 0.5F;
offsetBetweenPoint = moveDistance / 3;
}
@Override
protected void onDraw(Canvas canvas) {
for (int i = 0; i < points.size(); i++){
points.get(i).draw(canvas);
}
workerHandler.sendEmptyMessage(0);//update draw params on worker thread
}
@Override
protected void onDetachedFromWindow() {
super.onDetachedFromWindow();
workerThread.quit();
}
private void updateDrawParams() {
long currentMillis = System.currentTimeMillis();
if (startMillis == -1) {
startMillis = currentMillis;
}
if (lastMills == -1) {
lastMills = currentMillis;
} else {
long timeDelta = currentMillis - lastMills;
if (timeDelta < 16) {
try {
Thread.sleep(16 - timeDelta);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
//acquire current millis again, because this thread may sleep for not invalidating too frequently
currentMillis = System.currentTimeMillis();
long passMills = (currentMillis - startMillis) % cycle;
for (int i = 0; i < points.size(); i++){
points.get(i).update(passMills);
}
lastMills = currentMillis;
}
private class LoadingPoint {
private int index;
private float translateX;
private boolean visible;
private float cx;
private Paint paint;
public LoadingPoint(int i) {
index = i;
translateX = i * offsetBetweenPoint;
paint = new Paint();
paint.setDither(true);
paint.setAntiAlias(true);
paint.setColor(Color.parseColor("#455A64"));
paint.setAlpha(0);
}
public void update(long passMills) {
//做時間偏移
passMills = passMills - index * enterDuration;
//還沒有出現
if (passMills < 0) {
visible = false;
return;
}
visible = true;
float enterX = 0;
float exitX = 0;
if (passMills < enterDuration) {
//enter
float enterFraction = ((float) passMills) / enterDuration;
float interpolatedEnterFraction = enterInterpolator.getInterpolation(enterFraction);
enterX = interpolatedEnterFraction * enterDistance;
exitX = 0;
paint.setAlpha((int) (255 * interpolatedEnterFraction));
} else if (passMills < enterDuration + moveDuration) {
enterX = enterDistance;
exitX = 0;
paint.setAlpha(255);
} else {
enterX = enterDistance;
float exitFraction = ((float) (passMills - enterDuration - moveDuration)) / exitDuraion;
float interpolatedExitFraction = exitInterpolator.getInterpolation(exitFraction);
exitX = interpolatedExitFraction * exitDistance;
paint.setAlpha((int) (255 * (1 - interpolatedExitFraction)));
}
//move
float moveX = passMills * moveVelocity;
cx = enterX + moveX + exitX;
}
public void draw(Canvas canvas) {
if (visible) {
canvas.save();
//做位置偏移
canvas.translate(translateX + uniformCxOffset, 0);
canvas.drawCircle(cx, cy, radius, paint);
canvas.restore();
}
}
}
}