效果图
需求是根据传感器做一个重力球效果,先实现了动画后续加上跟传感器联动.
又是摆烂的一天, 尚能呼吸,未来可期啊
View源码
package com.android.circlescalebar.view;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.PointF;
import android.util.AttributeSet;
import android.view.View;
import com.android.circlescalebar.R;
import com.android.circlescalebar.utils.ChartUtils;
import com.android.circlescalebar.utils.DensityUtils;
public class CircleGearView extends View {
private Context mContext;
private Paint mPaint; // 画笔对象的引用
private PointF mProgressPoint;
private float mRoundWidth = DensityUtils.dp2px(4); // 圆环的宽度
private int centerX, centerY;
private int radius, roundRadius;
private int paddingOuterThumb;//外边距
private int minValidateTouchArcRadius; // 最小有效点击半径
private int maxValidateTouchArcRadius; // 最大有效点击半径
private int mMainColor; //主题颜色
private int mInnerRoundColor; //内圆 宽度 、颜色
private float mInnerRoundWidth;
private int mTxtProgress = 1; // 显示进度
private int max = 200; // 最大进度 -- 总共200个刻度 所以这样定义
private float progress = 1;
private double mOuterRoundProgress = 0f;//外圈进度
private boolean mOuterSences = true; //true 正向----false方向
public CircleGearView(Context context) {
this(context, null);
}
public CircleGearView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public CircleGearView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
mContext = context;
initView(attrs);
}
private void initView(AttributeSet attrs){
setLayerType(View.LAYER_TYPE_SOFTWARE, null); // 关闭硬件加速
this.setWillNotDraw(false); // 调用此方法后,才会执行 onDraw(Canvas) 方法
mPaint = new Paint();
//获取自定义属性和默认值
TypedArray typedArray = mContext.obtainStyledAttributes(attrs, R.styleable.CGViewStyleable);
mRoundWidth = typedArray.getDimension(R.styleable.CGViewStyleable_round_width, DensityUtils.dp2px(7));
mMainColor = typedArray.getColor(R.styleable.CGViewStyleable_round_color, getResources().getColor(R.color.green));
mInnerRoundWidth = typedArray.getDimension(R.styleable.CGViewStyleable_inner_round_width, DensityUtils.dp2px(2));
mInnerRoundColor = typedArray.getColor(R.styleable.CGViewStyleable_inner_round_color, getResources().getColor(R.color.white33));
paddingOuterThumb = DensityUtils.dp2px(20);
}
@Override
protected void onSizeChanged(int width, int height, int oldw, int oldh) {
centerX = width / 2;
centerY = height / 2;
int minCenter = Math.min(centerX, centerY);
radius = (int) (minCenter - mRoundWidth / 2 - paddingOuterThumb); //圆环的半径
roundRadius = radius - (int)(3 * mRoundWidth);
minValidateTouchArcRadius = (int) (radius - paddingOuterThumb * 1.5f);
maxValidateTouchArcRadius = (int) (radius + paddingOuterThumb * 1.5f);
super.onSizeChanged(width, height, oldw, oldh);
}
@Override
public void onDraw(Canvas canvas) {
// setLayerType(LAYER_TYPE_SOFTWARE, null);//对单独的View在运行时阶段禁用硬件加速
initOnDraw(canvas);
}
/** start circle -*/
private void initOnDraw(Canvas canvas) {
/** 画刻度-200份- 还分正反切换---start */
mPaint.setStrokeWidth(DensityUtils.dp2px(1));
for (int i = 0; i < 200; i++){
//radius:模糊半径,radius越大越模糊,越小越清晰,但是如果radius设置为0,则阴影消失不见
//dx:阴影的横向偏移距离,正值向右偏移,负值向左偏移
//dy:阴影的纵向偏移距离,正值向下偏移,负值向上偏移
//color: 绘制阴影的画笔颜色,即阴影的颜色(对图片阴影无效)
if (i < mOuterRoundProgress) {
if (mOuterSences) {
// mPaint.setShadowLayer(30, 0, 0, mMainColor);
mPaint.setColor(getResources().getColor(R.color.green));
} else
mPaint.setColor(getResources().getColor(R.color.white33));
} else {
if (mOuterSences)
mPaint.setColor(getResources().getColor(R.color.white33));
else {
// mPaint.setShadowLayer(30, 0, 0, mMainColor);
mPaint.setColor(getResources().getColor(R.color.green));
}
}
float mProgress = (i)* 1.0f/ 200 * max;
PointF mProgressPoint = ChartUtils.calcArcEndPointXY(centerX, centerY, radius, 360 * mProgress / max, 90);
//圆上到圆心
float scale1 = radius * 1.0F / mRoundWidth;
float scale2 = radius * 1.0F / (radius - mRoundWidth);
//计算内圆上的点
float disX = (scale1*mProgressPoint.x + scale2*centerX)/(scale1+ scale2);
float disY = (scale1*mProgressPoint.y + scale2*centerY)/(scale1+ scale2);
//计算外圆上的点
float disX2 = mProgressPoint.x*2 - disX;
float disY2 = mProgressPoint.y*2 - disY;
// if (mProgress%6 == 0){
// //直线3/4高度
// canvas.drawLine(disX2 ,disY2,disX,disY, mPaint);
// }else{
//直线1/2高度
float disX3 = (disX*1 + disX2)/2;
float disY3 = (disY*1 + disY2)/2;
canvas.drawLine(disX3 ,disY3,disX,disY, mPaint);
// }
}
/** 画刻度-200份- 还分正反切换---end */
// 移动圆点
mProgressPoint = ChartUtils.calcArcEndPointXY(centerX, centerY, radius - 55, 360 *
progress / max, (float)90);
// //直接用画笔画
mPaint.setColor(getResources().getColor(R.color.green)); //设置进度的颜色
// 设置渐变
// Shader shader = new RadialGradient(
// 0, 0, 50, // 圆的中心坐标和半径
// mMainColor, mInnerRoundColor, // 渐变的起止颜色
// Shader.TileMode.CLAMP // 渐变模式
// );
// mPaint.setShader(shader);
canvas.drawCircle(mProgressPoint.x, mProgressPoint.y,30 ,mPaint);
canvas.restore();
canvas.save();
}
@Override
protected void onDetachedFromWindow() {
super.onDetachedFromWindow();
}
/**
* 设置进度,此为线程安全控件,由于考虑多线的问题,需要同步
* 刷新界面调用postInvalidate()能在非UI线程刷新
*
* @param progress
*/
public synchronized void setProgress(float progress) {
if (progress < 0) {
mTxtProgress = 1;
progress = 0;
}
mTxtProgress = Math.round(progress);
float ss = progress * 200 / 100;
progress = (int) ss;
if (progress < 0) {
throw new IllegalArgumentException("progress not less than 0");
}
if (progress > max) {
progress = max;
mOuterRoundProgress = progress + 1;
}
if (progress <= max) {
this.progress = progress;
mOuterRoundProgress = progress + 1;
postInvalidate();
}
}
}
工具类
package com.android.circlescalebar.utils;
import android.graphics.PointF;
public class ChartUtils {
/**
* 依圆心坐标,半径,扇形角度,计算出扇形终射线与圆弧交叉点的xy坐标
*
* @param cirX 圆centerX
* @param cirY 圆centerY
* @param radius 圆半径
* @param cirAngle 当前弧角度
* @return 扇形终射线与圆弧交叉点的xy坐标
*/
public static PointF calcArcEndPointXY(float cirX, float cirY, float radius, float
cirAngle) {
float posX = 0.0f;
float posY = 0.0f;
//将角度转换为弧度
float arcAngle = (float) (Math.PI * cirAngle / 180.0);
if (cirAngle < 90) {
posX = cirX + (float) (Math.cos(arcAngle)) * radius;
posY = cirY + (float) (Math.sin(arcAngle)) * radius;
} else if (cirAngle == 90) {
posX = cirX;
posY = cirY + radius;
} else if (cirAngle > 90 && cirAngle < 180) {
arcAngle = (float) (Math.PI * (180 - cirAngle) / 180.0);
posX = cirX - (float) (Math.cos(arcAngle)) * radius;
posY = cirY + (float) (Math.sin(arcAngle)) * radius;
} else if (cirAngle == 180) {
posX = cirX - radius;
posY = cirY;
} else if (cirAngle > 180 && cirAngle < 270) {
arcAngle = (float) (Math.PI * (cirAngle - 180) / 180.0);
posX = cirX - (float) (Math.cos(arcAngle)) * radius;
posY = cirY - (float) (Math.sin(arcAngle)) * radius;
} else if (cirAngle == 270) {
posX = cirX;
posY = cirY - radius;
} else {
arcAngle = (float) (Math.PI * (360 - cirAngle) / 180.0);
posX = cirX + (float) (Math.cos(arcAngle)) * radius;
posY = cirY - (float) (Math.sin(arcAngle)) * radius;
}
return new PointF(posX, posY);
}
/**
* 依圆心坐标,半径,扇形角度,计算出扇形终射线与圆弧交叉点的xy坐标
*
* @param cirX 圆centerX
* @param cirY 圆centerY
* @param radius 圆半径
* @param cirAngle 当前弧角度
* @param orginAngle 起点弧角度
* @return 扇形终射线与圆弧交叉点的xy坐标
*/
public static PointF calcArcEndPointXY(float cirX, float cirY, float radius, float
cirAngle, float orginAngle) {
cirAngle = (orginAngle + cirAngle) % 360;
return calcArcEndPointXY(cirX, cirY, radius, cirAngle);
}
}
package com.android.circlescalebar.utils;
import android.content.res.Resources;
public class DensityUtils {
public float density;
public DensityUtils() {
density = Resources.getSystem().getDisplayMetrics().density;
}
/**
* 根据手机的分辨率从 dp 的单位 转成为 px(像素)
* @param dpValue 虚拟像素
* @return 像素
*/
public static int dp2px(float dpValue) {
return (int) (0.5f + dpValue * Resources.getSystem().getDisplayMetrics().density);
}
/**
* 根据手机的分辨率从 px(像素) 的单位 转成为 dp
* @param pxValue 像素
* @return 虚拟像素
*/
public static float px2dp(int pxValue) {
return (pxValue / Resources.getSystem().getDisplayMetrics().density);
}
/**
* 根据手机的分辨率从 dp 的单位 转成为 px(像素)
* @param dpValue 虚拟像素
* @return 像素
*/
public int dip2px(float dpValue) {
return (int) (0.5f + dpValue * density);
}
/**
* 根据手机的分辨率从 px(像素) 的单位 转成为 dp
* @param pxValue 像素
* @return 虚拟像素
*/
public float px2dip(int pxValue) {
return (pxValue / density);
}
}
调用实现
private int count = 0;
private Handler handler = new Handler();
private Runnable updateTextRunnable;
private CircleGearView circleGearView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
circleGearView = findViewById(R.id.circleGearView);
updateTextRunnable = new Runnable() {
@Override
public void run() {
circleGearView.setProgress(count);
count++;
if (count > 100) {
// 停止循环
handler.removeCallbacks(this);
} else {
// 继续循环
handler.postDelayed(this, 1000); // 每秒更新一次
}
}
};
// 开始循环
handler.post(updateTextRunnable);
}
@Override
protected void onDestroy() {
super.onDestroy();
// 确保在Activity销毁时移除所有回调和消息,防止内存泄漏
handler.removeCallbacks(updateTextRunnable);
}
布局
<com.android.circlescalebar.view.CircleGearView
android:id="@+id/circleGearView"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:inner_round_color="@color/white33"
app:inner_round_width="2dp"
app:round_color="@color/green"
app:round_width="7dp" />
attrs
<declare-styleable name="CGViewStyleable">
<!-- 圆的宽度 -->
<attr name="round_width" format="dimension"/>
<attr name="round_color" format="color"/>
<attr name="inner_round_width" format="dimension"/>
<attr name="inner_round_color" format="color"/>
</declare-styleable>