美文网首页Android自定义View自定义ViewUI
Android 自定义View 音量控制View

Android 自定义View 音量控制View

作者: h2coder | 来源:发表于2021-06-18 12:02 被阅读0次

    类似iOS的控制中心里,音量的上下滑动增大、减小音量。

    view.gif

    简单说一下需求:

    • 需要进度条有圆角,使用Canvas裁切即可
    • 手势拖动进度条更新进度,也可以代码调用更新进度
    • 支持XML和Java代码设置最大值、最小值、当前值,以及进度的颜色、圆角半径等

    自定义属性

    <?xml version="1.0" encoding="utf-8"?>
    <resources>
        <declare-styleable name="VerticalSeekBar">
            <!-- 背景进度颜色 -->
            <attr name="vsb_bg" format="color" />
            <!-- 已有进度颜色 -->
            <attr name="vsb_progress_bg" format="color" />
            <!-- 进度条圆角半径 -->
            <attr name="vsb_bg_radius" format="integer|float|dimension" />
            <!-- 当前进度值 -->
            <attr name="vsb_progress" format="float" />
            <!-- 最小进度值 -->
            <attr name="vsb_min_progress" format="float" />
            <!-- 最大进度值 -->
            <attr name="vsb_max_progress" format="float" />
        </declare-styleable>
    </resources>
    

    完整代码

    /**
     * 垂直拽托进度条
     */
    public class VerticalSeekBar extends View {
        /**
         * View默认最小宽度
         */
        private int mDefaultWidth;
        /**
         * View默认最小高度
         */
        private int mDefaultHeight;
        /**
         * 控件宽
         */
        private int mViewWidth;
        /**
         * 控件高
         */
        private int mViewHeight;
        /**
         * 背景颜色
         */
        private int mBgColor;
        /**
         * 进度背景颜色
         */
        private int mProgressBgColor;
        /**
         * 进度条的圆角半径
         */
        private int mBgRadius;
        /**
         * 当前进度
         */
        private float mProgress;
        /**
         * 最小进度值
         */
        private float mMin;
        /**
         * 最大进度值
         */
        private float mMax;
        /**
         * 背景画笔
         */
        private Paint mBgPaint;
        /**
         * 进度画笔
         */
        private Paint mProgressPaint;
        /**
         * 进度更新监听
         */
        private VerticalSeekBar.OnProgressUpdateListener mOnProgressUpdateListener;
    
        public VerticalSeekBar(Context context) {
            this(context, null);
        }
    
        public VerticalSeekBar(Context context, @Nullable AttributeSet attrs) {
            this(context, attrs, 0);
        }
    
        public VerticalSeekBar(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
            super(context, attrs, defStyleAttr);
            init(context, attrs, defStyleAttr);
        }
    
        private void init(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
            initAttr(context, attrs, defStyleAttr);
            //取消硬件加速
            setLayerType(LAYER_TYPE_SOFTWARE, null);
            //背景画笔
            mBgPaint = new Paint();
            mBgPaint.setAntiAlias(true);
            mBgPaint.setColor(mBgColor);
            mBgPaint.setStyle(Paint.Style.FILL);
            //进度画笔
            mProgressPaint = new Paint();
            mProgressPaint.setColor(mProgressBgColor);
            mProgressPaint.setAntiAlias(true);
            mProgressPaint.setStyle(Paint.Style.FILL);
            //计算默认宽、高
            mDefaultWidth = dip2px(context, 36f);
            mDefaultHeight = dip2px(context, 114f);
        }
    
        private void initAttr(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
            int defaultBgColor = Color.parseColor("#EDF0FA");
            int defaultProgressBgColor = Color.parseColor("#6D79FE");
            int defaultBgRadius = dip2px(context, 8f);
            float defaultProgress = 0;
            float defaultMinProgress = 0;
            int defaultMaxProgress = 100;
            if (attrs != null) {
                TypedArray array = context.obtainStyledAttributes(attrs, R.styleable.VerticalSeekBar, defStyleAttr, 0);
                //进度背景颜色
                mBgColor = array.getColor(R.styleable.VerticalSeekBar_vsb_bg, defaultBgColor);
                //已有进度的背景颜色
                mProgressBgColor = array.getColor(R.styleable.VerticalSeekBar_vsb_progress_bg, defaultProgressBgColor);
                //进度条的圆角
                mBgRadius = array.getDimensionPixelSize(R.styleable.VerticalSeekBar_vsb_bg_radius, defaultBgRadius);
                //当前进度值
                mProgress = array.getFloat(R.styleable.VerticalSeekBar_vsb_progress, defaultProgress);
                //最小进度值
                mMin = array.getFloat(R.styleable.VerticalSeekBar_vsb_min_progress, defaultMinProgress);
                //最大进度值
                mMax = array.getFloat(R.styleable.VerticalSeekBar_vsb_max_progress, defaultMaxProgress);
                array.recycle();
            } else {
                mBgColor = defaultBgColor;
                mProgressBgColor = defaultProgressBgColor;
                mProgress = defaultProgress;
                mMin = defaultMinProgress;
            }
        }
    
        @Override
        protected void onSizeChanged(int w, int h, int oldw, int oldh) {
            super.onSizeChanged(w, h, oldw, oldh);
            mViewWidth = w;
            mViewHeight = h;
        }
    
        @Override
        protected void onDraw(Canvas canvas) {
            super.onDraw(canvas);
            //裁切圆角
            clipRound(canvas);
            //画背景
            drawBg(canvas);
            //画进度条
            drawProgress(canvas);
        }
    
        //------------ getFrameXxx()方法都是处理padding ------------
    
        private float getFrameLeft() {
            return getPaddingStart();
        }
    
        private float getFrameRight() {
            return mViewWidth - getPaddingEnd();
        }
    
        private float getFrameTop() {
            return getPaddingTop();
        }
    
        private float getFrameBottom() {
            return mViewHeight - getPaddingBottom();
        }
    
        //------------ getFrameXxx()方法都是处理padding ------------
    
        /**
         * 裁剪圆角
         */
        private void clipRound(Canvas canvas) {
            Path path = new Path();
            RectF roundRect = new RectF(getFrameLeft(), getFrameTop(), getFrameRight(), getFrameBottom());
            path.addRoundRect(roundRect, mBgRadius, mBgRadius, Path.Direction.CW);
            canvas.clipPath(path);
        }
    
        /**
         * 画背景
         */
        private void drawBg(Canvas canvas) {
            canvas.drawRect(new RectF(getFrameLeft(), getFrameTop(),
                            getFrameRight(), getFrameBottom()),
                    mBgPaint);
        }
    
        /**
         * 画进度
         */
        private void drawProgress(Canvas canvas) {
            float contentHeight = mViewHeight - getPaddingTop() - getPaddingBottom();
            //计算出当前进度应该有个top值,因为进度是从小往上,所以百分比要被1减去
            float progressRatio = getProgressRatio();
            float top;
            if (progressRatio == 0) {
                top = contentHeight;
            } else {
                top = contentHeight * (1 - progressRatio);
            }
            //画进度矩形
            RectF rect = new RectF(getFrameLeft(),
                    top,
                    getFrameRight(),
                    getFrameBottom());
            canvas.drawRect(rect, mProgressPaint);
        }
    
        @Override
        protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
            super.onMeasure(widthMeasureSpec, heightMeasureSpec);
            setMeasuredDimension(handleMeasure(widthMeasureSpec, true),
                    handleMeasure(heightMeasureSpec, false));
        }
    
        @Override
        public boolean dispatchTouchEvent(MotionEvent event) {
            int action = event.getAction();
            //拦截Down事件,然后让父类不进行拦截
            if (action == MotionEvent.ACTION_DOWN) {
                getParent().requestDisallowInterceptTouchEvent(true);
                if (mOnProgressUpdateListener != null) {
                    mOnProgressUpdateListener.onStartTrackingTouch(this);
                }
                return true;
            }
            return super.dispatchTouchEvent(event);
        }
    
        @Override
        public boolean onTouchEvent(MotionEvent event) {
            int action = event.getAction();
            int contentHeight = mViewHeight - getPaddingTop() - getPaddingBottom();
            if (action == MotionEvent.ACTION_DOWN) {
                return true;
            } else if (action == MotionEvent.ACTION_MOVE || action == MotionEvent.ACTION_UP) {
                //Move或Up的时候,计算拽托进度
                float endY = event.getY();
                //限制拉到顶
                if (endY < 0) {
                    endY = 0;
                }
                //限制拉到底
                if (endY > contentHeight) {
                    endY = contentHeight;
                }
                //计算触摸点和高度的差值
                float distanceY = Math.abs(contentHeight - endY);
                float ratio = distanceY / contentHeight;
                //计算百分比应该有的进度:进度 = 总进度 * 进度百分比值
                float progress = mMax * ratio;
                setProgress(progress, true);
                if (action == MotionEvent.ACTION_UP) {
                    if (mOnProgressUpdateListener != null) {
                        mOnProgressUpdateListener.onStopTrackingTouch(this);
                    }
                }
                return true;
            }
            return super.onTouchEvent(event);
        }
    
        /**
         * 处理MeasureSpec
         */
        private int handleMeasure(int measureSpec, boolean isWidth) {
            int result;
            if (isWidth) {
                result = mDefaultWidth;
            } else {
                result = mDefaultHeight;
            }
            int specMode = MeasureSpec.getMode(measureSpec);
            int specSize = MeasureSpec.getSize(measureSpec);
            if (specMode == MeasureSpec.EXACTLY) {
                result = specSize;
            } else {
                //处理wrap_content的情况
                if (specMode == MeasureSpec.AT_MOST) {
                    result = Math.min(result, specSize);
                }
            }
            return result;
        }
    
        /**
         * 设置进度背景颜色
         */
        public void setBgColor(int bgColor) {
            //没有变化,不重绘
            if (bgColor == mBgColor) {
                return;
            }
            this.mBgColor = bgColor;
            mBgPaint.setColor(bgColor);
            invalidate();
        }
    
        /**
         * 设置已有进度的背景颜色
         */
        public void setProgressBgColor(int progressBgColor) {
            //没有变化,不重绘
            if (progressBgColor == mProgressBgColor) {
                return;
            }
            this.mProgressBgColor = progressBgColor;
            mProgressPaint.setColor(progressBgColor);
            invalidate();
        }
    
        /**
         * 设置进度
         */
        public void setProgress(float progress) {
            setProgress(progress, false);
        }
    
        /**
         * 设置进度
         *
         * @param fromUser 是否是用户触摸发生的改变
         */
        public void setProgress(float progress, boolean fromUser) {
            //忽略相同进度的设置
            if (mProgress == progress) {
                return;
            }
    //        if (progress > mMin && progress < mMax) {
    //        }
            this.mProgress = progress;
            invalidate();
            if (mOnProgressUpdateListener != null) {
                mOnProgressUpdateListener.onProgressUpdate(this, progress, fromUser);
            }
        }
    
        /**
         * 获取当前进度
         */
        public float getProgress() {
            return mProgress;
        }
    
        /**
         * 设置进度最小值
         */
        public void setMin(float min) {
            this.mMin = min;
            invalidate();
        }
    
        /**
         * 获取最小进度
         */
        public float getMin() {
            return mMin;
        }
    
        /**
         * 设置进度最大值
         */
        public void setMax(float max) {
            this.mMax = max;
            invalidate();
        }
    
        /**
         * 获取最大进度
         */
        public float getMax() {
            return mMax;
        }
    
        public interface OnProgressUpdateListener {
            /**
             * 按下时回调
             */
            void onStartTrackingTouch(VerticalSeekBar seekBar);
    
            /**
             * 进度更新时回调
             *
             * @param progress 当前进度
             * @param fromUser 是否是用户改变的
             */
            void onProgressUpdate(VerticalSeekBar seekBar, float progress, boolean fromUser);
    
            /**
             * 松手时回调
             */
            void onStopTrackingTouch(VerticalSeekBar seekBar);
        }
    
        public static class SimpleProgressUpdateListener implements OnProgressUpdateListener {
            @Override
            public void onStartTrackingTouch(VerticalSeekBar seekBar) {
            }
    
            @Override
            public void onProgressUpdate(VerticalSeekBar seekBar, float progress, boolean fromUser) {
            }
    
            @Override
            public void onStopTrackingTouch(VerticalSeekBar seekBar) {
            }
        }
    
        public void setOnProgressUpdateListener(
                VerticalSeekBar.OnProgressUpdateListener onProgressUpdateListener) {
            mOnProgressUpdateListener = onProgressUpdateListener;
        }
    
        /**
         * 获取当前进度值比值
         */
        public float getProgressRatio() {
            return mProgress / (mMax * 1.0f);
        }
    
        public static int sp2px(Context context, float spValue) {
            final float fontScale = context.getResources().getDisplayMetrics().scaledDensity;
            return (int) (spValue * fontScale + 0.5f);
        }
    
        public static int dip2px(Context context, float dipValue) {
            final float scale = context.getResources().getDisplayMetrics().density;
            return (int) (dipValue * scale + 0.5f);
        }
    }
    

    使用

    • xml中添加控件,记得改控件的包名
      <com.zh.cavas.sample.widget.VerticalSeekBar
          android:id="@+id/volume_seek_bar"
          android:layout_width="36dp"
          android:layout_height="114dp"
          tools:vsb_progress="50"
          app:vsb_min_progress="1"
          app:vsb_max_progress="100"
          app:vsb_bg="#EDF0FA"
          app:vsb_progress_bg="#3DDA9B"
          app:vsb_bg_radius="8dp" />
    
    • 设置监听回调,最小值、最大值,当前进度
    VerticalSeekBar volumeSeekBar = findViewById(R.id.volume_seek_bar);
    volumeSeekBar.setOnProgressUpdateListener(new VerticalSeekBar.SimpleProgressUpdateListener() {
        @SuppressLint("SetTextI18n")
        @Override
        public void onProgressUpdate(VerticalSeekBar seekBar, float progress, boolean fromUser) {
            //设置音量
            Log.d(TAG, "VolumeSeekBar onProgressUpdate => progress = " + progress);
        }
    });
    volumeSeekBar.setMin(0f);
    volumeSeekBar.setMax(1f);
    volumeSeekBar.setProgress(0.6f);
    

    相关文章

      网友评论

        本文标题:Android 自定义View 音量控制View

        本文链接:https://www.haomeiwen.com/subject/feukyltx.html