我们先来看看To圈(QQ,微信等其他大部分软件也是大同小异)的注册录音界面运行截图:
(⊙o⊙)…为了实现这个效果还是花了一番功夫的,
主要难点有以下方面:
1、跟随音量变化的话筒。
这个话筒一开始感觉是最头痛的部分,完全不知道从何开始实现。首先直接用图片肯定是不行的,想实现我最后达到的效果需要12张图片,这太占资源了直接GG。然后又想到用遮罩实现,但是仔细观察可以发现话筒的圆形进度条周围是透明的,也就是说如果用遮罩,除非完美重合(重合就得考虑屏幕适配问题了,这就是个大难题了)。最后我想出的办法有点取巧吧:用一个竖向进度条实现话筒的进度部分,然后下面的Y字形直接用canvas画(为了使其看起来像个话筒花费了大量代码进行计算...)。这样一来看起来差不多,而且规避了屏幕适配问题,也不需要加载那么多图片然后轮着换了。但这肯定不是最好的方法,所以跪求更高雅的实现的方法!!!
2、圈内的蓝色圆弧计时部分。
3、手势判断。
这么一总结感觉1比2和3加起来都难得多..
实现过程:
首先是界面实现:
activity_register.xml
下面是黑色录音框布局:
layout_recode_popwindow.xml
实现效果如下:
代码实现:
从上面的界面中可以看到有一个自定义的View:RecodePopWindowCircle.java
package com.whale.nangua.toquan.view;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Rect;
import android.graphics.RectF;
import android.util.AttributeSet;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.ProgressBar;
import android.widget.RelativeLayout;
import com.whale.nangua.toquan.R;
/**
* Created by nangua on 2016/7/29.
*/
public class RecodePopWindowCircle extends RelativeLayout {
ProgressBar progressbar_register_recode;//进度条
int width; //控件总高
int height; //控件总宽
boolean IS_SHOW_RECODING = true; //默认设置为true
float scale = this.getResources().getDisplayMetrics().density; //获得像素
public RecodePopWindowCircle(Context context, AttributeSet attrs) {
super(context, attrs);
//在构造函数中将Xml中定义的布局解析出来。
LayoutInflater.from(context).inflate(R.layout.layout_recode_circlepopwindow, this, true);
init();
}
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
super.onLayout(changed, l, t, r, b);
progressbar_register_recode = (ProgressBar) this.findViewById(R.id.progressbar_register_recode);
width = getWidth();
height = getHeight();
}
private void init() {
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
Paint paint = new Paint(); //画笔
if (IS_SHOW_RECODING) {
progressbar_register_recode.setVisibility(View.VISIBLE);
//画话筒下面的Y形,整体下移5个像素点5*scale
paint.setColor(Color.WHITE);
paint.setStrokeWidth(6); //宽度
paint.setAntiAlias(true); //抗锯齿
paint.setStyle(Paint.Style.STROKE); //设置空心
RectF oval = new RectF(); //RectF对象
int xandy = width / 2;
int r = (int) ( 8 * scale); //进度条底部半径
int space = (int) (5 * scale); //圆弧与底部进度条的间隔
oval.left = xandy - r - space; //左边
oval.top = xandy - 2 * r - space + 5 * scale; //上边
oval.right = xandy + r + space; //右边
oval.bottom = xandy + space + 5 * scale;
//画弧形
canvas.drawArc(oval, 20, 140, false, paint);
//画线
int lineLength = (int) (10 * scale);
canvas.drawLine(xandy, xandy + space + 5 * scale, xandy, xandy + space + lineLength + 5 * scale, paint);
//画弧形的圆点
//因为位置计算没有精确化所以做了些微调
int pointx = (int) (Math.cos(xandy) + r + space);
paint.setStyle(Paint.Style.FILL); //设置实心
//右边缘圆点
canvas.drawCircle(xandy + pointx - 1 * scale, xandy + 2 * scale, 3, paint);
//左边缘圆点
canvas.drawCircle(xandy - pointx + 1 * scale, xandy + 2 * scale, 3, paint);
//直线下边缘圆点
canvas.drawCircle(xandy, xandy + space + lineLength + 5 * scale, 3, paint);
}
//否则显示垃圾桶界面
else {
progressbar_register_recode.setVisibility(View.INVISIBLE);
Bitmap bitmap = BitmapFactory.decodeResource(this.getContext().getResources(), R.drawable.ic_trash);
canvas.drawBitmap(bitmap,null, new Rect((int) (width/2 - 20*scale),
(int) (height/2 - 25*scale),
(int) (width/2 + 20*scale),
(int) (height/2 + 25*scale)),null);
}
//画外围的蓝色录音圆弧
paint.setColor(Color.parseColor("#0CA6D9"));
paint.setStrokeWidth(2); //宽度
paint.setAntiAlias(true); //抗锯齿
paint.setStyle(Paint.Style.STROKE); //设置空心
canvas.drawArc(new RectF(0 + 2, 0 + 2, width - 2, height - 2), -90, endArc, false, paint);
}
/**
* 设置是否显示录音话筒在录音
*/
public void setIsShowRecoding(boolean IS_SHOW_RECODING) {
this.IS_SHOW_RECODING = IS_SHOW_RECODING;
postInvalidate();
}
//设置时间圆弧终止角度
public void setEndArc(int endArc) {
this.endArc = endArc;
postInvalidate();
}
//设置进度
public void setProgress(int progress) {
progressbar_register_recode.setProgress(progress);
}
int endArc = 0; //圆弧终止角度
}
实现的过程注释说明得很清楚了,这里再概括一下,主要是在试图中绘制Y字形话筒的"把手",以及外围蓝色录音弧,并提供了设置方法以便在Activity中改变视图。
这里再Y字形三个点处加画了白色小圈圈,以使其更加Q弹...
最后就是控制代码的实现了:
RegisterActivity.java
package com.whale.nangua.toquan;
import android.app.Activity;
import android.media.MediaPlayer;
import android.os.Bundle;
import android.os.Handler;
import android.view.MotionEvent;
import android.view.View;
import android.widget.Button;
import android.widget.ImageButton;
import android.widget.RadioButton;
import android.widget.RadioGroup;
import android.widget.TextView;
import com.whale.nangua.toquan.view.RecodePopWindowCircle;
import com.whale.nangua.toquan.voice.SoundMeter;
import java.io.IOException;
/**
* Created by nangua on 2016/7/26.
*/
public class RegisterActivity extends Activity implements
View.OnClickListener, RadioGroup.OnCheckedChangeListener, SoundMeter.onStartRecoder {
//性别选择的radiobutton
private RadioButton radiobtn_register_man;
private RadioGroup radiogroup_register_sexcheck;
private View layout_register_popup;
//播放录音按钮
private Button btn_register_play;
//录音按钮
private ImageButton imgbtn_register_recode;
private RecodePopWindowCircle circleview_register_microphone;
private long startVoiceT; //开始录音的时间
private String voiceName; //音频名
//录音组件
private SoundMeter mSensor;
private Handler mHandler = new Handler();
private Runnable ampTask = new Runnable() {
public void run() {
double amp = mSensor.getAmplitude(); //得到音频图
updateDisplay(amp);
mHandler.postDelayed(ampTask, POLL_INTERVAL);
}
};
private int endArc = 0;
private Runnable updateArcTask = new Runnable() {
@Override
public void run() {
endArc += 1;
circleview_register_microphone.setEndArc(endArc);
//更新时间圈圈
mHandler.postDelayed(updateArcTask, UPDATE_ARC_INTERVAL);
}
};
private Runnable mSleepTask = new Runnable() {
public void run() {
stop();
}
};
//录音延迟
private static final int POLL_INTERVAL = 300;
//时间圆弧更新延迟,默认一秒
private static final int UPDATE_ARC_INTERVAL = 200;
//录音提示文字
TextView tv_register_show;
//是否取消发送
private boolean IF_CANCLE_SEND = false;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_register);
initView();
}
float scale; //像素密度
int screenHeight; //屏幕高度
private void initView() {
screenHeight = this.getWindowManager().getDefaultDisplay().getHeight(); //屏幕高度
scale = this.getResources().getDisplayMetrics().density;
//初始化录音提示文字
tv_register_show = (TextView) findViewById(R.id.tv_register_show);
//初始化播放录音按钮
btn_register_play = (Button) findViewById(R.id.btn_register_play);
btn_register_play.setOnClickListener(this);
//初始化录音组件
mSensor = new SoundMeter();
mSensor.setonStartRecoderCallback(this);
circleview_register_microphone = (RecodePopWindowCircle) findViewById(R.id.circleview_register_microphone);
//初始化性别选择rbtn
radiobtn_register_man = (RadioButton) findViewById(R.id.radiobtn_register_man);
radiobtn_register_man.setChecked(true);
radiogroup_register_sexcheck = (RadioGroup) findViewById(R.id.radiogroup_register_sexcheck);
radiogroup_register_sexcheck.setOnCheckedChangeListener(this);
layout_register_popup = findViewById(R.id.layout_register_popup);
layout_register_popup.setVisibility(View.INVISIBLE);
imgbtn_register_recode = (ImageButton) findViewById(R.id.imgbtn_register_recode);
imgbtn_register_recode.setOnTouchListener(new View.OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
int Y = (int) event.getRawY();
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
btn_register_play.setVisibility(View.VISIBLE);
layout_register_popup.setVisibility(View.VISIBLE);
circleview_register_microphone.setEndArc(0);//初始化时间圆弧
startVoiceT = System.currentTimeMillis();
voiceName = startVoiceT + ".amr";
/**
* 开始录音方法,传入音频名字为事件+.amr
*/
start(voiceName);
break;
case MotionEvent.ACTION_UP:
layout_register_popup.setVisibility(View.GONE);
endArc = 0;
stop();
//如果取消发送
if (IF_CANCLE_SEND) {
//TODO 取消发送
}
//如果发送
else {
//TODO 发送
}
break;
case MotionEvent.ACTION_MOVE:
//位置判断在方框范围以内
if (Y <= screenHeight / 2 + 90 * scale) {
tv_register_show.setText("手指松开,取消发送");
tv_register_show.setBackground(getResources().getDrawable(R.drawable.shape_register_recodetv));
circleview_register_microphone.setIsShowRecoding(false);
} else {
tv_register_show.setText("手指上划,取消发送");
tv_register_show.setBackground(null);
circleview_register_microphone.setIsShowRecoding(true);
}
break;
}
return true;
}
});
}
private void stop() {
mHandler.removeCallbacks(mSleepTask);
mHandler.removeCallbacks(ampTask);
mHandler.removeCallbacks(updateArcTask);
mSensor.stop();
circleview_register_microphone.setProgress(0);
}
/**
* 更新显示音频高低图
*
* @param signalEMA
*/
private void updateDisplay(double signalEMA) {
int temp = 100 / 12;
switch ((int) signalEMA) {
case 0:
circleview_register_microphone.setProgress(temp);
break;
case 1:
circleview_register_microphone.setProgress(2 * temp);
break;
case 2:
circleview_register_microphone.setProgress(3 * temp);
break;
case 3:
circleview_register_microphone.setProgress(4 * temp);
break;
case 4:
circleview_register_microphone.setProgress(5 * temp);
break;
case 5:
circleview_register_microphone.setProgress(6 * temp);
break;
case 6:
circleview_register_microphone.setProgress(7 * temp);
break;
case 7:
circleview_register_microphone.setProgress(8 * temp);
break;
case 8:
circleview_register_microphone.setProgress(9 * temp);
break;
case 9:
circleview_register_microphone.setProgress(10 * temp);
break;
case 10:
circleview_register_microphone.setProgress(11 * temp);
break;
case 11:
circleview_register_microphone.setProgress(12 * temp);
break;
default:
break;
}
}
/**
* 开始录音
*
* @param name
*/
private void start(String name) {
mSensor.start(name);
mHandler.postDelayed(ampTask, POLL_INTERVAL);
mHandler.postDelayed(updateArcTask, UPDATE_ARC_INTERVAL);
}
@Override
public void onClick(View v) {
switch (v.getId()) {
case R.id.btn_register_play:
MediaPlayer mediaPlayer = new MediaPlayer();
try {
mediaPlayer.setDataSource(soundFilePath);
mediaPlayer.prepare();
} catch (IOException e) {
e.printStackTrace();
}
mediaPlayer.start();
break;
}
}
String soundFilePath;//录音文件路径
@Override
public void setVoicePath(String path) {
soundFilePath = path;
}
/**
* 性别选择改变的监听方法
*
* @param group
* @param checkedId
*/
@Override
public void onCheckedChanged(RadioGroup group, int checkedId) {
switch (checkedId) {
case R.id.radiobtn_register_women:
//TODO 选择了男性
break;
case R.id.radiobtn_register_man:
//TODO 选择了女性
break;
}
}
}
最后实现的效果如下:
总的来说功能还是比较好实现的,就是目前经验还是不是太足做起来比较费力。
需要源码的留评论哈~
继续加油~