/*
* Copyright (c) 2016 Zhang Hai <Dreaming.in.Code.ZH@Gmail.com>
* All Rights Reserved.
*/
package me.zhanghai.android.douya.ui;
import android.content.res.Resources;
import android.graphics.Canvas;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.support.annotation.NonNull;
import me.zhanghai.android.douya.util.MathUtils;
public class RotateDrawableCompat extends DrawableWrapperCompat {
private static final int MAX_LEVEL = 10000;
private RotateState mState;
public RotateDrawableCompat(Drawable drawable) {
super(drawable);
}
@Override
public void draw(@NonNull Canvas canvas) {
Drawable drawable = getDrawable();
if (drawable == null) {
return;
}
Rect bounds = drawable.getBounds();
int width = bounds.right - bounds.left;
int height = bounds.bottom - bounds.top;
float pivotX = mState.mIsPivotXRelative ? (width * mState.mPivotX) : mState.mPivotX;
float pivotY = mState.mIsPivotYRelative ? (height * mState.mPivotY) : mState.mPivotY;
int saveCount = canvas.save();
canvas.rotate(mState.mCurrentDegrees, bounds.left + pivotX, bounds.top + pivotY);
drawable.draw(canvas);
canvas.restoreToCount(saveCount);
}
/**
* Sets the start angle for rotation.
*
* @param fromDegrees starting angle in degrees
* @see #getFromDegrees()
*/
public void setFromDegrees(float fromDegrees) {
if (mState.mFromDegrees == fromDegrees) {
return;
}
mState.mFromDegrees = fromDegrees;
invalidateSelf();
}
/**
* @return starting angle for rotation in degrees
* @see #setFromDegrees(float)
*/
public float getFromDegrees() {
return mState.mFromDegrees;
}
/**
* Sets the end angle for rotation.
*
* @param toDegrees ending angle in degrees
* @see #getToDegrees()
*/
public void setToDegrees(float toDegrees) {
if (mState.mToDegrees == toDegrees) {
return;
}
mState.mToDegrees = toDegrees;
invalidateSelf();
}
/**
* @return ending angle for rotation in degrees
* @see #setToDegrees(float)
*/
public float getToDegrees() {
return mState.mToDegrees;
}
public void setDegrees(float degrees) {
mState.mFromDegrees = mState.mToDegrees = mState.mCurrentDegrees = degrees;
}
/**
* Sets the X position around which the drawable is rotated.
* <p>
* If the X pivot is relative (as specified by
* {@link #setPivotXRelative(boolean)}), then the position represents a
* fraction of the drawable width. Otherwise, the position represents an
* absolute value in pixels.
*
* @param pivotX X position around which to rotate
* @see #setPivotXRelative(boolean)
*/
public void setPivotX(float pivotX) {
if (mState.mPivotX == pivotX) {
return;
}
mState.mPivotX = pivotX;
invalidateSelf();
}
/**
* @return X position around which to rotate
* @see #setPivotX(float)
*/
public float getPivotX() {
return mState.mPivotX;
}
/**
* Sets whether the X pivot value represents a fraction of the drawable
* width or an absolute value in pixels.
*
* @param relative true if the X pivot represents a fraction of the drawable
* width, or false if it represents an absolute value in pixels
* @see #isPivotXRelative()
*/
public void setPivotXRelative(boolean relative) {
if (mState.mIsPivotXRelative == relative) {
return;
}
mState.mIsPivotXRelative = relative;
invalidateSelf();
}
/**
* @return true if the X pivot represents a fraction of the drawable width,
* or false if it represents an absolute value in pixels
* @see #setPivotXRelative(boolean)
*/
public boolean isPivotXRelative() {
return mState.mIsPivotXRelative;
}
/**
* Sets the Y position around which the drawable is rotated.
* <p>
* If the Y pivot is relative (as specified by
* {@link #setPivotYRelative(boolean)}), then the position represents a
* fraction of the drawable height. Otherwise, the position represents an
* absolute value in pixels.
*
* @param pivotY Y position around which to rotate
* @see #getPivotY()
*/
public void setPivotY(float pivotY) {
if (mState.mPivotY == pivotY) {
return;
}
mState.mPivotY = pivotY;
invalidateSelf();
}
/**
* @return Y position around which to rotate
* @see #setPivotY(float)
*/
public float getPivotY() {
return mState.mPivotY;
}
/**
* Sets whether the Y pivot value represents a fraction of the drawable
* height or an absolute value in pixels.
*
* @param relative True if the Y pivot represents a fraction of the drawable
* height, or false if it represents an absolute value in pixels
* @see #isPivotYRelative()
*/
public void setPivotYRelative(boolean relative) {
if (mState.mIsPivotYRelative == relative) {
return;
}
mState.mIsPivotYRelative = relative;
invalidateSelf();
}
/**
* @return true if the Y pivot represents a fraction of the drawable height,
* or false if it represents an absolute value in pixels
* @see #setPivotYRelative(boolean)
*/
public boolean isPivotYRelative() {
return mState.mIsPivotYRelative;
}
@Override
protected boolean onLevelChange(int level) {
super.onLevelChange(level);
float value = level / (float) MAX_LEVEL;
mState.mCurrentDegrees = MathUtils.lerp(mState.mFromDegrees, mState.mToDegrees, value);
invalidateSelf();
return true;
}
@Override
protected RotateState mutateConstantState() {
mState = new RotateState(mState, null);
return mState;
}
private static final class RotateState extends DrawableWrapperState {
boolean mIsPivotXRelative = true;
float mPivotX = 0.5f;
boolean mIsPivotYRelative = true;
float mPivotY = 0.5f;
float mFromDegrees = 0.0f;
float mToDegrees = 360.0f;
float mCurrentDegrees = 0.0f;
RotateState(RotateState orig, Resources res) {
super(orig, res);
if (orig != null) {
mIsPivotXRelative = orig.mIsPivotXRelative;
mPivotX = orig.mPivotX;
mIsPivotYRelative = orig.mIsPivotYRelative;
mPivotY = orig.mPivotY;
mFromDegrees = orig.mFromDegrees;
mToDegrees = orig.mToDegrees;
mCurrentDegrees = orig.mCurrentDegrees;
}
}
@NonNull
@Override
public Drawable newDrawable(Resources res) {
return new RotateDrawableCompat(this, res);
}
}
private RotateDrawableCompat(RotateState state, Resources res) {
super(state, res);
mState = state;
}
}