/*
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/
package com.facebook.drawee.drawable;
import android.graphics.Canvas;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.os.SystemClock;
import com.facebook.common.internal.Preconditions;
import com.facebook.common.internal.VisibleForTesting;
/**
* Drawable that automatically rotates underlying drawable.
*/
public class AutoRotateDrawable extends ForwardingDrawable implements Runnable {
private static final int DEGREES_IN_FULL_ROTATION = 360;
private static final int FRAME_INTERVAL_MS = 20;
// Specified duration in milliseconds for one complete rotation.
private int mInterval;
// Specified rotation direction
private boolean mClockwise;
// Current angle by which the drawable is rotated.
@VisibleForTesting
float mRotationAngle = 0;
// Whether we have our next frame scheduled for update
private boolean mIsScheduled = false;
/**
* Creates a new AutoRotateDrawable with given underlying drawable, interval and a clockwise
* rotation.
*
* @param drawable underlying drawable to apply the rotation to
* @param interval duration in milliseconds of one complete rotation
*/
public AutoRotateDrawable(Drawable drawable, int interval) {
this(drawable, interval, true);
}
/**
* Creates a new AutoRotateDrawable with given underlying drawable and interval.
*
* @param drawable underlying drawable to apply the rotation to
* @param interval duration in milliseconds of one complete rotation
* @param clockwise defines whether the rotation is clockwise or not
*/
public AutoRotateDrawable(Drawable drawable, int interval, boolean clockwise) {
super(Preconditions.checkNotNull(drawable));
mInterval = interval;
mClockwise = clockwise;
}
/**
* Resets to the initial state.
*/
public void reset() {
mRotationAngle = 0;
mIsScheduled = false;
unscheduleSelf(this);
invalidateSelf();
}
/**
* Define whether the rotation is clockwise or not.
* By default is the rotation clockwise.
*/
public void setClockwise(boolean clockwise) {
mClockwise = clockwise;
}
@Override
public void draw(Canvas canvas) {
int saveCount = canvas.save();
Rect bounds = getBounds();
int width = bounds.right - bounds.left;
int height = bounds.bottom - bounds.top;
float angle = mRotationAngle;
if (!mClockwise) {
angle = DEGREES_IN_FULL_ROTATION - mRotationAngle;
}
canvas.rotate(angle, bounds.left + width / 2, bounds.top + height / 2);
super.draw(canvas);
canvas.restoreToCount(saveCount);
scheduleNextFrame();
}
@Override
public void run() {
mIsScheduled = false;
mRotationAngle += getIncrement();
invalidateSelf();
}
/**
* Schedule the next frame for drawing.
*
* Ideally, we'd like to call this from the callback (i.e. {@code
* run()}), but if we do there's no place where we can call
* scheduleNextFrame() for the first time. As a tradeoff, we call
* this from draw(), which means scheduleNextFrame() could
* technically be called multiple times for the same frame, so we
* must handle that gracefully.
*/
private void scheduleNextFrame() {
if (!mIsScheduled) {
mIsScheduled = true;
scheduleSelf(this, SystemClock.uptimeMillis() + FRAME_INTERVAL_MS);
}
}
private int getIncrement() {
return (int) (((float)FRAME_INTERVAL_MS) / mInterval * DEGREES_IN_FULL_ROTATION);
}
}