效果圖
需求是根據傳感器做一個重力球效果,先實現了動畫后續加上跟傳感器聯動.
又是擺爛的一天, 尚能呼吸,未來可期啊
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);}@Overrideprotected 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);}@Overridepublic 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));} elsemPaint.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();}@Overrideprotected 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, floatcirAngle) {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, floatcirAngle, 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.CircleGearViewandroid: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>