Android自定义带有圆形进度条的可长按控件功能

2022-06-15 18:58:50

这几天有在学习Jetpack中CameraX的内容,在拍摄视频的时候想着做一个自定义带有进度条的可长按控件,用来显示拍摄进度,故记录下来与大家分享!效果如下:

(篇幅过长是因为有代码解析过程,可直接到最后查看完整代码)

这个控件较为简易,从效果中可以看出,控件模拟了单击拍照,长按可以录制视频的功能,中途松手或者时间到都可以停止录制

思路很简单,使用简单的画笔工具就可以完成这个控件

    继承自View定义自定义属性并获取定义填充样式的画笔onMeasure中测量大小,onDraw中绘制圆与扇形监听长按监听开始定时器并刷新画布,监听触摸事件进行结束的回调

    以上就是全部的思路了,代码拆解如下:

    (一)继承自View并实现构造方法,代码如下:

    public class LongClickView extends View {
        public int DEFAULT_MAX_SECONDS = 15;
        public int DEFAULT_ANNULUS_WIDTH = 5;
        public int DEFAULT_ANNULUS_COLOR;
        public int DEFAULT_RATE = 50;
        private Paint mSmallCirclePaint;
        private Paint mMiddenCirclePaint;
        private Paint mBigCirclePaint;
        private Paint mAngleCirclePaint;
        private int mWidthSize;
        private Timer mTimer;//计时器
        private AtomicInteger mCount = new AtomicInteger(0);
        private MyClickListener mMyClickListener;
        private boolean mIsFinish = true;
        private long mStartTime;//点击的时间
        private long mEndTime;//点击结束的时间
        private int mMaxSeconds;
        private int mDelayMilliseconds;
        private int mAnnulusColor;
        private float mAnnulusWidth;
    
        public interface MyClickListener {
            void longClickFinish();//长按结束
    
            void singleClickFinish();//单击结束
        }
    
        public void setMyClickListener(MyClickListener myClickListener) {
            mMyClickListener = myClickListener;
        }
    
        public LongClickView(Context context) {
            this(context, null);
        }
    
        public LongClickView(Context context, @Nullable AttributeSet attrs) {
            this(context, attrs, 0);
        }
    
        public LongClickView(Context context, @Nullable AttributeSet attrs, int defStyleAttr)                 {
            super(context, attrs, defStyleAttr);
            getAttrs(context, attrs);
            initView();
        }
    }

    (二)定义并获取自定义属性,属性以及获取属性代码如下:

    attr_long_click_view.xml

    <?xml version="1.0" encoding="utf-8"?>
    <resources>
        <declare-styleable name="LongClickView">
            <attr name="maxSeconds" format="integer" />
            <attr name="annulusWidth" format="integer" />
            <attr name="annulusColor" format="color" />
            <attr name="delayMilliseconds" format="integer" />
        </declare-styleable>
    </resources>
     private void getAttrs(Context context, @Nullable AttributeSet attrs) {
            TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.LongClickView);
            //maxSeconds 最大的秒数
            mMaxSeconds = typedArray.getInt(R.styleable.LongClickView_maxSeconds, DEFAULT_MAX_SECONDS);
            //annulusWidth 圆环的宽度
            mAnnulusWidth = typedArray.getInt(R.styleable.LongClickView_annulusWidth, DEFAULT_ANNULUS_WIDTH);
            //annulusColor 圆环的颜色
            DEFAULT_ANNULUS_COLOR = context.getResources().getColor(R.color.color_grey);
            mAnnulusColor = typedArray.getColor(R.styleable.LongClickView_annulusColor, DEFAULT_ANNULUS_COLOR);
            //delayMilliseconds 进度条隔多少时间走一次,值越小走的越快,显得更流畅
            mDelayMilliseconds = typedArray.getInt(R.styleable.LongClickView_delayMilliseconds, DEFAULT_RATE);
        }

    (三)定义画笔工具 的代码如下:

        private void initView() {
            mBigCirclePaint = new Paint();
            mSmallCirclePaint = new Paint();
            mMiddenCirclePaint = new Paint();
            mAngleCirclePaint = new Paint();
            mBigCirclePaint.setStyle(Paint.Style.FILL);
            mBigCirclePaint.setColor(Color.LTGRAY);
            mBigCirclePaint.setAntiAlias(true);
            mBigCirclePaint.setStrokeWidth(5);
            mSmallCirclePaint.setStrokeWidth(5);
            mSmallCirclePaint.setAntiAlias(true);
            mSmallCirclePaint.setColor(Color.WHITE);
            mSmallCirclePaint.setStyle(Paint.Style.FILL);
    
            mMiddenCirclePaint.setStrokeWidth(5);
            mMiddenCirclePaint.setAntiAlias(true);
            mMiddenCirclePaint.setColor(Color.LTGRAY);
            mMiddenCirclePaint.setStyle(Paint.Style.FILL);
            mAngleCirclePaint.setStrokeWidth(5);
            mAngleCirclePaint.setAntiAlias(true);
            mAngleCirclePaint.setColor(mAnnulusColor);
            mAngleCirclePaint.setStyle(Paint.Style.FILL);
            ...//这里是长按监听
    
        }

    (四)onMeasure中测量大小,onDraw中绘制圆与扇形,代码如下:

    onMeasure中,如果没有定义实际宽高就会使用父组件的宽高,如果有实际宽高便会使用自己的宽高

     @Override
        protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
            super.onMeasure(widthMeasureSpec, heightMeasureSpec);
            mWidthSize = MeasureSpec.getSize(widthMeasureSpec);
            setMeasuredDimension(mWidthSize, mWidthSize);
        }

    onDraw中,一共有三层圆形填充绘制以及一层扇形填充绘制,先绘制最外层的灰色圆形,再根据此时的进度绘制一定角度的扇形,然后覆盖一层灰色的圆形,最后在覆盖上一层白色的中心圆,并且在绘制过程以及绘制结束时的中心圆半径不同。代码如下:

     @Override
        protected void onDraw(Canvas canvas) {
            canvas.drawCircle(mWidthSize / 2, mWidthSize / 2, mWidthSize / 2, mBigCirclePaint);//最外层的填充圆
            RectF rectF = new RectF(0, 0, mWidthSize, mWidthSize);//进度扇形
            if (mCount.get() > 0) {
                //求出每一次定时器执行所绘制的扇形度数
                float perAngle = 360f / mMaxSeconds / (1000f / mDelayMilliseconds);
                canvas.drawArc(rectF, 0, perAngle * mCount.get(), true, mAngleCirclePaint);
            }
            canvas.drawCircle(mWidthSize / 2, mWidthSize / 2, mWidthSize / 2 - mAnnulusWidth, mMiddenCirclePaint);//中间一层灰色的圆
            //最后绘制中心圆
            if (mIsFinish) {
                canvas.drawCircle(mWidthSize / 2, mWidthSize / 2, mWidthSize / 2 - mAnnulusWidth, mSmallCirclePaint);
            } else {
                canvas.drawCircle(mWidthSize / 2, mWidthSize / 2, mWidthSize / 8, mSmallCirclePaint);
            }
            super.onDraw(canvas);
        }

    (五)监听长按监听开始定时器并刷新画布,监听触摸事件进行结束的回调,定时器使用的是Timer类,当时间超过自定义的最大秒数时就会自动停止,并定时刷新画布,代码如下:

            this.setOnLongClickListener(new OnLongClickListener() {
                @Override
                public boolean onLongClick(View v) {
                    mIsFinish = false;
                    mCount.set(0);
                    mTimer = new Timer();
                    mTimer.schedule(new TimerTask() {
                        @Override
                        public void run() {
                            mCount.addAndGet(1);
                            invalidate();
                            if (mCount.get() * mDelayMilliseconds >= mMaxSeconds * 1000) {
                                mCount.set(0);
                                this.cancel();
                                invalidate();
                                mIsFinish = true;
                                if (mMyClickListener != null) {
                                    mMyClickListener.longClickFinish();
                                }
                            }
                        }
                    }, 0, mDelayMilliseconds);
                    return true;
                }
            });
     @Override
        public boolean onTouchEvent(MotionEvent event) {
            if (event.getAction() == MotionEvent.ACTION_UP) {
                mEndTime = System.currentTimeMillis();
                new MyAsyncTask().execute();
            } else if (event.getAction() == MotionEvent.ACTION_DOWN) {
                mStartTime = System.currentTimeMillis();
            }
            return super.onTouchEvent(event);
        }

    将定时器停止与停止后的判断逻辑放在AsyncTask中编写,确保定时器不会继续处理逻辑之后再做判断

        public class MyAsyncTask extends AsyncTask<Void, Void, Void> {
            @Override
            protected Void doInBackground(Void... voids) {
                if (mTimer != null) {
                    mTimer.cancel();
                }
                return null;
            }
            @Override
            protected void onPostExecute(Void aVoid) {
                //使用时间戳的差来判断是单击或者长按
                if (mEndTime - mStartTime > 1000) {
                    //防止在自动结束后松开手指又重新调用了一次长按结束的回调
                    if (!mIsFinish) {
                        if (mMyClickListener != null) {
                            mMyClickListener.longClickFinish();
                        }
                    }
                } else {
                    //若是单击就清除进度条
                    mCount.set(0);
                    invalidate();
                    if (mMyClickListener != null) {
                        mMyClickListener.singleClickFinish();
                    }
                }
                mIsFinish = true;
            }
        }

     

    结束后的回调类代码如下:

       public interface MyClickListener {
            void longClickFinish();//长按结束
    
            void singleClickFinish();//单击结束
        }

    最后,完整的代码如下,自定义属性上方有贴出来代码:

    public class LongClickView extends View {
        public int DEFAULT_MAX_SECONDS = 15;
        public int DEFAULT_ANNULUS_WIDTH = 5;
        public int DEFAULT_ANNULUS_COLOR;
        public int DEFAULT_RATE = 50;
        private Paint mSmallCirclePaint;
        private Paint mMiddenCirclePaint;
        private Paint mBigCirclePaint;
        private Paint mAngleCirclePaint;
        private int mWidthSize;
        private Timer mTimer;//计时器
        private AtomicInteger mCount = new AtomicInteger(0);
        private MyClickListener mMyClickListener;
        private boolean mIsFinish = true;
        private long mStartTime;//点击的时间
        private long mEndTime;//点击结束的时间
        private int mMaxSeconds;
        private int mDelayMilliseconds;
        private int mAnnulusColor;
        private float mAnnulusWidth;
        public interface MyClickListener {
            void longClickFinish();//长按结束
            void singleClickFinish();//单击结束
        }
        public void setMyClickListener(MyClickListener myClickListener) {
            mMyClickListener = myClickListener;
        }
        public LongClickView(Context context) {
            this(context, null);
        }
        public LongClickView(Context context, @Nullable AttributeSet attrs) {
            this(context, attrs, 0);
        }
        public LongClickView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
            super(context, attrs, defStyleAttr);
            getAttrs(context, attrs);
            initView();
        }
        private void getAttrs(Context context, @Nullable AttributeSet attrs) {
            TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.LongClickView);
            //maxSeconds 最大的秒数
            mMaxSeconds = typedArray.getInt(R.styleable.LongClickView_maxSeconds, DEFAULT_MAX_SECONDS);
            //annulusWidth 圆环的宽度
            mAnnulusWidth = typedArray.getInt(R.styleable.LongClickView_annulusWidth, DEFAULT_ANNULUS_WIDTH);
            //annulusColor 圆环的颜色
            DEFAULT_ANNULUS_COLOR = context.getResources().getColor(R.color.color_grey);
            mAnnulusColor = typedArray.getColor(R.styleable.LongClickView_annulusColor, DEFAULT_ANNULUS_COLOR);
            //delayMilliseconds 进度条隔多少时间走一次,值越小走的越快,显得更流畅
            mDelayMilliseconds = typedArray.getInt(R.styleable.LongClickView_delayMilliseconds, DEFAULT_RATE);
        }
        private static final String TAG = "LongClickView";
        private void initView() {
            mBigCirclePaint = new Paint();
            mSmallCirclePaint = new Paint();
            mMiddenCirclePaint = new Paint();
            mAngleCirclePaint = new Paint();
            mBigCirclePaint.setStyle(Paint.Style.FILL);
            mBigCirclePaint.setColor(Color.LTGRAY);
            mBigCirclePaint.setAntiAlias(true);
            mBigCirclePaint.setStrokeWidth(5);
            mSmallCirclePaint.setStrokeWidth(5);
            mSmallCirclePaint.setAntiAlias(true);
            mSmallCirclePaint.setColor(Color.WHITE);
            mSmallCirclePaint.setStyle(Paint.Style.FILL);
            mMiddenCirclePaint.setStrokeWidth(5);
            mMiddenCirclePaint.setAntiAlias(true);
            mMiddenCirclePaint.setColor(Color.LTGRAY);
            mMiddenCirclePaint.setStyle(Paint.Style.FILL);
            mAngleCirclePaint.setStrokeWidth(5);
            mAngleCirclePaint.setAntiAlias(true);
            mAngleCirclePaint.setColor(mAnnulusColor);
            mAngleCirclePaint.setStyle(Paint.Style.FILL);
            this.setOnLongClickListener(new OnLongClickListener() {
                @Override
                public boolean onLongClick(View v) {
                    mIsFinish = false;
                    mCount.set(0);
                    mTimer = new Timer();
                    mTimer.schedule(new TimerTask() {
                        @Override
                        public void run() {
                            mCount.addAndGet(1);
                            invalidate();
                            if (mCount.get() * mDelayMilliseconds >= mMaxSeconds * 1000) {
                                mCount.set(0);
                                this.cancel();
                                invalidate();
                                mIsFinish = true;
                                if (mMyClickListener != null) {
                                    mMyClickListener.longClickFinish();
                                }
                            }
                        }
                    }, 0, mDelayMilliseconds);
                    return true;
                }
            });
        }
        @Override
        protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
            super.onMeasure(widthMeasureSpec, heightMeasureSpec);
            mWidthSize = MeasureSpec.getSize(widthMeasureSpec);
            setMeasuredDimension(mWidthSize, mWidthSize);
        }
        @Override
        protected void onDraw(Canvas canvas) {
            canvas.drawCircle(mWidthSize / 2, mWidthSize / 2, mWidthSize / 2, mBigCirclePaint);//最外层的填充圆
            RectF rectF = new RectF(0, 0, mWidthSize, mWidthSize);//进度扇形
            if (mCount.get() > 0) {
                //求出每一次定时器执行所绘制的扇形度数
                float perAngle = 360f / mMaxSeconds / (1000f / mDelayMilliseconds);
                canvas.drawArc(rectF, 0, perAngle * mCount.get(), true, mAngleCirclePaint);
            }
            canvas.drawCircle(mWidthSize / 2, mWidthSize / 2, mWidthSize / 2 - mAnnulusWidth, mMiddenCirclePaint);//中间一层灰色的圆
            //最后绘制中心圆
            if (mIsFinish) {
                canvas.drawCircle(mWidthSize / 2, mWidthSize / 2, mWidthSize / 2 - mAnnulusWidth, mSmallCirclePaint);
            } else {
                canvas.drawCircle(mWidthSize / 2, mWidthSize / 2, mWidthSize / 8, mSmallCirclePaint);
            }
            super.onDraw(canvas);
        }
        @Override
        public boolean onTouchEvent(MotionEvent event) {
            if (event.getAction() == MotionEvent.ACTION_UP) {
                mEndTime = System.currentTimeMillis();
                new MyAsyncTask().execute();
            } else if (event.getAction() == MotionEvent.ACTION_DOWN) {
                mStartTime = System.currentTimeMillis();
            }
            return super.onTouchEvent(event);
        }
        public class MyAsyncTask extends AsyncTask<Void, Void, Void> {
            @Override
            protected Void doInBackground(Void... voids) {
                if (mTimer != null) {
                    mTimer.cancel();
                }
                return null;
            }
            @Override
            protected void onPostExecute(Void aVoid) {
                //使用时间戳的差来判断是单击或者长按
                if (mEndTime - mStartTime > 1000) {
                    //防止在结束后松开手指有重新调用了一次长按结束的回调
                    if (!mIsFinish) {
                        if (mMyClickListener != null) {
                            mMyClickListener.longClickFinish();
                        }
                    }
                } else {
                    mCount.set(0);
                    invalidate();
                    if (mMyClickListener != null) {
                        mMyClickListener.singleClickFinish();
                    }
                }
                mIsFinish = true;
            }
        }
    }

    使用的代码如下:

    activity_long_click_view.xml

    <?xml version="1.0" encoding="utf-8"?>
    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:app="http://schemas.android.com/apk/res-auto"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:gravity="center"
        android:orientation="vertical">
    
        <com.example.customerview.long_click_view.LongClickView
            android:id="@+id/long_click_view"
            android:layout_width="100dp"
            android:layout_height="wrap_content"
            app:annulusColor="@color/color_2196F3"
            app:annulusWidth="20"
            app:delayMilliseconds="40"
            app:maxSeconds="4" />
    
        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginTop="20dp"
            android:text="长按录制视频,单击拍照"
            android:textColor="@color/colorBlack"
            android:textSize="20dp" />
    </LinearLayout>

    LongClickViewActivity.java

            mLongClickView.setMyClickListener(new LongClickView.MyClickListener() {
                @Override
                public void longClickFinish() {
                    runOnUiThread(new Runnable() {
                        @Override
                        public void run() {
                            Toast.makeText(LongClickViewActivity.this, "长按结束", Toast.LENGTH_SHORT).show();
                        }
                    });
                }
    
                @Override
                public void singleClickFinish() {
                    runOnUiThread(new Runnable() {
                        @Override
                        public void run() {
                            Toast.makeText(LongClickViewActivity.this, "单击结束", Toast.LENGTH_SHORT).show();
                        }
                    });
                }
            });

    到此这篇关于Android自定义带有圆形进度条的可长按控件功能的文章就介绍到这了,更多相关Android圆形进度条内容请搜索易采站长站以前的文章或继续浏览下面的相关文章希望大家以后多多支持易采站长站!