前言
衆所周知,爲了不ANR,不可以在UI線程上執行耗時的操作。所以爲了效率,也爲了通用,我把計算繪製參數的操作放到工作線程中去了。參考了這篇文章。
首先創建一個工作線程(workerThread),一個與workerThread關聯的Handler(workerHandler),還有一個和UI線程關聯的Handler(uiHandler)。在onDraw()
執行結束之後,會通過workerHander發消息,workerThread收到消息會去重新計算繪製參數。完成之後,會通過uiHandler發消息,UI線程收到消息之後調用invalidate()
,會導致onDraw()被執行。進入下一次循環。
這樣就實現了,重新計算繪製參數在workerThread執行,invalidate()
在UI線程執行。在這裏,handler充當信使的作用,它可以在其他線程向自己關聯的線程發消息、post runnable等等。
HandlerThread知識
HandlerThread類的註釋:
Handy class for starting a new thread that has a looper. The looper can then be used to create handler classes. Note that start() must still be called.
Handler類的註釋
/**
* A Handler allows you to send and process {@link Message} and Runnable
* objects associated with a thread's {@link MessageQueue}. Each Handler
* instance is associated with a single thread and that thread's message
* queue. When you create a new Handler, it is bound to the thread /
* message queue of the thread that is creating it -- from that point on,
* it will deliver messages and runnables to that message queue and execute
* them as they come out of the message queue.
*
* <p>There are two main uses for a Handler: (1) to schedule messages and
* runnables to be executed as some point in the future; and (2) to enqueue
* an action to be performed on a different thread than your own.......
源碼
public class LoadingView extends View {
/**
* A list that contains the cx of each point.
*/
private List<Float> cx;
/**
* The cy of every point is the same.
*/
private float cy;
/**
* The radius of every point is the same.
*/
private float radius;
/**
* The paints that used to draw each point.
*/
private List<Paint> paints;
/**
* The length that point transfer to enter and exit.
*/
private float dx;
private List<Float> dxs;
/**
* The offset between each point.
*/
private float offset;
/**
* Used in animation.
*/
private long startMillis = -1;
/**
* Used to make translation more smooth
*/
private Interpolator enterInterpolator, exitInterpolator;
/**
* The time one point enter or exit
*/
private long duration;
/**
* The moving velocity of the point which is not entering or exiting
*/
private float v = 0.04F;
/**
* The number of points
*/
private int pointNum;
private float cxOffest;
private Handler uiHandler;
private HandlerThread workerThread;
private Handler workerHandler;
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;
cx = new ArrayList<Float>(Collections.nCopies(pointNum, 0.0F));
Paint paint = new Paint();
paint.setDither(true);
paint.setAntiAlias(true);
paint.setColor(Color.parseColor("#00BCD4"));
paint.setAlpha(0);
paints = new ArrayList<Paint>();
for (int i = 0; i < pointNum; i++) {
paints.add(new Paint(paint));
}
enterInterpolator = new DecelerateInterpolator(1.5F);
exitInterpolator = new AccelerateInterpolator(2.0F);
duration = 600;
uiHandler = new Handler(new Handler.Callback() {
@Override
public boolean handleMessage(Message message) {
invalidate();
return true;
}
});
workerThread = new HandlerThread("workerThread");
workerThread.start();
workerHandler = new Handler(workerThread.getLooper(), new Handler.Callback() {
@Override
public boolean handleMessage(Message message) {
updateDrawParams();
uiHandler.sendEmptyMessage(0);
return true;
}
});
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
initSize();
}
private void initSize(){
cy = getMeasuredHeight() / 2;
radius = getMeasuredHeight() / 3;
dx = getMeasuredWidth() / 3;
cxOffest = (getMeasuredWidth() - 2*dx - v*duration*3) * 0.5F;
offset = radius * 3;
}
@Override
protected void onDraw(Canvas canvas) {
for (int i = 0; i < cx.size(); i++) {
canvas.drawCircle(cx.get(i), cy, radius, paints.get(i));
}
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;
}
// (pointNum * 2 * duration) is a cycle
long passMills = (currentMillis - startMillis) % (pointNum * 2 * duration);
// updateDrawParams each point's cx and alpha
for (int i = 0; i < cx.size(); i++) {
long step = passMills / duration;
float animationFraction = (passMills % duration) * 1.0F / duration;
float enterX, transX, exitX;
if (step < 4) { // entering half
if (i < step) {
enterX = dx - i*offset + i*duration*v;
transX = (passMills - (i + 1) * duration) * v;
exitX = 0;
paints.get(i).setAlpha(255);
} else if (i == step) {
float interpolatedFraction = enterInterpolator.getInterpolation(animationFraction);
enterX = interpolatedFraction*dx - i*offset + i*duration*v;
transX = 0;
exitX = 0;
paints.get(i).setAlpha((int) (255*interpolatedFraction));
} else {
enterX = 0;
transX = 0;
exitX =0;
paints.get(i).setAlpha(0);
}
} else { // exiting half
if (i < step-4){
enterX = dx - i*offset + i*duration*v;
transX = (passMills - (i + 1) * duration) * v;
exitX = dx;
paints.get(i).setAlpha(0);
} else if (i == step-4){
float interpolatedFraction = exitInterpolator.getInterpolation(animationFraction);
enterX = dx - i*offset + i*duration*v;
transX = (passMills - (i + 1) * duration) * v;
exitX = interpolatedFraction * dx;
paints.get(i).setAlpha((int) (255*(1-interpolatedFraction)));
} else {
enterX = dx - i*offset + i*duration*v;
transX = (passMills - (i + 1) * duration) * v;
exitX = 0;
paints.get(i).setAlpha(255);
}
}
cx.set(i, cxOffest + enterX + transX + exitX);
}
}
}