|
import android.content.Context; |
|
import android.content.res.TypedArray; |
|
import android.graphics.Bitmap; |
|
import android.graphics.BitmapShader; |
|
import android.graphics.Camera; |
|
import android.graphics.Canvas; |
|
import android.graphics.Color; |
|
import android.graphics.Matrix; |
|
import android.graphics.Paint; |
|
import android.graphics.Shader; |
|
import android.graphics.drawable.BitmapDrawable; |
|
import android.graphics.drawable.Drawable; |
|
import android.util.AttributeSet; |
|
import android.view.View; |
|
import android.view.animation.Animation; |
|
import android.view.animation.AnimationUtils; |
|
import android.view.animation.DecelerateInterpolator; |
|
import android.view.animation.Interpolator; |
|
import android.view.animation.Transformation; |
|
import android.widget.ImageView; |
|
|
|
public class CircularFlipImageView extends ImageView implements View.OnClickListener, |
|
Animation.AnimationListener { |
|
private int borderWidth; |
|
private int canvasSize; |
|
private Bitmap image; |
|
private Paint paint; |
|
private Paint paintBorder; |
|
|
|
private static final int FLAG_ROTATION_X = 1 << 0; |
|
|
|
private static final int FLAG_ROTATION_Y = 1 << 1; |
|
|
|
private static final int FLAG_ROTATION_Z = 1 << 2; |
|
|
|
private static final Interpolator fDefaultInterpolator = new DecelerateInterpolator(); |
|
|
|
private static int sDefaultDuration; |
|
|
|
private static int sDefaultRotations; |
|
|
|
private static boolean sDefaultAnimated; |
|
|
|
private static boolean sDefaultFlipped; |
|
|
|
private static boolean sDefaultIsRotationReversed; |
|
|
|
|
|
public interface OnFlipListener { |
|
|
|
public void onClick(CircularFlipImageView view); |
|
|
|
public void onFlipStart(CircularFlipImageView view); |
|
|
|
public void onFlipEnd(CircularFlipImageView view); |
|
} |
|
|
|
private OnFlipListener mListener; |
|
|
|
private boolean mIsFlipped; |
|
|
|
private boolean mIsDefaultAnimated; |
|
|
|
private Drawable mDrawable; |
|
|
|
private Drawable mFlippedDrawable; |
|
|
|
private FlipAnimator mAnimation; |
|
|
|
private boolean mIsRotationXEnabled; |
|
|
|
private boolean mIsRotationYEnabled; |
|
|
|
private boolean mIsRotationZEnabled; |
|
|
|
private boolean mIsFlipping; |
|
|
|
private boolean mIsRotationReversed; |
|
|
|
public CircularFlipImageView(final Context context) { |
|
this(context, null); |
|
} |
|
|
|
public CircularFlipImageView(Context context, AttributeSet attrs) { |
|
this(context, attrs, R.attr.circularImageViewStyle); |
|
} |
|
|
|
public CircularFlipImageView(Context context, AttributeSet attrs, int defStyle) { |
|
super(context, attrs, defStyle); |
|
|
|
// init paint |
|
paint = new Paint(); |
|
paint.setAntiAlias(true); |
|
init(context, attrs, defStyle); |
|
|
|
paintBorder = new Paint(); |
|
paintBorder.setAntiAlias(true); |
|
|
|
// load the styled attributes and set their properties |
|
TypedArray attributes = context.obtainStyledAttributes(attrs, R.styleable.CircularFlipImageView, defStyle, 0); |
|
|
|
if(attributes.getBoolean(R.styleable.CircularFlipImageView_border, true)) { |
|
int defaultBorderSize = (int) (4 * getContext().getResources().getDisplayMetrics().density + 0.5f); |
|
setBorderWidth(attributes.getDimensionPixelOffset(R.styleable.CircularFlipImageView_border_width, defaultBorderSize)); |
|
setBorderColor(attributes.getColor(R.styleable.CircularFlipImageView_border_color, Color.WHITE)); |
|
} |
|
|
|
if(attributes.getBoolean(R.styleable.CircularFlipImageView_shadow, false)) |
|
addShadow(); |
|
} |
|
|
|
public void setBorderWidth(int borderWidth) { |
|
this.borderWidth = borderWidth; |
|
this.requestLayout(); |
|
this.invalidate(); |
|
} |
|
|
|
public void setBorderColor(int borderColor) { |
|
if (paintBorder != null) |
|
paintBorder.setColor(borderColor); |
|
this.invalidate(); |
|
} |
|
|
|
public void addShadow() { |
|
setLayerType(LAYER_TYPE_SOFTWARE, paintBorder); |
|
paintBorder.setShadowLayer(4.0f, 0.0f, 2.0f, Color.BLACK); |
|
} |
|
|
|
@Override |
|
public void onDraw(Canvas canvas) { |
|
// load the bitmap |
|
image = drawableToBitmap(getDrawable()); |
|
|
|
// init shader |
|
if (image != null) { |
|
|
|
canvasSize = canvas.getWidth(); |
|
if(canvas.getHeight()<canvasSize) |
|
canvasSize = canvas.getHeight(); |
|
|
|
BitmapShader shader = new BitmapShader(Bitmap.createScaledBitmap(image, canvasSize, canvasSize, false), Shader.TileMode.CLAMP, Shader.TileMode.CLAMP); |
|
paint.setShader(shader); |
|
|
|
// circleCenter is the x or y of the view's center |
|
// radius is the radius in pixels of the cirle to be drawn |
|
// paint contains the shader that will texture the shape |
|
int circleCenter = (canvasSize - (borderWidth * 2)) / 2; |
|
canvas.drawCircle(circleCenter + borderWidth, circleCenter + borderWidth, ((canvasSize - (borderWidth * 2)) / 2) + borderWidth - 4.0f, paintBorder); |
|
canvas.drawCircle(circleCenter + borderWidth, circleCenter + borderWidth, ((canvasSize - (borderWidth * 2)) / 2) - 4.0f, paint); |
|
|
|
//Added to restore image if loaded programmatically or Async |
|
if(!isFlipped()) { |
|
mDrawable = new BitmapDrawable(getContext().getResources(), image); |
|
} |
|
} |
|
} |
|
|
|
@Override |
|
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { |
|
int width = measureWidth(widthMeasureSpec); |
|
int height = measureHeight(heightMeasureSpec); |
|
setMeasuredDimension(width, height); |
|
} |
|
|
|
private int measureWidth(int measureSpec) { |
|
int result = 0; |
|
int specMode = MeasureSpec.getMode(measureSpec); |
|
int specSize = MeasureSpec.getSize(measureSpec); |
|
|
|
if (specMode == MeasureSpec.EXACTLY) { |
|
// The parent has determined an exact size for the child. |
|
result = specSize; |
|
} else if (specMode == MeasureSpec.AT_MOST) { |
|
// The child can be as large as it wants up to the specified size. |
|
result = specSize; |
|
} else { |
|
// The parent has not imposed any constraint on the child. |
|
result = canvasSize; |
|
} |
|
|
|
return result; |
|
} |
|
|
|
private int measureHeight(int measureSpecHeight) { |
|
int result = 0; |
|
int specMode = MeasureSpec.getMode(measureSpecHeight); |
|
int specSize = MeasureSpec.getSize(measureSpecHeight); |
|
|
|
if (specMode == MeasureSpec.EXACTLY) { |
|
// We were told how big to be |
|
result = specSize; |
|
} else if (specMode == MeasureSpec.AT_MOST) { |
|
// The child can be as large as it wants up to the specified size. |
|
result = specSize; |
|
} else { |
|
// Measure the text (beware: ascent is a negative number) |
|
result = canvasSize; |
|
} |
|
|
|
return (result + 2); |
|
} |
|
|
|
public Bitmap drawableToBitmap(Drawable drawable) { |
|
if (drawable == null) { |
|
return null; |
|
} else if (drawable instanceof BitmapDrawable) { |
|
return ((BitmapDrawable) drawable).getBitmap(); |
|
} |
|
|
|
Bitmap bitmap = Bitmap.createBitmap((drawable.getIntrinsicWidth() != -1 ? drawable.getIntrinsicWidth() : this.getWidth()), |
|
(drawable.getIntrinsicHeight() != -1 ? drawable.getIntrinsicHeight() : this.getHeight()), Bitmap.Config.ARGB_8888); |
|
Canvas canvas = new Canvas(bitmap); |
|
drawable.setBounds(0, 0, canvas.getWidth(), canvas.getHeight()); |
|
drawable.draw(canvas); |
|
|
|
return bitmap; |
|
} |
|
|
|
private void init(Context context, AttributeSet attrs, int defStyle) { |
|
sDefaultDuration = context.getResources().getInteger(R.integer.default_civ_duration); |
|
sDefaultRotations = context.getResources().getInteger(R.integer.default_civ_rotations); |
|
sDefaultAnimated = context.getResources().getBoolean(R.bool.default_civ_isAnimated); |
|
sDefaultFlipped = context.getResources().getBoolean(R.bool.default_civ_isFlipped); |
|
sDefaultIsRotationReversed = context.getResources().getBoolean(R.bool.default_civ_isRotationReversed); |
|
|
|
TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.CircularFlipImageView, defStyle, 0); |
|
mIsDefaultAnimated = a.getBoolean(R.styleable.CircularFlipImageView_isAnimated, sDefaultAnimated); |
|
mIsFlipped = a.getBoolean(R.styleable.CircularFlipImageView_isFlipped, sDefaultFlipped); |
|
mFlippedDrawable = a.getDrawable(R.styleable.CircularFlipImageView_flipDrawable); |
|
int duration = a.getInt(R.styleable.CircularFlipImageView_flipDuration, sDefaultDuration); |
|
int interpolatorResId = a.getResourceId(R.styleable.CircularFlipImageView_flipInterpolator, 0); |
|
Interpolator interpolator = interpolatorResId > 0 ? AnimationUtils |
|
.loadInterpolator(context, interpolatorResId) : fDefaultInterpolator; |
|
int rotations = a.getInteger(R.styleable.CircularFlipImageView_flipRotations, sDefaultRotations); |
|
mIsRotationXEnabled = (rotations & FLAG_ROTATION_X) != 0; |
|
mIsRotationYEnabled = (rotations & FLAG_ROTATION_Y) != 0; |
|
mIsRotationZEnabled = (rotations & FLAG_ROTATION_Z) != 0; |
|
|
|
mDrawable = getDrawable(); |
|
mIsRotationReversed = a.getBoolean(R.styleable.CircularFlipImageView_reverseRotation, sDefaultIsRotationReversed); |
|
|
|
mAnimation = new FlipAnimator(); |
|
mAnimation.setAnimationListener(this); |
|
mAnimation.setInterpolator(interpolator); |
|
mAnimation.setDuration(duration); |
|
|
|
setOnClickListener(this); |
|
|
|
setImageDrawable(mIsFlipped ? mFlippedDrawable : mDrawable); |
|
mIsFlipping = false; |
|
|
|
a.recycle(); |
|
} |
|
|
|
public void setFlippedDrawable(Drawable flippedDrawable){ |
|
mFlippedDrawable = flippedDrawable; |
|
if(mIsFlipped) setImageDrawable(mFlippedDrawable); |
|
} |
|
|
|
public void setDrawable(Drawable drawable){ |
|
mDrawable = drawable; |
|
if(!mIsFlipped) setImageDrawable(mDrawable); |
|
} |
|
|
|
public boolean isRotationXEnabled() { |
|
return mIsRotationXEnabled; |
|
} |
|
|
|
public void setRotationXEnabled(boolean enabled) { |
|
mIsRotationXEnabled = enabled; |
|
} |
|
|
|
public boolean isRotationYEnabled() { |
|
return mIsRotationYEnabled; |
|
} |
|
|
|
public void setRotationYEnabled(boolean enabled) { |
|
mIsRotationYEnabled = enabled; |
|
} |
|
|
|
public boolean isRotationZEnabled() { |
|
return mIsRotationZEnabled; |
|
} |
|
|
|
public void setRotationZEnabled(boolean enabled) { |
|
mIsRotationZEnabled = enabled; |
|
} |
|
|
|
public FlipAnimator getFlipAnimation() { |
|
return mAnimation; |
|
} |
|
|
|
public void setInterpolator(Interpolator interpolator) { |
|
mAnimation.setInterpolator(interpolator); |
|
} |
|
|
|
public void setDuration(int duration) { |
|
mAnimation.setDuration(duration); |
|
} |
|
|
|
public boolean isFlipped() { |
|
return mIsFlipped; |
|
} |
|
|
|
public boolean isFlipping() { |
|
return mIsFlipping; |
|
} |
|
|
|
public boolean isRotationReversed(){ |
|
return mIsRotationReversed; |
|
} |
|
|
|
public void setRotationReversed(boolean rotationReversed){ |
|
mIsRotationReversed = rotationReversed; |
|
} |
|
|
|
public boolean isAnimated() { |
|
return mIsDefaultAnimated; |
|
} |
|
|
|
public void setAnimated(boolean animated) { |
|
mIsDefaultAnimated = animated; |
|
} |
|
|
|
public void setFlipped(boolean flipped) { |
|
setFlipped(flipped, mIsDefaultAnimated); |
|
} |
|
|
|
public void setFlipped(boolean flipped, boolean animated) { |
|
if (flipped != mIsFlipped) { |
|
toggleFlip(animated); |
|
} |
|
} |
|
|
|
public void toggleFlip() { |
|
toggleFlip(mIsDefaultAnimated); |
|
} |
|
|
|
public void toggleFlip(boolean animated) { |
|
if (animated) { |
|
mAnimation.setToDrawable(mIsFlipped ? mDrawable : mFlippedDrawable); |
|
startAnimation(mAnimation); |
|
} else { |
|
setImageDrawable(mIsFlipped ? mDrawable : mFlippedDrawable); |
|
} |
|
mIsFlipped = !mIsFlipped; |
|
} |
|
|
|
|
|
public void setOnFlipListener(OnFlipListener listener) { |
|
mListener = listener; |
|
} |
|
|
|
|
|
@Override |
|
public void onClick(View v) { |
|
toggleFlip(); |
|
if (mListener != null) { |
|
mListener.onClick(this); |
|
} |
|
} |
|
|
|
@Override |
|
public void onAnimationStart(Animation animation) { |
|
if (mListener != null) { |
|
mListener.onFlipStart(this); |
|
} |
|
mIsFlipping = true; |
|
} |
|
|
|
@Override |
|
public void onAnimationEnd(Animation animation) { |
|
if (mListener != null) { |
|
mListener.onFlipEnd(this); |
|
} |
|
mIsFlipping = false; |
|
} |
|
|
|
@Override |
|
public void onAnimationRepeat(Animation animation) { |
|
} |
|
|
|
/** |
|
* Animation part All credits goes to coomar |
|
*/ |
|
public class FlipAnimator extends Animation { |
|
|
|
private Camera camera; |
|
|
|
private Drawable toDrawable; |
|
|
|
private float centerX; |
|
|
|
private float centerY; |
|
|
|
private boolean visibilitySwapped; |
|
|
|
public void setToDrawable(Drawable to) { |
|
toDrawable = to; |
|
visibilitySwapped = false; |
|
} |
|
|
|
public FlipAnimator() { |
|
setFillAfter(true); |
|
} |
|
|
|
|
|
@Override |
|
public void initialize(int width, int height, int parentWidth, int parentHeight) { |
|
super.initialize(width, height, parentWidth, parentHeight); |
|
camera = new Camera(); |
|
this.centerX = width / 2; |
|
this.centerY = height / 2; |
|
} |
|
|
|
@Override |
|
protected void applyTransformation(float interpolatedTime, Transformation t) { |
|
// Angle around the y-axis of the rotation at the given time. It is |
|
// calculated both in radians and in the equivalent degrees. |
|
final double radians = Math.PI * interpolatedTime; |
|
float degrees = (float) (180.0 * radians / Math.PI); |
|
|
|
if(mIsRotationReversed){ |
|
degrees = -degrees; |
|
} |
|
|
|
// Once we reach the midpoint in the animation, we need to hide the |
|
// source view and show the destination view. We also need to change |
|
// the angle by 180 degrees so that the destination does not come in |
|
// flipped around. This is the main problem with SDK sample, it does not |
|
// do this. |
|
if (interpolatedTime >= 0.5f) { |
|
if(mIsRotationReversed){ degrees += 180.f; } else{ degrees -= 180.f; } |
|
|
|
if (!visibilitySwapped) { |
|
setImageDrawable(toDrawable); |
|
visibilitySwapped = true; |
|
} |
|
} |
|
|
|
final Matrix matrix = t.getMatrix(); |
|
|
|
camera.save(); |
|
camera.translate(0.0f, 0.0f, (float) (150.0 * Math.sin(radians))); |
|
camera.rotateX(mIsRotationXEnabled ? degrees : 0); |
|
camera.rotateY(mIsRotationYEnabled ? degrees : 0); |
|
camera.rotateZ(mIsRotationZEnabled ? degrees : 0); |
|
camera.getMatrix(matrix); |
|
camera.restore(); |
|
|
|
matrix.preTranslate(-centerX, -centerY); |
|
matrix.postTranslate(centerX, centerY); |
|
} |
|
} |
|
} |