这几天有在学习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
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(); } }); } });