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

835 阅读3分钟

效果图


ScreenGif4.gif
  • 可以通过旋转view或者直接拖动来控制进度。
  • 根据旋转角度,音量的移动速度会改变
  • 根据旋转角度,归位时候的速度会改变

实现流程

  1. 重写onMeasure(),使得高度为外部矩形的高度+padding。
  2. 重写onDraw(),绘制两个矩形和一个球。
  3. 重写onTouchEvent()判断是点击小球移动还是旋转控件移动,并且判断点击是控件左半部分还是右半部分,在手指抬起时,执行归为动画。
  4. 设置音量改变接口供外部使用。

    使用

    记得要在外层Linearlayout中要添加clipChildred=false。。

    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:tools="http://schemas.android.com/tools"
     android:layout_width="match_parent"
     android:layout_height="match_parent"
     android:orientation="vertical"
     android:clipChildren="false"
     tools:context=".MainActivity">
    
     <TextView
         android:textSize="14sp"
         android:textColor="#333333"
         android:id="@+id/tv"
         android:gravity="center"
         android:layout_marginTop="100dp"
         android:layout_marginBottom="20dp"
         android:text="音量 :"
         android:layout_width="match_parent"
         android:layout_height="wrap_content" />
     <com.lsp.ProgressView
         android:id="@+id/voice"
         android:paddingLeft="20dp"
         android:paddingRight="20dp"
         android:layout_width="match_parent"
         android:layout_height="wrap_content" />
    </LinearLayout>

    代码

    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);
     }
    }