/*
* Copyright (C) 2013 HalZhang.
*
* http://www.gnu.org/licenses/gpl-3.0.txt
*/
package com.halzhang.android.examples.revolvegesture;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.PointF;
import android.graphics.RectF;
import android.graphics.drawable.Drawable;
import android.os.Parcel;
import android.os.Parcelable;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import android.view.View;
import java.util.ArrayList;
/**
* RevolveGesture
* <p>
* 旋转进度
* </p>
*
* @author <a href="http://weibo.com/halzhang">Hal</a>
* @version Apr 8, 2013
*/
public class RevolveGestureView extends View {
private static final String LOG_TAG = RevolveGestureView.class.getSimpleName();
private Bitmap mBitmap;
private int mMaxWidth = 0;
private float mCenterX = 0;
private float mCenterY = 0;
private int width;
private int height;
private float mDetaDegree;
// 默认起始角度
private float mDefaultDegree = 225f;
/**
* 最大角度,与最小角度,以下两个数值是在正常坐标系的角度
* 需要考虑这是倒立的平面直角坐标系,这样就可以控制旋转的范围在正常的坐标系的[225°,-45°]之间
*/
private float mMaxDegree = 45f;
private float mMinDegree = -225f;
private RectF mOvalRectF = new RectF();
private Paint mMainPaint;
private Paint mFirstPaint;
private int mMaxProgress;
private int mCurrentProgress;
private ArrayList<PointF> mPointFs;
private static final int STEP = 10;
private Drawable mDrawable;
public RevolveGestureView(Context context, AttributeSet attrs) {
super(context, attrs);
// Drawable bgDrawable = getBackground();
// if (bgDrawable != null && bgDrawable instanceof BitmapDrawable) {
// mBitmap = ((BitmapDrawable) bgDrawable).getBitmap();
// }
initSize();
mDetaDegree = -mDefaultDegree;
mMainPaint = new Paint();
mMainPaint.setAntiAlias(true);
mMainPaint.setStyle(Paint.Style.STROKE);
mMainPaint.setColor(0xFFFF0000);
mMainPaint.setStrokeWidth(8);
mFirstPaint = new Paint();
mFirstPaint.setAntiAlias(true);
mFirstPaint.setStyle(Paint.Style.STROKE);
mFirstPaint.setColor(0xFF00FF00);
mFirstPaint.setStrokeWidth(5);
mMaxProgress = (int) (mMaxDegree - mMinDegree);
mCurrentProgress = (int) Math.abs(mMinDegree - mDetaDegree);
}
public void setRevolveDrawableResource(int id) {
mDrawable = getContext().getResources().getDrawable(id);
mDrawable.setCallback(this);
if (mDrawable != null && mDrawable.isStateful()) {
mDrawable.setState(getDrawableState());
}
initSize();
int left = (int) ((mMaxWidth - width) * 0.5);
int top = (int) ((mMaxWidth - height) * 0.5);
mDrawable.setBounds(left, top, width + left, height + top);
postInvalidate();
}
@Override
protected void drawableStateChanged() {
Drawable d = mDrawable;
if (d != null && d.isStateful()) {
int[] state = getDrawableState();
d.setState(state);
}
super.drawableStateChanged();
}
@Override
protected boolean verifyDrawable(Drawable who) {
return who == mDrawable || super.verifyDrawable(who);
}
private void initSize() {
if (mDrawable == null) {
return;
}
width = mDrawable.getIntrinsicWidth();
height = mDrawable.getIntrinsicHeight();
mMaxWidth = (int) Math.sqrt(width * width + height * height);
mCenterX = mCenterY = mMaxWidth * 0.5f;
// 初始化点
int pointCount = mMaxProgress / STEP + 1;
mPointFs = new ArrayList<PointF>(pointCount);
float radius = (width * 0.5f) + 5.0f;
for (int i = (int) mMinDegree; i <= mMaxDegree; i = i + STEP) {
mPointFs.add(getPointF(mCenterX, mCenterY, i, radius));
}
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
setMeasuredDimension(mMaxWidth, mMaxWidth);
float r = (width * 0.5f);
mOvalRectF.top = mCenterY - r;
mOvalRectF.left = mCenterX - r;
mOvalRectF.right = mCenterX + r;
mOvalRectF.bottom = mCenterY + r;
Log.i(LOG_TAG, "Oval RectF: " + mOvalRectF.toString());
}
@Override
public void draw(Canvas canvas) {
super.draw(canvas);
int saveCount = canvas.save();
canvas.rotate(mDetaDegree, mMaxWidth * 0.5f, mMaxWidth * 0.5f);
mDrawable.draw(canvas);
canvas.restoreToCount(saveCount);
}
@Override
protected void onDraw(Canvas canvas) {
Log.i(LOG_TAG, "当前角度: " + mDetaDegree);
// Matrix matrix = new Matrix();
// // 设置转轴位置
// matrix.setTranslate((float) width / 2, (float) height / 2);
// // 开始转,正--逆时针;负--顺时针
// matrix.preRotate(mDetaDegree);
// // 转轴还原
// matrix.preTranslate(-(float) width / 2, -(float) height / 2);
// // 将位置送到view的中心
// matrix.postTranslate((float) (mMaxWidth - width) / 2, (float)
// (mMaxWidth - height) / 2);
// canvas.drawBitmap(mBitmap, matrix, null);
//
// canvas.drawArc(mOvalRectF, mMinDegree, mMaxProgress, false,
// mMainPaint);
// canvas.drawArc(mOvalRectF, mMinDegree, mCurrentProgress, false,
// mFirstPaint);
if (mPointFs != null && mPointFs.size() > 0) {
for (PointF p : mPointFs) {
// Log.i(LOG_TAG, "Point: " + p.toString());
canvas.drawCircle(p.x, p.y, 3, mMainPaint);
}
}
float radius = (width * 0.5f) + 5.0f;
for (int i = (int) mMinDegree; i <= mDetaDegree; i = i + STEP) {
PointF pointF = getPointF(mCenterX, mCenterY, i, radius);
canvas.drawCircle(pointF.x, pointF.y, 2, mFirstPaint);
}
}
private float mCurrentDegree = 0f;
@Override
public boolean onTouchEvent(MotionEvent event) {
if (mBitmap == null) {
}
float lastX;
float lastY;
float currentX;
float currentY;
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN: {
setPressed(true);
lastX = event.getX();
lastY = event.getY();
mCurrentDegree = detaDegree(mCenterX, mCenterY, lastX, lastY);
return true;
}
case MotionEvent.ACTION_MOVE: {
lastX = currentX = event.getX();
lastY = currentY = event.getY();
float degree = detaDegree(mCenterX, mCenterY, currentX, currentY);
// 滑过的弧度增量
float dete = degree - mCurrentDegree;
// 如果小于-90度说明 它跨周了,需要特殊处理350->17,
if (dete < -270) {
dete = dete + 360;
// 如果大于90度说明 它跨周了,需要特殊处理-350->-17,
} else if (dete > 270) {
dete = dete - 360;
}
addDegree(dete);
mCurrentDegree = degree;
mCurrentProgress = (int) Math.abs(mMinDegree - mDetaDegree);
invalidate();
return true;
}
case MotionEvent.ACTION_CANCEL:
setPressed(false);
}
return super.onTouchEvent(event);
}
/**
* 根据原点,角度,和半径,计算坐标
*
* @param originalX 原点x
* @param originalY 原点y
* @param degree 角度
* @param radius 半径
* @return {@link PointF}
*/
private PointF getPointF(float originalX, float originalY, float degree, float radius) {
float x = 0f;
float y = 0f;
// 角度转弧度
float radian = (float) ((degree * Math.PI) / 180);
x = (float) Math.sqrt((radius * radius) / (Math.tan(radian) * Math.tan(radian) + 1));
y = (float) (Math.tan(radian) * x);
float positiveDegree = degree;
if (degree < 0) {
positiveDegree = 360 + degree;
}
if (positiveDegree > 0 && positiveDegree < 90) {
// 第一象限
x = Math.abs(x);
y = Math.abs(y);
} else if (positiveDegree > 90 && positiveDegree < 180) {
// 第二象限
x = -Math.abs(x);
y = Math.abs(y);
} else if (positiveDegree > 180 && positiveDegree < 270) {
// 第三象限
x = -Math.abs(x);
y = -Math.abs(y);
} else if (positiveDegree > 270 && positiveDegree < 360) {
// 第四象限
x = Math.abs(x);
y = -Math.abs(y);
}
Log.i(LOG_TAG, "degree: " + degree + " x: " + x + " y: " + y);
return new PointF(x + originalX, y + originalY);
}
/**
* 计算以(originalX,originalY)为坐标圆点,建立直角体系,求出(targetX,targetY)坐标与x轴的夹角
* 主要是利用反正切函数的知识求出夹角
* <p>
* 第一象限:θ=arctan|b|/|a|, <br>
* 第二象限:θ=180°-arctan|b|/|a|,<br>
* 第三象限:θ=180°+arctan|b|/|a|,<br>
* 第四象限:θ=360°-arctan|b|/|a|,
* </p>
* <p>
* 角度弧度转换:
* </p>
* <p>
* 弧度 角度 <br>
* ---- = ---- <br>
* π 180°
* </p>
*
* @param originalX
* @param originalY
* @param targetX
* @param targetY
* @return
*/
private float detaDegree(float originalX, float originalY, float targetX, float targetY) {
float detaX = targetX - originalX;
float detaY = targetY - originalY;
// 弧度
double d;
// 坐标在四个象限里
if (detaX != 0) {
float tan = Math.abs(detaY / detaX);
if (detaX > 0) {
// 第一象限
if (detaY >= 0) {
d = Math.atan(tan);
} else {
// 第四象限
d = 2 * Math.PI - Math.atan(tan);
}
} else {
if (detaY >= 0) {
// 第二象限
d = Math.PI - Math.atan(tan);
} else {
// 第三象限
d = Math.PI + Math.atan(tan);
}
}
} else {
// 坐标在y轴上
if (detaY > 0) {
// 坐标在y>0上
d = Math.PI / 2;
} else {
// 坐标在y<0上
d = -Math.PI / 2;
}
}
return (float) ((d * 180) / Math.PI);
}
private void addDegree(float degree) {
mDetaDegree += degree;
if (mDetaDegree > 360 || mDetaDegree < -360) {
mDetaDegree = mDetaDegree % 360;
}
if (mDetaDegree > mMaxDegree) {
mDetaDegree = mMaxDegree;
} else if (mDetaDegree < mMinDegree) {
mDetaDegree = mMinDegree;
}
}
static class SavedState extends BaseSavedState {
float detaDegree;
int currentProgress;
/**
* Constructor called from
* {@link RevolveGestureView#onSaveInstanceState()}
*/
SavedState(Parcelable superState) {
super(superState);
}
/**
* Constructor called from {@link #CREATOR}
*/
private SavedState(Parcel in) {
super(in);
detaDegree = in.readFloat();
currentProgress = in.readInt();
}
@Override
public void writeToParcel(Parcel out, int flags) {
super.writeToParcel(out, flags);
out.writeFloat(detaDegree);
out.writeInt(currentProgress);
}
public static final Parcelable.Creator<SavedState> CREATOR = new Parcelable.Creator<SavedState>() {
public SavedState createFromParcel(Parcel in) {
return new SavedState(in);
}
public SavedState[] newArray(int size) {
return new SavedState[size];
}
};
}
@Override
public Parcelable onSaveInstanceState() {
// Force our ancestor class to save its state
Parcelable superState = super.onSaveInstanceState();
SavedState ss = new SavedState(superState);
ss.detaDegree = mDetaDegree;
ss.currentProgress = mCurrentProgress;
return ss;
}
@Override
public void onRestoreInstanceState(Parcelable state) {
SavedState ss = (SavedState) state;
super.onRestoreInstanceState(ss.getSuperState());
mCurrentProgress = ss.currentProgress;
setDetaDegree(ss.detaDegree);
}
private void setDetaDegree(float degree) {
mDetaDegree = degree;
postInvalidate();
}
}