Android圆形进度条
移动端开发_小黑 人气:0这几天有在学习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(); } }); } });
加载全部内容