您的位置:首页 > 移动开发 > Android开发

自定义SurfaceView之音频录制圆形进度条

2017-02-21 11:42 381 查看
本篇文章介绍自定义SurfaceView来实现如下的效果



由于对于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
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  android
相关文章推荐