Recently, we need to add the function of bullet curtain in our project. We use the open source framework of Station B. DanmakuFlameMaster . It's easy to use. Create a Parser to add a data source. prepare and start. However, it is not enough. Because of curiosity about how the barrage moves, we focus on the next part of the code. Cache and other source code have not been studied for the time being.
Start with prepare
@Override
public void prepare(BaseDanmakuParser parser, DanmakuContext config) {
prepare();
handler.setConfig(config);
handler.setParser(parser);
handler.setCallback(mCallback);
handler.prepare();
}
Call your own prepare first
private void prepare() {
if (handler == null)
handler = new DrawHandler(getLooper(mDrawingThreadType), this,mDanmakuVisible);
}
Create a DrawHandler, which takes the newly created Andler Thread's Loper to execute Handler Message in the sub-threads, so you can't update the UI in the callback of prepare dness.
handler's prepare s will follow DanmakuView's callback
mDanmakuView.setCallback(new master.flame.danmaku.controller.DrawHandler.Callback() {
@Override
public void updateTimer(DanmakuTimer timer) {
}
@Override
public void drawingFinished() {
}
@Override
public void danmakuShown(BaseDanmaku danmaku) {
}
@Override
public void prepared() {
//Execute in a sub-thread
mDanmakuView.start();
}
});
Then call start in the prepared callback method and the backdrop begins. Start will eventually execute on DrawHandler's handler Message
case START:
Long startTime = (Long) msg.obj;
if (startTime != null) {
pausedPosition = startTime;
} else {
pausedPosition = 0;
}
case SEEK_POS:
if (what == SEEK_POS) {
quitFlag = true;
quitUpdateThread();
Long position = (Long) msg.obj;
long deltaMs = position - timer.currMillisecond;
mTimeBase -= deltaMs;
timer.update(position);
mContext.mGlobalFlagValues.updateMeasureFlag();
if (drawTask != null)
drawTask.seek(position);
pausedPosition = position;
}
case RESUME:
removeMessages(DrawHandler.PAUSE);
quitFlag = false;
if (mReady) {
mRenderingState.reset();
mDrawTimes.clear();
mTimeBase = SystemClock.uptimeMillis() - pausedPosition;
timer.update(pausedPosition);
removeMessages(RESUME);
sendEmptyMessage(UPDATE);
drawTask.start();
notifyRendering();
mInSeekingAction = false;
if (drawTask != null) {
drawTask.onPlayStateChanged(IDrawTask.PLAY_STATE_PLAYING);
}
} else {
sendEmptyMessageDelayed(RESUME, 100);
}
break;
You can see that START and SEEK_TO have no break, so they are finally executed in RESUME, where DrawTask is initialized to handle barrage rendering and UPDATE messages are sent.
case UPDATE:
if (mUpdateInNewThread) {
updateInNewThread();
} else {
updateInCurrentThread();
}
Here, the number of threads in the current system determines whether to create new threads to handle barrage rendering. Here's a look at the logic of creating new threads
while (!isQuited() && !quitFlag) {
long startMS = SystemClock.uptimeMillis();
dTime = SystemClock.uptimeMillis() - lastTime;
long diffTime = mFrameUpdateRate - dTime;
if (diffTime > 1) {
SystemClock.sleep(1);
continue;
}
lastTime = startMS;
long d = syncTimer(startMS);
if (d < 0) {
SystemClock.sleep(60 - d);
continue;
}
d = mDanmakuView.drawDanmakus();
if (d > mCordonTime2) { // this situation may be cuased by ui-thread waiting of DanmakuView, so we sync-timer at once
timer.add(d);
mDrawTimes.clear();
}
if (!mDanmakusVisible) {
waitRendering(INDEFINITE_TIME);
} else if (mRenderingState.nothingRendered && mIdleSleep) {
dTime = mRenderingState.endTime - timer.currMillisecond;
if (dTime > 500) {
notifyRendering();
waitRendering(dTime - 10);
}
}
}
You can see that it has entered a circle, where the drawing of the barrage begins, and you can see this line:
d = mDanmakuView.drawDanmakus();
The above code in this line is used to synchronize the time and update the timing, and control up to 16ms a frame. The sliding of the barrage is calculated by time and updated.
Looking at drawDanmakus() you can see that the postInvalidate method is finally executed to redraw the View. So look directly at the onDraw method.
@Override
protected void onDraw(Canvas canvas) {
if ((!mDanmakuVisible) && (!mRequestRender)) {
super.onDraw(canvas);
return;
}
if (mClearFlag) {
DrawHelper.clearCanvas(canvas);
mClearFlag = false;
} else {
if (handler != null) {
RenderingState rs = handler.draw(canvas);
if (mShowFps) {
if (mDrawTimes == null)
mDrawTimes = new LinkedList<Long>();
String fps = String.format(Locale.getDefault(),
"fps %.2f,time:%d s,cache:%d,miss:%d", fps(), getCurrentTime() / 1000,
rs.cacheHitCount, rs.cacheMissCount);
DrawHelper.drawFPS(canvas, fps);
}
}
}
mRequestRender = false;
unlockCanvasAndPost();
}
You can see that handler.draw(canvas) is called, and you can continue to trace DrawTask's drawDanmakus method, looking only at the key code
screenDanmakus = danmakuList.sub(beginMills, endMills);
mRenderer.draw(mDisp, screenDanmakus, mStartRenderTime, renderingState);
The top line intercepts the screen to be displayed, the next line begins to draw, continues to track, to the key method.
// layout
mDanmakusRetainer.fix(drawItem, disp, mVerifier);
Continue tracking
drawItem.layout(disp, drawItem.getLeft(), topPos);
Because we are from the right to the left of the barrage, look at R2LDanmaku's layout method:
@Override
public void layout(IDisplayer displayer, float x, float y) {
if (mTimer != null) {
long currMS = mTimer.currMillisecond;
long deltaDuration = currMS - getActualTime();
if (deltaDuration > 0 && deltaDuration < duration.value) {
this.x = getAccurateLeft(displayer, currMS);
if (!this.isShown()) {
this.y = y;
this.setVisibility(true);
}
mLastTime = currMS;
return;
}
mLastTime = currMS;
}
this.setVisibility(false);
}
First look at the way to get x, that is, the core of the barrage that can move.
protected float getAccurateLeft(IDisplayer displayer, long currTime) {
long elapsedTime = currTime - getActualTime();
if (elapsedTime >= duration.value) {
return -paintWidth;
}
return displayer.getWidth() - elapsedTime * mStepX;
}
getActualTime returns the time that the barrage should be displayed. Now the time subtracted from the barrage is the time that has elapsed. If the time that has elapsed exceeds the duration of the barrage, it means that the barrage has been displayed. Otherwise return
displayer.getWidth() - elapsedTime * mStepX;
It can be seen as screen width - time * speed, and the result is the distance to the left.
Therefore, the whole barrage can be said to be updated according to a timer, and according to the time to calculate the position of the barrage, to achieve the sliding effect of the barrage.
Further research will be carried out in the process of using more questions...