Personal blog portal
1. Categories involved
- GlideDrawableImageViewTarget.java
- GifDrawable.java
- GifFrameLoader.java
- GifDecoder.java
Two, principle overview
Old rules first introduced the principle of the framework, so as not to look at the source code lost
GlideDrawableImageViewTarget
Will call loadGifDrawable
To start the animationGifDrawable
Will be indraw()
Draws the current frame and delegatesGifFrameLoader
Load the next frameGifFrameLoader
Rely onGifDecoder
Loading completes next frame notificationGifDrawable
Refresh the view
GifDrawable is an overwritten Drawable that uses its invalidateSelf() interface to tell it to redraw itself, and in the draw() method to do so, it needs to manage the loop, The GifFrameLoader controls the time interval between loading each frame, and the GifDecoder controls the loading location of the GIF frame
Three, the source details
Let’s look at the code for these two steps
GlideDrawableImageViewTarget
Call-loadedGifDrawable
To start the animationGifDrawable
Will be indraw()
Draws the current frame and delegatesGifFrameLoader
Load the next frame
// GlideDrawableImageViewTarget
public void onResourceReady(GlideDrawable resource, GlideAnimation<? super GlideDrawable> animation) {
if(! resource.isAnimated()) {float viewRatio = view.getWidth() / (float) view.getHeight();
float drawableRatio = resource.getIntrinsicWidth() / (float) resource.getIntrinsicHeight();
if (Math.abs(viewRatio - 1f) <= SQUARE_RATIO_MARGIN
&& Math.abs(drawableRatio - 1f) <= SQUARE_RATIO_MARGIN) {
resource = newSquaringDrawable(resource, view.getWidth()); }}super.onResourceReady(resource, animation);
this.resource = resource;
// The GifDrawable start method is called
resource.setLoopCount(maxLoopCount);
resource.start();
}
// GlideDrawable
public void start(a) {
// State permutations skip without looking
isStarted = true;
resetLoopCount();
if (isVisible) {
// Real enable codestartRunning(); }}private void startRunning(a) {
// GIF is only 1 frame
if (decoder.getFrameCount() == 1) {
// Notify the interface to redraw itself, complete
invalidateSelf();
}
// More than 1 frame
else if(! isRunning) { isRunning =true;
// frameLoader is working
frameLoader.start();
// Tell the interface to redraw itself, i.e. draw the current frame firstinvalidateSelf(); }}Copy the code
At this point, we’ve triggered frameloader.start (), and the first frame is now drawn on the screen due to invalidateSelf(). Let’s look at step 3: loop load the frame and render it to the screen
GifFrameLoader
Rely onGifDecoder
Loading completes next frame notificationGifDrawable
Refresh the view
// GifFrameLoader
public void start(a) {
if (isRunning) {
return;
}
isRunning = true;
isCleared = false;
// This function is called to load the next frame
loadNextFrame();
}
private void loadNextFrame(a) {
if(! isRunning || isLoadPending) {return;
}
isLoadPending = true;
// This line moves the parse position of the gifDecoder to the position of the next frame
gifDecoder.advance();
// Get the delay time for the next frame (GIF has an interval between each frame)
long targetTime = SystemClock.uptimeMillis() + gifDecoder.getNextDelay();
DelayTarget next = new DelayTarget(handler, gifDecoder.getCurrentFrameIndex(), targetTime);
// Start asynchronous loading, that is, do not execute the loader on the main thread
requestBuilder
.signature(new FrameSignature())
.into(next);
}
Copy the code
Directly trigger loadNextFrame() to load the next frame, the actual code is
gifDecoder.advance()
It can be roughly interpreted as jumping to the next frame head positiongifDecoder.getNextDelay()
Gets the interval for the next framerequestBuilder.into()
The next frame is parsed asynchronously, and a callback is called after parsingDelayTarget
Now look at the callback inside DelayTarget
// DelayTarget
public void onResourceReady(Bitmap resource, GlideAnimation<? super Bitmap> glideAnimation) {
this.resource = resource;
// When parsing is complete, resource stores the next frame
Message msg = handler.obtainMessage(FrameLoaderCallback.MSG_DELAY, this);
// MSG_DELAY is the interval between processing frames, and now it is time to cut back to the main thread asynchronously
handler.sendMessageAtTime(msg, targetTime);
}
private class FrameLoaderCallback implements Handler.Callback {
public static final int MSG_DELAY = 1;
public static final int MSG_CLEAR = 2;
@Override
public boolean handleMessage(Message msg) {
if (msg.what == MSG_DELAY) {
GifFrameLoader.DelayTarget target = (DelayTarget) msg.obj;
// Continue tracing
onFrameReady(target);
return true;
} else if (msg.what == MSG_CLEAR) {
GifFrameLoader.DelayTarget target = (DelayTarget) msg.obj;
Glide.clear(target);
}
return false; }}// GifFrameLoader
void onFrameReady(DelayTarget delayTarget) {
if (isCleared) {
handler.obtainMessage(FrameLoaderCallback.MSG_CLEAR, delayTarget).sendToTarget();
return;
}
DelayTarget previous = current;
current = delayTarget;
// Callback is a GlideDrawable that says I loaded the next frame for you
callback.onFrameReady(delayTarget.index);
if(previous ! =null) {
handler.obtainMessage(FrameLoaderCallback.MSG_CLEAR, previous).sendToTarget();
}
isLoadPending = false;
loadNextFrame();
}
Copy the code
DelayTarget.onResourceReady()
Loading the next frame is complete;handler.sendMessageAtTime
Cut back to the main thread and fix the frame interval problemcallback.onFrameReady()
The notification callback, or GlideDrawable, is loaded
Let’s take a look at what GlideDrawable does when it’s notified that it’s loaded
// GlideDrawable
public void onFrameReady(int frameIndex) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB && getCallback() == null) {
stop();
reset();
return;
}
// Refresh yourself, triggering the draw() method
invalidateSelf();
// If there is a full loop, the number of loops is increased by one
if (frameIndex == decoder.getFrameCount() - 1) {
loopCount++;
}
// If you loop enough times, break out of the loop
if (maxLoopCount != LOOP_FOREVER && loopCount >= maxLoopCount) {
stop();
}
}
public void draw(Canvas canvas) {
if (isRecycled) {
return;
}
if (applyGravity) {
Gravity.apply(GifState.GRAVITY, getIntrinsicWidth(), getIntrinsicHeight(), getBounds(), destRect);
applyGravity = false;
}
// Get the current frame from frameLoader.Bitmap currentFrame = frameLoader.getCurrentFrame(); Bitmap toDraw = currentFrame ! =null ? currentFrame : state.firstFrame;
// Draw directly onto the canvas
canvas.drawBitmap(toDraw, null, destRect, paint);
}
Copy the code
- After the next frame is loaded, GlideDrawable fires its own
draw()
Method to start drawing frameLoader.getCurrentFrame()
Take out the next framecanvas.drawBitmap()
Draw directly onto the interface
So far, glide GIF loading implementation has been explained! Thanks for watching