Android模拟糟糕音量控制设计大赛之ProgressView

效果图 ScreenGif4.gif

  • 可以通过旋转view或者直接拖动来控制进度。
  • 根据旋转角度,音量的移动速度会改变
  • 根据旋转角度,归位时候的速度会改变
实现流程
  1. 重写onMeasure(),使得高度为外部矩形的高度+padding。
  2. 重写onDraw(),绘制两个矩形和一个球。
  3. 重写onTouchEvent()判断是点击小球移动还是旋转控件移动,并且判断点击是控件左半部分还是右半部分,在手指抬起时,执行归为动画。
  4. 设置音量改变接口供外部使用。
使用 【Android模拟糟糕音量控制设计大赛之ProgressView】记得要在外层Linearlayout中要添加clipChildred=false。。

代码
public class ProgressView extends View { private static final String TAG = "HappyVoiceView"; /** * 外层矩形框画笔 */ private Paint outRectPaint; /** * 内层矩形框画笔 */ private Paint innerRectPaint; /** * 音量控制球画笔 */ private Paint ballPaint; /** * 外层,内层,球的矩形范围 */ private RectF rectF1, rectF2, ballRect; /** * 外层矩形高度 */ private int outRectHeight = 50; /** * 内层矩形框画笔 */ private int innerRectHeight = 20; /** * 小球的半径 */ private int circleRadius = 15; /** * 内层小球可移动范围 */ private int length = 0; /** * 是否是旋转控件 */ private boolean doRoate = false; /** * 竖直方向偏移 */ private float downY; /** * 角度 */ private int degress = 0; /** * 手指抬起归位 */ private ValueAnimator valueAnimator; /** * 小球当前位置 */ private int ballCurrentLength = 0; /** * 从左面旋转控件 */ private boolean isTouchLeft = false; /** * 从右面面旋转控件 */ private boolean isTouchBall = false; /** * 小球移动最终速度 */ private int speed = 1; /** * 小球最小速度 */ private int minSpeed = 2; /** * 音量改变监听 */ private OnVoiceUpdateLinstener voiceUpdateLinstener; public void setVoiceUpdateLinstener(OnVoiceUpdateLinstener voiceUpdateLinstener) { this.voiceUpdateLinstener = voiceUpdateLinstener; }public ProgressView(Context context) { super(context); init(); } public ProgressView(Context context, @Nullable AttributeSet attrs) { super(context, attrs); init(); } public ProgressView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); init(); } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { super.onMeasure(widthMeasureSpec, heightMeasureSpec); int widthSize = MeasureSpec.getSize(widthMeasureSpec); setMeasuredDimension(widthSize, outRectHeight + getPaddingTop() + getPaddingBottom()); } private void init() { outRectPaint = initPaint(); outRectPaint.setColor(Color.CYAN); innerRectPaint = initPaint(); innerRectPaint.setColor(Color.YELLOW); ballPaint = initPaint(); ballPaint.setColor(Color.RED); ballRect = new RectF(); } private void initValueAnimator() { valueAnimator = ValueAnimator.ofInt(degress, 0); valueAnimator.setDuration(Math.abs(degress)/10*100); valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { @Override public void onAnimationUpdate(ValueAnimator valueAnimator) { degress = (int) valueAnimator.getAnimatedValue(); invalidate(); } }); valueAnimator.start(); } private Paint initPaint() { Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG); //抗锯齿 paint.setDither(true); //防抖动 paint.setColor(Color.CYAN); paint.setStyle(Paint.Style.FILL); paint.setStrokeCap(Paint.Cap.SQUARE); return paint; } @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); canvas.translate(getMeasuredWidth() / 2, getMeasuredHeight() / 2); dealBallLength(); rectF1 = new RectF((-getMeasuredWidth() / 2) + getPaddingLeft(), -outRectHeight / 2, (getMeasuredWidth() / 2) - getPaddingLeft(), outRectHeight / 2); rectF2 = new RectF((-getMeasuredWidth() / 2) + getPaddingLeft() + getPaddingLeft(), -innerRectHeight / 2, (getMeasuredWidth() / 2) - getPaddingLeft() - getPaddingLeft(), innerRectHeight / 2); length = (int) rectF2.width(); canvas.rotate(degress); canvas.drawRoundRect(rectF1, 10, 10, outRectPaint); canvas.drawRoundRect(rectF2, 20, 20, innerRectPaint); canvas.drawCircle((rectF2.left + ballCurrentLength) + circleRadius / 2, rectF2.centerY(), circleRadius, ballPaint); ballRect.left = (rectF2.left + ballCurrentLength) + circleRadius / 2 - circleRadius; ballRect.right = (rectF2.left + ballCurrentLength) + circleRadius / 2 + circleRadius; ballRect.top = rectF2.centerY() - circleRadius; ballRect.bottom = rectF2.centerY() + circleRadius; if (voiceUpdateLinstener != null) { voiceUpdateLinstener.onVoiceChanged((int) ((float) ballCurrentLength / length * 100)); } } private int dealBallLength() { speed = Math.abs(degress) / 3 + minSpeed; if (degress > 0 && ballCurrentLength < length) { speed = ballCurrentLength + speed > length ? (length - ballCurrentLength) : speed; ballCurrentLength += speed; invalidate(); } else if (degress < 0 && ballCurrentLength > 0) { speed = ballCurrentLength - speed < 0 ? ballCurrentLength : speed; ballCurrentLength -= speed; invalidate(); }return ballCurrentLength; } @Override public boolean onTouchEvent(MotionEvent event) { float y = event.getY(); float x = event.getX(); switch (event.getAction()) { case MotionEvent.ACTION_DOWN: if (valueAnimator != null && valueAnimator.isRunning()) { return false; } if (ballRect.contains(event.getX() - getMeasuredWidth() / 2, y - getMeasuredHeight() / 2)) { isTouchBall = true; break; } if (rectF1.contains(event.getX() - getMeasuredWidth() / 2, y - getMeasuredHeight() / 2)) {//平移过坐标系 doRoate = true; downY = (int) event.getY(); if (event.getX() - getMeasuredWidth() / 2 <= 0) { isTouchLeft = true; } else { isTouchLeft = false; } } break; case MotionEvent.ACTION_MOVE: if (isTouchBall) { ballCurrentLength = (int) (x - getPaddingLeft() - getPaddingLeft()); if (ballCurrentLength < 0) { ballCurrentLength = 0; } else if (ballCurrentLength > length) { ballCurrentLength = length; } invalidate(); break; } x = (float) Math.atan((y - downY) / rectF1.right); degress = (int) Math.toDegrees(x); degress = isTouchLeft ? -degress : degress; invalidate(); break; case MotionEvent.ACTION_UP: if (doRoate) { initValueAnimator(); } doRoate = false; isTouchBall = false; break; } return true; } public interface OnVoiceUpdateLinstener { void onVoiceChanged(int voice); } }

    推荐阅读