Android自定義帶有圓形進度條的可長按控件功能

這幾天有在學習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圓形進度條內容請搜索WalkonNet以前的文章或繼續瀏覽下面的相關文章希望大傢以後多多支持WalkonNet!

推薦閱讀: