当前位置:   article > 正文

基于ArcSeekBar(圆弧拖动条)的修改_android arcseekbar 虚弧线进度条

android arcseekbar 虚弧线进度条

原作者地址:ArcSeekBar(圆弧拖动条)

原作者的效果是这样的。

我的需求是 移动滑块,前景色要变动。

我看到作者的GitHub上,也有人提这样的需求。

于是我自己在原作者的基础上,进行了二次修改

先放效果图:

前景色渐变 + 跟着滑块一起动。

背景色渐变 + 静止

废话不多说,直接上代码。

ArcSeekBar.java
  1. /**
  2. * 作用: 圆弧形 SeekBar
  3. * 作者: GcsSloop
  4. * 摘要: 该 SeekBar 实现的核心原理非常简单,其绘制内容实际上为一个圆弧,只用了一条 Path,
  5. * 尽管核心原理简单,但仍有以下细节需要进行注意:
  6. * 1. 画布旋转导致的坐标系问题.
  7. * - 为了让开口方向可控,并且实现起来简单,进行了画布旋转,因此实际显示的坐标系下的坐标和绘制坐标系坐标是不同的,尤其需要注意.
  8. * 2.当前进度确定问题
  9. * - 进度是由角度确定的, 先根据当前点击位置和中心点连线与水平线的夹角表示当前角度,
  10. * - 根据该角度和实际圆弧的角度相关信息推算出当前进度百分比.
  11. * - 根据百分比和最大数值确定当前实际的进度.
  12. * 3. 拖动与点击
  13. * - 为了防止误触, 只有手指按下位置在拖动按钮附近时才可以执行拖动.
  14. * - 但是用户单击时,可以可以直接跳转进度, 当然,只有点击在圆弧进度条的区域内才允许设置新的进度.
  15. * - 如何判断是否点击在圆弧区域内,使用了 Region 的相关方法,该 Region 区域是从 Paint 的 getFillPath 得到的.
  16. * 4. 进度回调去重
  17. * - 用户拖动时,判断是否和上次进度相同,如果相同,则不发送回调.
  18. * 5. 防止突变
  19. * - 由于进度条时圆弧形状的,因此进度可能会从 0.0 直接突变到 1.0 或者相反,因此在计算进度与当前进度差异过大时,禁止改变当前进度.
  20. */
  21. public class ArcSeekBar extends View {
  22. private static final int DEFAULT_EDGE_LENGTH = 250; // 默认宽高
  23. private static final float CIRCLE_ANGLE = 360; // 圆周角
  24. private static final int DEFAULT_ARC_WIDTH = 40; // 默认宽度 dp
  25. private static final float DEFAULT_OPEN_ANGLE = 120; // 开口角度
  26. private static final float DEFAULT_ROTATE_ANGLE = 90; // 旋转角度
  27. private static final int DEFAULT_BORDER_WIDTH = 0; // 默认描边宽度
  28. private static final int DEFAULT_BORDER_COLOR = 0xffffffff; // 默认描边颜色
  29. private static final int DEFAULT_THUMB_COLOR = 0xffffffff; // 拖动按钮颜色
  30. private static final int DEFAULT_THUMB_WIDTH = 2; // 拖动按钮描边宽度 dp
  31. private static final int DEFAULT_THUMB_RADIUS = 15; // 拖动按钮半径 dp
  32. private static final int DEFAULT_THUMB_SHADOW_RADIUS = 0; // 拖动按钮阴影半径 dp
  33. private static final int DEFAULT_THUMB_SHADOW_COLOR = 0xFF000000; // 拖动按钮阴影颜色
  34. private static final int DEFAULT_SHADOW_RADIUS = 0; // 默认阴影半径 dp
  35. private static final int THUMB_MODE_STROKE = 0; // 拖动按钮模式 - 描边
  36. private static final int THUMB_MODE_FILL = 1; // 拖动按钮模式 - 填充
  37. private static final int THUMB_MODE_FILL_STROKE = 2; // 拖动按钮模式 - 填充+描边
  38. private static final int DEFAULT_MAX_VALUE = 100; // 默认最大数值
  39. private static final int DEFAULT_MIN_VALUE = 0; // 默认最小数值
  40. private static final String KEY_PROGRESS_PRESENT = "PRESENT"; // 用于存储和获取当前百分比
  41. // 可配置数据
  42. private int[] mArcColors; // Seek 颜色
  43. private int[] mProgressColors; // progress的颜色
  44. private float mArcWidth; // Seek 宽度
  45. private float mOpenAngle; // 开口的角度大小 0 - 360
  46. private float mRotateAngle; // 旋转角度
  47. private int mBorderWidth; // 描边宽度
  48. private int mBorderColor; // 描边颜色
  49. private int mThumbColor; // 拖动按钮颜色
  50. private float mThumbWidth; // 拖动按钮宽度
  51. private float mThumbRadius; // 拖动按钮半径
  52. private float mThumbShadowRadius;// 拖动按钮阴影半径
  53. private int mThumbShadowColor;// 拖动按钮阴影颜色
  54. private int mThumbMode; // 拖动按钮模式
  55. private int mShadowRadius; // 阴影半径
  56. private int mMaxValue; // 最大数值
  57. private int mMinValue; // 最小数值
  58. private int progress; // 进度值
  59. private float mCenterX; // 圆弧 SeekBar 中心点 X
  60. private float mCenterY; // 圆弧 SeekBar 中心点 Y
  61. private float mThumbX; // 拖动按钮 中心点 X
  62. private float mThumbY; // 拖动按钮 中心点 Y
  63. private int mOffsetX; //偏移量 X
  64. private int mOffsetY; //偏移量 Y
  65. private Path mSeekPath; // seekBar 绘制路径
  66. private Path mProgressPath; // seekBar progress 绘制路径
  67. private Path mBorderPath;
  68. private Paint mArcPaint;
  69. private Paint mProgressPaint; // seekBar progress 画笔
  70. private Paint mThumbPaint;
  71. private Paint mBorderPaint;
  72. private Paint mShadowPaint;
  73. private RectF content;
  74. private float[] mTempPos;
  75. private float[] mTempTan;
  76. private PathMeasure mSeekPathMeasure;
  77. private PathMeasure mProgressPathMeasure;
  78. private float mProgressPresent = 0; // 当前进度百分比
  79. private boolean mCanDrag = false; // 是否允许拖动
  80. private boolean mAllowTouchSkip = false; // 是否允许越过边界
  81. private GestureDetector mDetector;
  82. private Matrix mInvertMatrix; // 逆向 Matrix, 用于计算触摸坐标和绘制坐标的转换
  83. private Region mArcRegion; // ArcPath的实际区域大小,用于判定单击事件
  84. public ArcSeekBar(Context context) {
  85. this(context, null);
  86. }
  87. public ArcSeekBar(Context context, AttributeSet attrs) {
  88. this(context, attrs, 0);
  89. }
  90. public ArcSeekBar(Context context, AttributeSet attrs, int defStyleAttr) {
  91. super(context, attrs, defStyleAttr);
  92. setSaveEnabled(true);
  93. setLayerType(LAYER_TYPE_SOFTWARE, null);
  94. initAttrs(context, attrs);
  95. initPaint();
  96. initData();
  97. }
  98. //--- 初始化 -----------------------------------------------------------------------------------
  99. // 初始化各种属性
  100. private void initAttrs(Context context, AttributeSet attrs) {
  101. TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.ArcSeekBar);
  102. mArcColors = getArcColors(context, ta);
  103. mProgressColors = getProgressColors(context, ta);
  104. mArcWidth = ta.getDimensionPixelSize(R.styleable.ArcSeekBar_arc_width, dp2px(DEFAULT_ARC_WIDTH));
  105. mOpenAngle = ta.getFloat(R.styleable.ArcSeekBar_arc_open_angle, DEFAULT_OPEN_ANGLE);
  106. mRotateAngle = ta.getFloat(R.styleable.ArcSeekBar_arc_rotate_angle, DEFAULT_ROTATE_ANGLE);
  107. mMaxValue = ta.getInt(R.styleable.ArcSeekBar_arc_max, DEFAULT_MAX_VALUE);
  108. mMinValue = ta.getInt(R.styleable.ArcSeekBar_arc_min, DEFAULT_MIN_VALUE);
  109. // 如果用户设置的最大值和最小值不合理,则直接按照默认进行处理
  110. if (mMaxValue <= mMinValue) {
  111. mMaxValue = DEFAULT_MAX_VALUE;
  112. mMinValue = DEFAULT_MIN_VALUE;
  113. }
  114. progress = ta.getInt(R.styleable.ArcSeekBar_arc_progress, mMinValue);
  115. mBorderWidth = ta.getDimensionPixelSize(R.styleable.ArcSeekBar_arc_border_width, dp2px(DEFAULT_BORDER_WIDTH));
  116. mBorderColor = ta.getColor(R.styleable.ArcSeekBar_arc_border_color, DEFAULT_BORDER_COLOR);
  117. mThumbColor = ta.getColor(R.styleable.ArcSeekBar_arc_thumb_color, DEFAULT_THUMB_COLOR);
  118. mThumbRadius = ta.getDimensionPixelSize(R.styleable.ArcSeekBar_arc_thumb_radius, dp2px(DEFAULT_THUMB_RADIUS));
  119. mThumbShadowRadius = ta.getDimensionPixelSize(R.styleable.ArcSeekBar_arc_thumb_shadow_radius, dp2px(DEFAULT_THUMB_SHADOW_RADIUS));
  120. mThumbShadowColor = ta.getColor(R.styleable.ArcSeekBar_arc_thumb_shadow_color, DEFAULT_THUMB_SHADOW_COLOR);
  121. mThumbWidth = ta.getDimensionPixelSize(R.styleable.ArcSeekBar_arc_thumb_width, dp2px(DEFAULT_THUMB_WIDTH));
  122. mThumbMode = ta.getInt(R.styleable.ArcSeekBar_arc_thumb_mode, THUMB_MODE_STROKE);
  123. mShadowRadius = ta.getDimensionPixelSize(R.styleable.ArcSeekBar_arc_shadow_radius, dp2px(DEFAULT_SHADOW_RADIUS));
  124. mOffsetX = ta.getInt(R.styleable.ArcSeekBar_arc_offset_x, 0);
  125. mOffsetY = ta.getInt(R.styleable.ArcSeekBar_arc_offset_y, 0);
  126. ta.recycle();
  127. }
  128. // 获取 Arc 颜色数组
  129. private int[] getArcColors(Context context, TypedArray ta) {
  130. int[] ret;
  131. int resId = ta.getResourceId(R.styleable.ArcSeekBar_arc_progress_background, 0);
  132. if (0 == resId) {
  133. resId = R.array.arc_colors_default;
  134. }
  135. ret = getColorsByArrayResId(context, resId);
  136. return ret;
  137. }
  138. private int[] getProgressColors(Context context, TypedArray ta) {
  139. int[] ret;
  140. int resId = ta.getResourceId(R.styleable.ArcSeekBar_arc_progress_front, 0);
  141. if (0 == resId) {
  142. resId = R.array.arc_colors_default;
  143. }
  144. ret = getColorsByArrayResId(context, resId);
  145. return ret;
  146. }
  147. // 根据 resId 获取颜色数组
  148. private int[] getColorsByArrayResId(Context context, int resId) {
  149. int[] ret;
  150. TypedArray colorArray = context.getResources().obtainTypedArray(resId);
  151. ret = new int[colorArray.length()];
  152. for (int i = 0; i < colorArray.length(); i++) {
  153. ret[i] = colorArray.getColor(i, 0);
  154. }
  155. return ret;
  156. }
  157. // 初始化数据
  158. private void initData() {
  159. mSeekPath = new Path();
  160. mProgressPath = new Path();
  161. mBorderPath = new Path();
  162. mSeekPathMeasure = new PathMeasure();
  163. mProgressPathMeasure = new PathMeasure();
  164. mTempPos = new float[2];
  165. mTempTan = new float[2];
  166. mDetector = new GestureDetector(getContext(), new OnClickListener());
  167. mInvertMatrix = new Matrix();
  168. mArcRegion = new Region();
  169. setProgress(getProgress());
  170. }
  171. // 初始化画笔
  172. private void initPaint() {
  173. initArcPaint();
  174. initProgressPaint();
  175. initThumbPaint();
  176. initBorderPaint();
  177. initShadowPaint();
  178. }
  179. // 初始化圆弧画笔
  180. private void initArcPaint() {
  181. mArcPaint = new Paint();
  182. mArcPaint.setAntiAlias(true);
  183. mArcPaint.setStrokeWidth(mArcWidth);
  184. mArcPaint.setStyle(Paint.Style.STROKE);
  185. mArcPaint.setStrokeCap(Paint.Cap.ROUND);
  186. }
  187. private void initProgressPaint() {
  188. mProgressPaint = new Paint();
  189. mProgressPaint.setAntiAlias(true);
  190. mProgressPaint.setStrokeWidth(mArcWidth);
  191. mProgressPaint.setStyle(Paint.Style.STROKE);
  192. mProgressPaint.setStrokeCap(Paint.Cap.ROUND);
  193. }
  194. // 初始化拖动按钮画笔
  195. private void initThumbPaint() {
  196. mThumbPaint = new Paint();
  197. mThumbPaint.setAntiAlias(true);
  198. mThumbPaint.setColor(mThumbColor);
  199. mThumbPaint.setStrokeWidth(mThumbWidth);
  200. mThumbPaint.setStrokeCap(Paint.Cap.ROUND);
  201. if (mThumbMode == THUMB_MODE_FILL) {
  202. mThumbPaint.setStyle(Paint.Style.FILL_AND_STROKE);
  203. } else if (mThumbMode == THUMB_MODE_FILL_STROKE) {
  204. mThumbPaint.setStyle(Paint.Style.FILL_AND_STROKE);
  205. } else {
  206. mThumbPaint.setStyle(Paint.Style.STROKE);
  207. }
  208. mThumbPaint.setTextSize(56);
  209. }
  210. // 初始化拖动按钮画笔
  211. private void initBorderPaint() {
  212. mBorderPaint = new Paint();
  213. mBorderPaint.setAntiAlias(true);
  214. mBorderPaint.setColor(mBorderColor);
  215. mBorderPaint.setStrokeWidth(mBorderWidth);
  216. mBorderPaint.setStyle(Paint.Style.STROKE);
  217. }
  218. // 初始化阴影画笔
  219. private void initShadowPaint() {
  220. mShadowPaint = new Paint();
  221. mShadowPaint.setAntiAlias(true);
  222. mShadowPaint.setStrokeWidth(mBorderWidth);
  223. mShadowPaint.setStyle(Paint.Style.FILL_AND_STROKE);
  224. }
  225. //--- 初始化结束 -------------------------------------------------------------------------------
  226. //--- 状态存储 ---------------------------------------------------------------------------------
  227. @Override
  228. protected Parcelable onSaveInstanceState() {
  229. Bundle bundle = new Bundle();
  230. bundle.putParcelable("superState", super.onSaveInstanceState());
  231. bundle.putFloat(KEY_PROGRESS_PRESENT, mProgressPresent);
  232. return bundle;
  233. }
  234. @Override
  235. protected void onRestoreInstanceState(Parcelable state) {
  236. if (state instanceof Bundle) {
  237. Bundle bundle = (Bundle) state;
  238. this.mProgressPresent = bundle.getFloat(KEY_PROGRESS_PRESENT);
  239. state = bundle.getParcelable("superState");
  240. }
  241. if (null != mOnProgressChangeListener) {
  242. mOnProgressChangeListener.onProgressChanged(this, getProgress(), false);
  243. }
  244. super.onRestoreInstanceState(state);
  245. }
  246. //--- 状态存储结束 -----------------------------------------------------------------------------
  247. @Override
  248. protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
  249. int ws = MeasureSpec.getSize(widthMeasureSpec); //取出宽度的确切数值
  250. int wm = MeasureSpec.getMode(widthMeasureSpec); //取出宽度的测量模式
  251. int hs = MeasureSpec.getSize(heightMeasureSpec); //取出高度的确切数值
  252. int hm = MeasureSpec.getMode(heightMeasureSpec); //取出高度的测量模
  253. if (wm == MeasureSpec.UNSPECIFIED) {
  254. wm = MeasureSpec.EXACTLY;
  255. ws = dp2px(DEFAULT_EDGE_LENGTH);
  256. } else if (wm == MeasureSpec.AT_MOST) {
  257. wm = MeasureSpec.EXACTLY;
  258. ws = Math.min(dp2px(DEFAULT_EDGE_LENGTH), ws);
  259. }
  260. if (hm == MeasureSpec.UNSPECIFIED) {
  261. hm = MeasureSpec.EXACTLY;
  262. hs = dp2px(DEFAULT_EDGE_LENGTH);
  263. } else if (hm == MeasureSpec.AT_MOST) {
  264. hm = MeasureSpec.EXACTLY;
  265. hs = Math.min(dp2px(DEFAULT_EDGE_LENGTH), hs);
  266. }
  267. setMeasuredDimension(MeasureSpec.makeMeasureSpec(ws, wm), MeasureSpec.makeMeasureSpec(hs, hm));
  268. }
  269. @Override
  270. protected void onSizeChanged(int w, int h, int oldw, int oldh) {
  271. super.onSizeChanged(w, h, oldw, oldh);
  272. // 计算在当前大小下,内容应该显示的大小和起始位置
  273. int safeW = w - getPaddingLeft() - getPaddingRight();
  274. int safeH = h - getPaddingTop() - getPaddingBottom();
  275. float edgeLength, startX, startY;
  276. float fix = mArcWidth / 2 + mBorderWidth + mShadowRadius * 2; // 修正距离,画笔宽度的修正
  277. if (safeW < safeH) {
  278. // 宽度小于高度,以宽度为准
  279. edgeLength = safeW - fix;
  280. startX = getPaddingLeft();
  281. startY = (safeH - safeW) / 2.0f + getPaddingTop();
  282. } else {
  283. // 宽度大于高度,以高度为准
  284. edgeLength = safeH - fix;
  285. startX = (safeW - safeH) / 2.0f + getPaddingLeft();
  286. startY = getPaddingTop();
  287. }
  288. // 得到显示区域和中心的
  289. content = new RectF(startX + fix, startY + fix, startX + edgeLength, startY + edgeLength);
  290. mCenterX = content.centerX();
  291. mCenterY = content.centerY();
  292. // 得到路径
  293. mSeekPath.reset();
  294. mSeekPath.addArc(content, mOpenAngle / 2, CIRCLE_ANGLE - mOpenAngle);
  295. mSeekPathMeasure.setPath(mSeekPath, false);
  296. computeThumbPos(mProgressPresent);
  297. resetShaderColor();
  298. // 绘制progress路径
  299. mProgressPath.reset();
  300. float total = CIRCLE_ANGLE - mOpenAngle;//100%进度的扇形角度
  301. float percent = ((float) getProgress() / (mMaxValue - mMinValue));
  302. mProgressPath.addArc(content, mOpenAngle / 2, total * percent);
  303. mProgressPathMeasure.setPath(mProgressPath, false);//是否闭合开口,设置成false
  304. resetProgressColor();
  305. mInvertMatrix.reset();
  306. mInvertMatrix.preRotate(-mRotateAngle, mCenterX, mCenterY);
  307. mArcPaint.getFillPath(mSeekPath, mBorderPath);
  308. mBorderPath.close();
  309. mArcRegion.setPath(mBorderPath, new Region(0, 0, w, h));
  310. }
  311. // 重置 shader 颜色
  312. private void resetShaderColor() {
  313. // 计算渐变数组
  314. float startPos = (mOpenAngle / 2) / CIRCLE_ANGLE;
  315. float stopPos = (CIRCLE_ANGLE - (mOpenAngle / 2)) / CIRCLE_ANGLE;
  316. int len = mArcColors.length - 1;
  317. float distance = (stopPos - startPos) / len;
  318. float pos[] = new float[mArcColors.length];
  319. for (int i = 0; i < mArcColors.length; i++) {
  320. pos[i] = startPos + (distance * i);
  321. }
  322. SweepGradient gradient = new SweepGradient(mCenterX, mCenterY, mArcColors, pos);
  323. mArcPaint.setShader(gradient);
  324. }
  325. private void resetProgressColor() {
  326. // 计算渐变数组
  327. float startPos = (mOpenAngle / 2) / CIRCLE_ANGLE;
  328. float stopPos = (CIRCLE_ANGLE - (mOpenAngle / 2)) / CIRCLE_ANGLE;
  329. int len = mProgressColors.length - 1;
  330. float distance = (stopPos - startPos) / len;
  331. float pos[] = new float[mProgressColors.length];
  332. for (int i = 0; i < mProgressColors.length; i++) {
  333. pos[i] = startPos + (distance * i);
  334. }
  335. SweepGradient gradient = new SweepGradient(mCenterX, mCenterY, mProgressColors, pos);
  336. mProgressPaint.setShader(gradient);
  337. invalidate();
  338. }
  339. // 具体绘制
  340. @Override
  341. protected void onDraw(Canvas canvas) {
  342. canvas.save();
  343. canvas.rotate(mRotateAngle, mCenterX + mOffsetX, mCenterY + mOffsetY);
  344. mShadowPaint.setShadowLayer(mShadowRadius * 2, 0, 0, getColor());
  345. canvas.drawPath(mBorderPath, mShadowPaint);
  346. canvas.drawPath(mSeekPath, mArcPaint);
  347. mProgressPath.reset();
  348. float total = CIRCLE_ANGLE - mOpenAngle;//100%进度的扇形角度
  349. float percent = ((float) getProgress() / (mMaxValue - mMinValue));
  350. mProgressPath.addArc(content, mOpenAngle / 2, total * percent);
  351. mProgressPathMeasure.setPath(mProgressPath, false);//是否闭合开口,设置成false
  352. resetProgressColor();
  353. canvas.drawPath(mProgressPath, mProgressPaint);
  354. if (mBorderWidth > 0) {
  355. canvas.drawPath(mBorderPath, mBorderPaint);
  356. }
  357. if (mThumbShadowRadius > 0) {
  358. mThumbPaint.setShadowLayer(mThumbShadowRadius, 0, 0, mThumbShadowColor);
  359. canvas.drawCircle(mThumbX, mThumbY, mThumbRadius, mThumbPaint);
  360. mThumbPaint.clearShadowLayer();
  361. }
  362. canvas.drawCircle(mThumbX, mThumbY, mThumbRadius, mThumbPaint);
  363. canvas.restore();
  364. }
  365. private boolean moved = false;
  366. private int lastProgress = -1;
  367. @SuppressLint("ClickableViewAccessibility")
  368. @Override
  369. public boolean onTouchEvent(MotionEvent event) {
  370. super.onTouchEvent(event);
  371. int action = event.getActionMasked();
  372. switch (action) {
  373. case ACTION_DOWN:
  374. moved = false;
  375. judgeCanDrag(event);
  376. if (null != mOnProgressChangeListener) {
  377. mOnProgressChangeListener.onStartTrackingTouch(this);
  378. }
  379. break;
  380. case ACTION_MOVE:
  381. if (!mCanDrag) {
  382. break;
  383. }
  384. float tempProgressPresent = getCurrentProgress(event.getX(), event.getY());
  385. if (!mAllowTouchSkip) {
  386. // 不允许突变
  387. if (Math.abs(tempProgressPresent - mProgressPresent) > 0.5f) {
  388. break;
  389. }
  390. }
  391. // 允许突变 或者非突变
  392. mProgressPresent = tempProgressPresent;
  393. computeThumbPos(mProgressPresent);
  394. // 事件回调
  395. if (null != mOnProgressChangeListener && getProgress() != lastProgress) {
  396. mOnProgressChangeListener.onProgressChanged(this, getProgress(), true);
  397. lastProgress = getProgress();
  398. }
  399. moved = true;
  400. resetProgressColor();
  401. break;
  402. case ACTION_UP:
  403. case ACTION_CANCEL:
  404. if (null != mOnProgressChangeListener && moved) {
  405. mOnProgressChangeListener.onStopTrackingTouch(this);
  406. }
  407. break;
  408. }
  409. mDetector.onTouchEvent(event);
  410. invalidate();
  411. return true;
  412. }
  413. // 判断是否允许拖动
  414. private void judgeCanDrag(MotionEvent event) {
  415. float[] pos = {event.getX(), event.getY()};
  416. mInvertMatrix.mapPoints(pos);
  417. if (getDistance(pos[0], pos[1]) <= mThumbRadius * 1.5) {
  418. mCanDrag = true;
  419. } else {
  420. mCanDrag = false;
  421. }
  422. }
  423. private class OnClickListener extends GestureDetector.SimpleOnGestureListener {
  424. @Override
  425. public boolean onSingleTapUp(MotionEvent e) {
  426. // 判断是否点击在了进度区域
  427. if (!isInArcProgress(e.getX(), e.getY())) return false;
  428. // 点击允许突变
  429. mProgressPresent = getCurrentProgress(e.getX(), e.getY());
  430. computeThumbPos(mProgressPresent);
  431. // 事件回调
  432. if (null != mOnProgressChangeListener) {
  433. mOnProgressChangeListener.onProgressChanged(ArcSeekBar.this, getProgress(), true);
  434. mOnProgressChangeListener.onStopTrackingTouch(ArcSeekBar.this);
  435. }
  436. return true;
  437. }
  438. }
  439. // 判断该点是否在进度条上面
  440. private boolean isInArcProgress(float px, float py) {
  441. float[] pos = {px, py};
  442. mInvertMatrix.mapPoints(pos);
  443. return mArcRegion.contains((int) pos[0], (int) pos[1]);
  444. }
  445. // 获取当前进度理论进度数值
  446. private float getCurrentProgress(float px, float py) {
  447. float diffAngle = getDiffAngle(px, py);
  448. float progress = diffAngle / (CIRCLE_ANGLE - mOpenAngle);
  449. if (progress < 0) progress = 0;
  450. if (progress > 1) progress = 1;
  451. return progress;
  452. }
  453. // 获得当前点击位置所成角度与开始角度之间的数值差
  454. private float getDiffAngle(float px, float py) {
  455. float angle = getAngle(px, py);
  456. float diffAngle;
  457. diffAngle = angle - mRotateAngle;
  458. if (diffAngle < 0) {
  459. diffAngle = (diffAngle + CIRCLE_ANGLE) % CIRCLE_ANGLE;
  460. }
  461. diffAngle = diffAngle - mOpenAngle / 2;
  462. return diffAngle;
  463. }
  464. // 计算指定位置与内容区域中心点的夹角
  465. private float getAngle(float px, float py) {
  466. float angle = (float) ((Math.atan2(py - mCenterY, px - mCenterX)) * 180 / 3.14f);
  467. if (angle < 0) {
  468. angle += 360;
  469. }
  470. return angle;
  471. }
  472. // 计算指定位置与上次位置的距离
  473. private float getDistance(float px, float py) {
  474. return (float) Math.sqrt((px - mThumbX) * (px - mThumbX) + (py - mThumbY) * (py - mThumbY));
  475. }
  476. private int dp2px(int dp) {
  477. return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dp, getContext().getResources().getDisplayMetrics());
  478. }
  479. // 计算拖动块应该显示的位置
  480. private void computeThumbPos(float present) {
  481. if (present < 0) present = 0;
  482. if (present > 1) present = 1;
  483. if (null == mSeekPathMeasure) return;
  484. float distance = mSeekPathMeasure.getLength() * present;
  485. mSeekPathMeasure.getPosTan(distance, mTempPos, mTempTan);
  486. mThumbX = mTempPos[0];
  487. mThumbY = mTempPos[1];
  488. }
  489. //--- 线性取色 ---------------------------------------------------------------------------------
  490. /**
  491. * 获取当前进度的具体颜色
  492. *
  493. * @return 当前进度在渐变中的颜色
  494. */
  495. public int getColor() {
  496. return getColor(mProgressPresent);
  497. }
  498. /**
  499. * 获取某个百分比位置的颜色
  500. *
  501. * @param radio 取值[0,1]
  502. * @return 最终颜色
  503. */
  504. private int getColor(float radio) {
  505. float diatance = 1.0f / (mArcColors.length - 1);
  506. int startColor;
  507. int endColor;
  508. if (radio >= 1) {
  509. return mArcColors[mArcColors.length - 1];
  510. }
  511. for (int i = 0; i < mArcColors.length; i++) {
  512. if (radio <= i * diatance) {
  513. if (i == 0) {
  514. return mArcColors[0];
  515. }
  516. startColor = mArcColors[i - 1];
  517. endColor = mArcColors[i];
  518. float areaRadio = getAreaRadio(radio, diatance * (i - 1), diatance * i);
  519. return getColorFrom(startColor, endColor, areaRadio);
  520. }
  521. }
  522. return -1;
  523. }
  524. /**
  525. * 计算当前比例在子区间的比例
  526. *
  527. * @param radio 总比例
  528. * @param startPosition 子区间开始位置
  529. * @param endPosition 子区间结束位置
  530. * @return 自区间比例[0, 1]
  531. */
  532. private float getAreaRadio(float radio, float startPosition, float endPosition) {
  533. return (radio - startPosition) / (endPosition - startPosition);
  534. }
  535. /**
  536. * 取两个颜色间的渐变区间 中的某一点的颜色
  537. *
  538. * @param startColor 开始的颜色
  539. * @param endColor 结束的颜色
  540. * @param radio 比例 [0, 1]
  541. * @return 选中点的颜色
  542. */
  543. private int getColorFrom(int startColor, int endColor, float radio) {
  544. int redStart = Color.red(startColor);
  545. int blueStart = Color.blue(startColor);
  546. int greenStart = Color.green(startColor);
  547. int redEnd = Color.red(endColor);
  548. int blueEnd = Color.blue(endColor);
  549. int greenEnd = Color.green(endColor);
  550. int red = (int) (redStart + ((redEnd - redStart) * radio + 0.5));
  551. int greed = (int) (greenStart + ((greenEnd - greenStart) * radio + 0.5));
  552. int blue = (int) (blueStart + ((blueEnd - blueStart) * radio + 0.5));
  553. return Color.argb(255, red, greed, blue);
  554. }
  555. //region 对外接口 -------------------------------------------------------------------------------
  556. /**
  557. * 设置进度
  558. *
  559. * @param progress 进度值
  560. */
  561. public void setProgress(int progress) {
  562. System.out.println("setProgress = " + progress);
  563. if (progress > mMaxValue) progress = mMaxValue;
  564. if (progress < mMinValue) progress = mMinValue;
  565. mProgressPresent = (progress - mMinValue) * 1.0f / (mMaxValue - mMinValue);
  566. System.out.println("setProgress present = " + mProgressPresent);
  567. if (null != mOnProgressChangeListener) {
  568. mOnProgressChangeListener.onProgressChanged(this, progress, false);
  569. }
  570. computeThumbPos(mProgressPresent);
  571. resetProgressColor();
  572. postInvalidate();
  573. }
  574. public void setProgress() {
  575. setProgress(getProgress());
  576. }
  577. /**
  578. * 获取当前进度数值
  579. *
  580. * @return 当前进度数值
  581. */
  582. public int getProgress() {
  583. return (int) (mProgressPresent * (mMaxValue - mMinValue)) + mMinValue;
  584. }
  585. /**
  586. * 设置颜色
  587. *
  588. * @param colors 颜色
  589. */
  590. public void setArcColors(int[] colors) {
  591. mArcColors = colors;
  592. resetShaderColor();
  593. resetProgressColor();
  594. postInvalidate();
  595. }
  596. /**
  597. * 设置最大数值
  598. * @param max 最大数值
  599. */
  600. public void setMaxValue(int max) {
  601. mMaxValue = max;
  602. }
  603. /**
  604. * 设置最小数值
  605. * @param min 最小数值
  606. */
  607. public void setMinValue(int min) {
  608. mMinValue = min;
  609. }
  610. /**
  611. * 设置颜色
  612. *
  613. * @param colorArrayRes 颜色资源 R.array.arc_color
  614. */
  615. public void setArcColors(int colorArrayRes) {
  616. setArcColors(getColorsByArrayResId(getContext(), colorArrayRes));
  617. }
  618. // endregion -----------------------------------------------------------------------------------
  619. // region 状态回调 ------------------------------------------------------------------------------
  620. private OnProgressChangeListener mOnProgressChangeListener;
  621. public void setOnProgressChangeListener(OnProgressChangeListener onProgressChangeListener) {
  622. mOnProgressChangeListener = onProgressChangeListener;
  623. }
  624. public interface OnProgressChangeListener {
  625. /**
  626. * 进度发生变化
  627. *
  628. * @param seekBar 拖动条
  629. * @param progress 当前进度数值
  630. * @param isUser 是否是用户操作, true 表示用户拖动, false 表示通过代码设置
  631. */
  632. void onProgressChanged(ArcSeekBar seekBar, int progress, boolean isUser);
  633. /**
  634. * 用户开始拖动
  635. *
  636. * @param seekBar 拖动条
  637. */
  638. void onStartTrackingTouch(ArcSeekBar seekBar);
  639. /**
  640. * 用户结束拖动
  641. *
  642. * @param seekBar 拖动条
  643. */
  644. void onStopTrackingTouch(ArcSeekBar seekBar);
  645. }
  646. }

attrs.xml

  1. <declare-styleable name="ArcSeekBar">
  2. <attr name="arc_width" format="dimension|reference" />
  3. <attr name="arc_open_angle" format="float" />
  4. <attr name="arc_rotate_angle" format="float" />
  5. <!-- <attr name="arc_colors" format="reference" />-->
  6. <attr name="arc_border_width" format="dimension|reference" />
  7. <attr name="arc_border_color" format="color|reference" />
  8. <attr name="arc_max" format="integer|reference" />
  9. <attr name="arc_min" format="integer|reference" />
  10. <attr name="arc_progress" format="integer|reference" />
  11. <attr name="arc_progress_background" format="reference" />
  12. <attr name="arc_progress_front" format="reference" />
  13. <attr name="arc_thumb_width" format="dimension|reference" />
  14. <attr name="arc_thumb_color" format="color|reference" />
  15. <attr name="arc_thumb_radius" format="dimension|reference" />
  16. <attr name="arc_thumb_shadow_radius" format="dimension|reference" />
  17. <attr name="arc_thumb_shadow_color" format="color|reference" />
  18. <attr name="arc_thumb_mode" format="integer|dimension">
  19. <enum name="STROKE" value="0" />
  20. <enum name="FILL" value="1" />
  21. <enum name="FILL_STROKE" value="2" />
  22. </attr>
  23. <attr name="arc_shadow_radius" format="dimension|reference" />
  24. <attr name="arc_offset_x" format="integer" />
  25. <attr name="arc_offset_y" format="integer" />
  26. </declare-styleable>

color颜色值:

  1. <array name="arc_colors_default">
  2. <item>#343434</item>
  3. <item>#626262</item>
  4. <item>#fff</item>
  5. </array>
  6. <array name="arc_colors_1">
  7. <item>#FFE881</item>
  8. <item>#fefefe</item>
  9. <item>#68D1F4</item>
  10. </array>

动图中 我的应用实例:

  1. <ArcSeekBar
  2. android:id="@+id/arc_seek_bar"
  3. android:layout_width="280dp"
  4. android:layout_height="wrap_content"
  5. android:layout_centerInParent="true"
  6. app:arc_max="100"
  7. app:arc_open_angle="90"
  8. app:arc_progress="0"
  9. app:arc_progress_background="@array/arc_colors_default"
  10. app:arc_progress_front="@array/arc_colors_1"
  11. app:arc_rotate_angle="90"
  12. app:arc_thumb_color="#fff"
  13. app:arc_thumb_mode="FILL"
  14. app:arc_thumb_radius="16dp"
  15. app:arc_thumb_shadow_color="#000000"
  16. app:arc_thumb_shadow_radius="3dp"
  17. app:arc_thumb_width="3dp"
  18. app:arc_width="20dp"
  19. app:arc_offset_x="-20"
  20. app:arc_offset_y="20"
  21. />

 我修改的属性

namedescformatdefault
arc_progress_background背景色
reference
arc_colors_default
arc_progress_front前景色
reference
arc_colors_default
arc_offset_x圆环X轴偏移量int0
arc_offset_y圆环Y轴偏移量int0

 

 前景色和后景色是从作者的arc_colors属性演变而来的

偏移量的目的:因为圆环宽度如果小于滑块宽度,会出现 滑块被裁剪的现象默认为0

功能简单,我就不放GitHub了。有心人可以去给原作者一个star

再次感谢作者:GcsSloop

作者的GitHub:GitHub - GcsSloop/arc-seekbar: Android 圆弧形 SeekBar。

本文内容由网友自发贡献,转载请注明出处:【wpsshop博客】
推荐阅读
相关标签
  

闽ICP备14008679号