本文地址:https://blog.csdn.net/qq_40785165/article/details/115265201,转载需附上此地址
大家好,我是小黑,一个还没秃头的程序员~~~
永远不要沉溺在安逸里得过且过,能给你遮风挡雨的,同样能让你不见天日,只有让自己更加强大,才能真正撑起一片天。
这几天有在学习Jetpack中CameraX的内容,在拍摄视频的时候想着做一个自定义带有进度条的可长按控件,用来显示拍摄进度,故记录下来与大家分享!效果如下:
(篇幅过长是因为有代码解析过程,可直接到最后查看完整代码)
这个控件较为简易,从效果中可以看出,控件模拟了单击拍照,长按可以录制视频的功能,中途松手或者时间到都可以停止录制
思路很简单,使用简单的画笔工具就可以完成这个控件
以上就是全部的思路了,代码拆解如下:
(一)继承自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
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 {
@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 {
@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
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();
}
});
}
});
这样一来,一个带有圆形进度的可长按控件就完成了,如果需要在中间显示文字可以使用Rect+Paint进行绘制,大家感兴趣的话可以亲自试一下,最后,希望喜欢我文章的朋友们可以帮忙点赞、收藏、评论,也可以关注一下,如果有问题可以在评论区提出,我后面会继续写关于自己自定义控件的经验的博客,与大家分享,谢谢大家的支持与阅读!