UI效果图
功能
- 长按进度条增加
- 进度条满一周时通知外部倒计时结束
- 松开时则会自动取消进度,并且通知外部
- 可以自定义开始的角度
- 可以自定义倒计长
实现
中间一张图,外面绘制个底圈,再绘制一个上层的圈即可。
当用户按下时,则开始增加角度。当用户手松开时进度归零。
由上分析
创建一个继承于ImageView的控件
public class PausePressView extends AppCompatImageView {
public PausePressView(@NonNull Context context) {
this(context, null);
}
public PausePressView(@NonNull Context context, @Nullable AttributeSet attrs) {
this(context, attrs, 0);
}
public PausePressView(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
}
初始化画笔
private void initPaint() {
paint = new Paint();
paint.setColor(getContext().getResources().getColor(R.color.c_FF6E66));
paint.setStyle(Paint.Style.STROKE);
paint.setAntiAlias(true);
paint.setStrokeCap(Paint.Cap.ROUND);
paint.setStrokeWidth(10);
}
- 颜色
- 类型(stroke,只有外框,fill则填充,也有fill_and_stroke)
- 抗锯齿
- 线头圆角帽
- 线的宽度
设置外边距,并且初始化画笔
public PausePressView(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
initPaint();
setPadding(20, 20, 20, 20);
}
也就是让图片收缩20个px,这个大小同学们根据自己的需求进行调整
如上图,也就是图片会在内部缩小间距20px.
测量
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
ViewGroup.LayoutParams layoutParams = this.getLayoutParams();
if (layoutParams != null) {
defaultWidth = layoutParams.width;
defaultHeight = layoutParams.height;
}
//获取到图片资源的大小,然后设置大小
setMeasuredDimension(defaultWidth, defaultHeight);
}
设置多少就用多少吧
绘制
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
int measuredWidth = getMeasuredWidth();
int measuredHeight = getMeasuredHeight();
if (rect == null) {
rect = new RectF(10, 10, measuredWidth - 10, measuredHeight - 10);
}
paint.setColor(getContext().getResources().getColor(R.color.c_FFE7E6));
//绘制底线
canvas.drawArc(rect, -90, 360, false, paint);
paint.setColor(getContext().getResources().getColor(R.color.c_FF6E66));
//绘制前景线
canvas.drawArc(rect, -90, 180, false, paint);
}
- super.onDraw(canvas); 先绘制图片
- 在图片绘制好以后,我们绘制扇形外圈
到这里,我们的静态UI就绘制好了。
事件处理
@Override
public boolean onTouchEvent(MotionEvent event) {
int action = event.getAction();
switch (action) {
case MotionEvent.ACTION_DOWN:
//开始倒计时
this.isRelease = false;
startCountDown();
break;
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_CANCEL:
//结束倒计时
this.isRelease = true;
break;
}
/*消费事件*/
return true;
}
开始倒计时
//倒计时的时长
private int countDownDuration = 2;
//当前的进度
private float currentProgress = 0;
//手是否有释放
private boolean isRelease = false;
/**
* 开始倒计时
*/
private void startCountDown() {
if (this.currentProgress >= 360) {
this.currentProgress = 0;
}
//换算成毫秒
int duration;
if (!isRelease) {
duration = countDownDuration * 1000;
} else {
//往回倒时只要1秒的动画即可
duration = 1000;
}
//第20毫秒绘制一次进度
int rate = duration / 20;
//求度数
float perDegree = 360 * 1.0f / rate;
post(new Runnable() {
@Override
public void run() {
if (!isRelease) {
currentProgress += perDegree;
} else {
currentProgress -= perDegree;
}
if (currentProgress < 0) {
currentProgress = 0;
}
if (currentProgress >= 360) {
//结束了
currentProgress = 360;
//TODO:告诉外部
}
//重新绘制
invalidate();
if (currentProgress > 0 && currentProgress < 360) {
postDelayed(this, 20);
}
}
});
}
当然,绘制的地方字段要修改了
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
int measuredWidth = getMeasuredWidth();
int measuredHeight = getMeasuredHeight();
if (rect == null) {
rect = new RectF(10, 10, measuredWidth - 10, measuredHeight - 10);
}
paint.setColor(getContext().getResources().getColor(R.color.c_FFE7E6));
//绘制底线
canvas.drawArc(rect, -90, 360, false, paint);
paint.setColor(getContext().getResources().getColor(R.color.c_FF6E66));
//绘制前景线
canvas.drawArc(rect, -90, currentProgress, false, paint);
}
到这一步,动起来的效果就有了
告诉外部当前状态
定义接口
public interface OnCountDownStateChangeListener {
//倒计时结束
void onCountDownEnd();
//用户取消倒计时
void onCountDownCancel();
}
设置接口
public void setOnCountDownStateChangeListener(OnCountDownStateChangeListener listener) {
this.mOnCountDownStateChangeListener = listener;
}
当倒计时结束时调用接口
/**
* 开始倒计时
*/
private void startCountDown() {
if (this.currentProgress >= 360) {
this.currentProgress = 0;
}
//换算成毫秒
int duration;
if (!isRelease) {
duration = countDownDuration * 1000;
} else {
//往回倒时只要1秒的动画即可
duration = 1000;
}
//第20毫秒绘制一次进度
int rate = duration / 20;
//求度数
float perDegree = 360 * 1.0f / rate;
post(new Runnable() {
@Override
public void run() {
if (!isRelease) {
currentProgress += perDegree;
} else {
currentProgress -= perDegree;
}
if (currentProgress < 0) {
currentProgress = 0;
}
if (currentProgress >= 360) {
//结束了
currentProgress = 360;
//告诉外部
if (mOnCountDownStateChangeListener != null) {
mOnCountDownStateChangeListener.onCountDownEnd();
}
}
//重新绘制
invalidate();
if (currentProgress > 0 && currentProgress < 360) {
postDelayed(this, 20);
}
}
});
}
当用户手释放的时候:
@Override
public boolean onTouchEvent(MotionEvent event) {
int action = event.getAction();
switch (action) {
case MotionEvent.ACTION_DOWN:
//开始倒计时
this.isRelease = false;
startCountDown();
break;
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_CANCEL:
//结束倒计时
this.isRelease = true;
//告诉外部
if (currentProgress < 360 && mOnCountDownStateChangeListener != null) {
mOnCountDownStateChangeListener.onCountDownCancel();
}
break;
}
/*消费事件*/
return true;
}
到这里,基本功能就完成了。
还需要暴露设置属性方法,以及定义自定义属性的配置。
这个很简单,详情就不多说了,可以去参考一下我们的自定义控件课程。
推一下客户端下载哈:
可以去看看断点的个人中心
可以看到侧栏会有下载应用的二维码,快去下载吧。
今天就水到这里了,本文会同步到微信公众号,希望大家不要随便点击广告。