自定义SurfaceView之音频录制圆形进度条
2017-02-21 11:42
381 查看
本篇文章介绍自定义SurfaceView来实现如下的效果
由于对于SurfaceView不是很熟练,这次拿它来练手
SurfaceView使用介绍可以参考另外一篇博客:Android绘图机制与处理技巧(一)SurfaceView
灰色总进度条
绿色圆形进度条
绿色小圆圈
然后需要重写三个方法:surfaceCreated(),surfaceChanged(),surfaceDestroyed()和run()
定义的成员变量,具体用途可以看注释
然后我们需要对SurfaceHolder以及一些其他的属性初始化
重写SurfaceView的surfaceCreated()方法
在这里根据子线程标志位做初始化,以及计算半径,中心点坐标等等,并开启线程
由于我们不用改变SurfaceView大小因此无需在surfaceChanged()方法中写逻辑
重写SurfaceView的surfaceDestroyed()方法,在这里将标志位设为false
然后重写run()方法,该方法是一个子线程,在这里通过不停循环才进行绘制界面
有几个要注意的地方:
lockCanvas()用来获取当前canvas绘图对象,绘制完毕后通过holder.unlockCanvasAndPost(canvas)方法来提交画布内容
由于每次循环都是通过lockCanvas()来获取的canvas对象,因此会保留上一次的绘图操作,所以我们每次回之前都需要通过drawcolor()来进行清屏操作
isSustainedDraw标记位来判断手指持续按住屏幕的状态,在按下的状态下才会刷新
isChangeCenterBitmap标记位用来在手指不触碰屏幕的情况下限制屏幕只刷新一次,节省资源
使用Thread.sleep(correctSleepTime)尽可能的节省系统资源
画按钮图形的方法
通过isSustainedDraw判断后使用canvas.drawBitmap()方法绘制相应的图片
通过这个方法绘制灰色大圆、绿色圆点和圆弧
画大圆较简单,设置好paint属性后使用drawCircle()方法即可
画圆弧也比较容易,设置好RectF类的坐标和paint属性后,使用drawArc()方法即可
需要注意的是画圆点,重点在于怎么计算圆点围绕中心旋转时的坐标:
我们可以使用Math.cos(radians) * radius来计算X轴偏移量,Math.sin(radians) * radius来计算Y轴偏移量,radians表示弧度,可以通过angle * Math.PI / 180来计算
用到的包装方法,可以设置一些属性
xml布局
使用方法
如果要写其他逻辑的话在onTouch方法里添加即可
代码示例:
CustomViewSamples
由于对于SurfaceView不是很熟练,这次拿它来练手
SurfaceView用途:
一般View可以满足大部分的绘图需求,但如果需要并发执行复杂耗时的逻辑的时候,就会不断阻塞主线程,导致画面卡顿,为了避免这种问题的发生,我们应该使用SurfaceView来解决这个问题SurfaceView使用介绍可以参考另外一篇博客:Android绘图机制与处理技巧(一)SurfaceView
总共由以下几个部分组成:
按钮按下效果灰色总进度条
绿色圆形进度条
绿色小圆圈
具体实现过程分析:
创建自定义SurfaceView需要继承自SurfaceView,并实现SurfaceHolder.Callback, Runnable接口public class CircleRecordSurfaceView extends SurfaceView implements SurfaceHolder.Callback, Runnable {
然后需要重写三个方法:surfaceCreated(),surfaceChanged(),surfaceDestroyed()和run()
定义的成员变量,具体用途可以看注释
//选择按钮图标 private boolean isChangeCenterBitmap = true; //持续画图 private boolean isSustainedDraw = false; private boolean isStart = true; //是否画小圆点,默认为true private boolean isDrawSmallCircle = true; //小圆点颜色 private int smallCircleColor; //是否画圆弧,默认为true private boolean isDrawArc = true; //圆弧颜色 private int arcColor; private CompleteTimeCallBack completeTimeCallBack; private SurfaceHolder holder = null; //绘图属性--------- private Canvas canvas; //录音按钮 private Paint pPaint; private int px;//坐标x位置 private int py;//坐标y位置 //radius = defaultRadius * dp private int radius;//半径 //defaultRadius 默认值为40 private int defaultRadius = 40; //起始角度 private float startAngle = 270; //进度 private float sweepAngle; //小球起始角度默认等于进度条起始角度 private float angle, duration = 20; private int startBitmap; private int stopBitmap; //中心图片的范围,默认为10,值越大图片越小 private int centerBitmap_margin = 10; private int dp; private Bitmap bitmap; private boolean isGetBitmap = false; long a, b, calculateTime, sleepTime = 60, correctSleepTime;
然后我们需要对SurfaceHolder以及一些其他的属性初始化
public void init() { //获取mSurfaceHolder holder = getHolder(); holder.addCallback(this); //背景设为透明 if (!isInEditMode()) { setZOrderOnTop(true); } holder.setFormat(PixelFormat.TRANSLUCENT); //设置进度条 pPaint = new Paint(); pPaint.setAntiAlias(true); pPaint.setStrokeWidth(4); pPaint.setStyle(Paint.Style.STROKE); angle = startAngle; sweepAngle = 0; dp = Resources.getSystem().getDisplayMetrics().densityDpi / 160; }
重写SurfaceView的surfaceCreated()方法
@Override public void surfaceCreated(SurfaceHolder holder) { if (!isStart) { reset(); } radius = defaultRadius * dp; isChangeCenterBitmap = true; px = this.getWidth() / 2; py = this.getHeight() / 2; new Thread(this).start(); }
在这里根据子线程标志位做初始化,以及计算半径,中心点坐标等等,并开启线程
由于我们不用改变SurfaceView大小因此无需在surfaceChanged()方法中写逻辑
@Override public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) { }
重写SurfaceView的surfaceDestroyed()方法,在这里将标志位设为false
@Override public void surfaceDestroyed(SurfaceHolder holder) { isStart = false; }
然后重写run()方法,该方法是一个子线程,在这里通过不停循环才进行绘制界面
@Override public void run() { while (isStart) { if (isSustainedDraw) { canvas = holder.lockCanvas(); // 获得画布对象,开始对画布画画 if (canvas == null) { continue; } canvas.drawColor(Color.TRANSPARENT, PorterDuff.Mode.CLEAR); // canvas.drawColor(canvasColor); // 把画布填充指定颜色 drawCenterBitmap(); drawCircle(); holder.unlockCanvasAndPost(canvas); // 完成画画,把画布显示在屏幕上 calculateTime = calculateTime + sleepTime; try { b = a; a = System.currentTimeMillis(); if (b == 0) { correctSleepTime = sleepTime; } else { if ((a - b) >= sleepTime && (a - b) < 2 * sleepTime) { correctSleepTime = sleepTime - (a - b - correctSleepTime); } else if ((a - b) > 2 * sleepTime) { correctSleepTime = 0; //不睡眠 } else if ((a - b) < sleepTime) { correctSleepTime = sleepTime - (a - b - correctSleepTime); } } if (correctSleepTime > 0) { Thread.sleep(correctSleepTime); } } catch (InterruptedException e) { e.printStackTrace(); } } else if (isChangeCenterBitmap) { canvas = holder.lockCanvas(); // 获得画布对象,开始对画布画画 if (canvas == null) { continue; } canvas.drawColor(Color.TRANSPARENT, PorterDuff.Mode.CLEAR); // canvas.drawColor(canvasColor); // 把画布填充指定颜色 drawCenterBitmap(); drawCircle(); holder.unlockCanvasAndPost(canvas); // 完成画画,把画布显示在屏幕上 } } }
有几个要注意的地方:
lockCanvas()用来获取当前canvas绘图对象,绘制完毕后通过holder.unlockCanvasAndPost(canvas)方法来提交画布内容
由于每次循环都是通过lockCanvas()来获取的canvas对象,因此会保留上一次的绘图操作,所以我们每次回之前都需要通过drawcolor()来进行清屏操作
isSustainedDraw标记位来判断手指持续按住屏幕的状态,在按下的状态下才会刷新
isChangeCenterBitmap标记位用来在手指不触碰屏幕的情况下限制屏幕只刷新一次,节省资源
使用Thread.sleep(correctSleepTime)尽可能的节省系统资源
画按钮图形的方法
private void drawCenterBitmap() { int area = radius - centerBitmap_margin * dp; RectF imageRect = new RectF(px - area, py - area, px + area, py + area); if (isChangeCenterBitmap) { if (!isGetBitmap) { if (isSustainedDraw) { bitmap = BitmapFactory.decodeResource(getResources(), stopBitmap); } else { bitmap = BitmapFactory.decodeResource(getResources(), startBitmap); } } isChangeCenterBitmap = false; } canvas.drawBitmap(bitmap, null, imageRect, pPaint); }
通过isSustainedDraw判断后使用canvas.drawBitmap()方法绘制相应的图片
通过这个方法绘制灰色大圆、绿色圆点和圆弧
public void drawCircle() { //绘制大圆 pPaint.setColor(Color.LTGRAY); pPaint.setStyle(Paint.Style.STROKE); canvas.drawCircle(px, py, radius, pPaint); //绘制原点 if (isDrawSmallCircle) { pPaint.setColor(smallCircleColor); pPaint.setStyle(Paint.Style.FILL); //radians=angle * Math.PI / 180 角度转弧度公式 //Math.cos(radians) * defaultRadius cos计算x轴偏移量,sin计算y轴偏移量 float ballX = (float) (px + radius * Math.cos(angle * Math.PI / 180)); float ballY = (float) (py + radius * Math.sin(angle * Math.PI / 180)); canvas.drawCircle(ballX, ballY, 4 * dp, pPaint); } //绘制圆弧 if (isDrawArc) { pPaint.setStyle(Paint.Style.STROKE); // pPaint.setColor(Color.parseColor(arcColor)); pPaint.setColor(arcColor); RectF rect = new RectF(px - radius, py - radius, px + radius, py + radius); canvas.drawArc(rect, startAngle, sweepAngle, false, pPaint);//画弧形 } float speed = 360 / (duration * (1000 / sleepTime)); // Log.i(TAG, "drawCircle: speed:"+speed); angle = angle + speed; if (angle > 360) { angle = 0; } sweepAngle = sweepAngle + speed; if (sweepAngle > 360) { reset(); if (completeTimeCallBack != null) { completeTimeCallBack.stop(); } isSustainedDraw = false; isChangeCenterBitmap = true; } }
画大圆较简单,设置好paint属性后使用drawCircle()方法即可
画圆弧也比较容易,设置好RectF类的坐标和paint属性后,使用drawArc()方法即可
需要注意的是画圆点,重点在于怎么计算圆点围绕中心旋转时的坐标:
我们可以使用Math.cos(radians) * radius来计算X轴偏移量,Math.sin(radians) * radius来计算Y轴偏移量,radians表示弧度,可以通过angle * Math.PI / 180来计算
用到的包装方法,可以设置一些属性
public void startDraw() { isChangeCenterBitmap = true; isSustainedDraw = true; } public void stopDraw() { isChangeCenterBitmap = true; isSustainedDraw = false; } public void reset() { isStart = true; angle = startAngle; sweepAngle = 0; } //设置圆弧颜色,用#RRGGBB 或者 #AARRGGBB public void setArcColor(int arcColor) { this.arcColor = arcColor; } //设置小圆点颜色,用#RRGGBB 或者 #AARRGGBB public void setSmallCircleColor(int smallCircleColor) { this.smallCircleColor = smallCircleColor; } public void setDefaultRadius(int defaultRadius) { this.defaultRadius = defaultRadius; } public void setStartBitmap(int startBitmap) { this.startBitmap = startBitmap; } public void setStopBitmap(int stopBitmap) { this.stopBitmap = stopBitmap; } public void setDuration(float duration) { this.duration = duration; }
xml布局
<com.gavinandre.customviewsamples.view.CircleRecordSurfaceView android:id="@+id/circle_record_view" android:layout_width="110dp" android:layout_height="110dp" android:layout_centerInParent="true"/>
使用方法
mCircleRecordView.setDuration(6); mCircleRecordView.setStartBitmap(R.mipmap.audio_record_mic_btn); mCircleRecordView.setStopBitmap(R.mipmap.audio_record_mic_btn_press); mCircleRecordView.setArcColor(ContextCompat.getColor(this, R.color.record_green)); mCircleRecordView.setSmallCircleColor(ContextCompat.getColor(this, R.color.record_green)); mCircleRecordView.setDefaultRadius(50); mCircleRecordView.setOnTouchListener(new View.OnTouchListener() { @Override public boolean onTouch(View v, MotionEvent event) { switch (event.getAction()) { case MotionEvent.ACTION_DOWN: mCircleRecordView.startDraw(); break; case MotionEvent.ACTION_UP: mCircleRecordView.reset(); mCircleRecordView.stopDraw(); break; default: break; } return true; } });
如果要写其他逻辑的话在onTouch方法里添加即可
代码示例:
CustomViewSamples
相关文章推荐
- 自定义View之音频播放圆形进度条
- Android自定义View实现音频播放圆形进度条
- Android 高手进阶之自定义View,自定义属性(带进度的圆形进度条)
- Android 自定义View修炼-自定义View-带百分比进度的圆形进度条(采用自定义属性)
- Android自定义控件之圆形进度条ImageView
- Android 高手进阶之自定义View,自定义属性(带进度的圆形进度条)
- Android 高手进阶之自定义View,自定义属性(带进度的圆形进度条)
- Android 高手进阶之自定义View,自定义属性(带进度的圆形进度条)
- Android 高手进阶之自定义View,自定义属性(带进度的圆形进度条)
- 自定义View实现Android圆形进度条
- android自定义view(自定义带进度显示的圆形进度条)
- Android 高手进阶之自定义View,自定义属性(带进度的圆形进度条)
- android 圆形进度条 自定义view
- Android 高手进阶之自定义View,自定义属性(带进度的圆形进度条)
- Android 高手进阶之自定义View,自定义属性(带进度的圆形进度条)
- Android 高手进阶之自定义View,自定义属性(带进度的圆形进度条)
- Android 高手进阶之自定义View,自定义属性(带进度的圆形进度条)
- Android 高手进阶之自定义View,自定义属性(带进度的圆形进度条)
- Android高手进阶之自定义View,自定义属性(带进度的圆形进度条)
- Android 高手进阶之自定义View,自定义属性(带进度的圆形进度条)