Android仿微信语音聊天界面设计

作者:迟做总比不做强 时间:2023-01-10 01:33:28 

有段时间没有看视频了,昨天晚上抽了点空时间,又看了下鸿洋大神的视频教程,又抽时间写了个学习记录。代码和老师讲的基本一样,网上也有很多相同的博客。我只是在AndroidStudio环境下写的。

—-主界面代码——


public class MainActivity extends Activity {
private ListView mListView;
private ArrayAdapter<Recorder> mAdapter;
private List<Recorder> mDatas = new ArrayList<Recorder>();
private AudioRecorderButton mAudioRecorderButton;
private View animView;

@Override
protected void onCreate(Bundle savedInstanceState) {
 super.onCreate(savedInstanceState);
 setContentView(R.layout.activity_main);
 mListView = (ListView) findViewById(R.id.id_listview);
 mAudioRecorderButton = (AudioRecorderButton) findViewById(R.id.id_recorder_button);
 mAudioRecorderButton.setFinishRecorderCallBack(new AudioRecorderButton.AudioFinishRecorderCallBack() {

public void onFinish(float seconds, String filePath) {
   Recorder recorder = new Recorder(seconds, filePath);
   mDatas.add(recorder);
   //更新数据
   mAdapter.notifyDataSetChanged();
   //设置位置
   mListView.setSelection(mDatas.size() - 1);
  }
 });

mAdapter = new RecoderAdapter(this, mDatas);
 mListView.setAdapter(mAdapter);

//listView的item点击事件
 mListView.setOnItemClickListener(new OnItemClickListener() {

public void onItemClick(AdapterView<?> arg0, View view, int position, long id) {
   // 声音播放动画
   if (animView != null) {
    animView.setBackgroundResource(R.drawable.adj);
    animView = null;
   }
   animView = view.findViewById(R.id.id_recoder_anim);
   animView.setBackgroundResource(R.drawable.play_anim);
   AnimationDrawable animation = (AnimationDrawable) animView.getBackground();
   animation.start();
   // 播放录音
   MediaPlayerManager.playSound(mDatas.get(position).filePath, new MediaPlayer.OnCompletionListener() {

public void onCompletion(MediaPlayer mp) {
     //播放完成后修改图片
     animView.setBackgroundResource(R.drawable.adj);
    }
   });
  }
 });
}

@Override
protected void onPause() {
 super.onPause();
 MediaPlayerManager.pause();
}

@Override
protected void onResume() {
 super.onResume();
 MediaPlayerManager.resume();
}

@Override
protected void onDestroy() {
 super.onDestroy();
 MediaPlayerManager.release();
}

—自定义Button——-


/**
* @param
* @author ldm
* @description 自定义Button
* @time 2016/6/25 9:26
*/
public class AudioRecorderButton extends Button {
// 按钮正常状态(默认状态)
private static final int STATE_NORMAL = 1;
//正在录音状态
private static final int STATE_RECORDING = 2;
//录音取消状态
private static final int STATE_CANCEL = 3;
//记录当前状态
private int mCurrentState = STATE_NORMAL;
//是否开始录音标志
private boolean isRecording = false;
//判断在Button上滑动距离,以判断 是否取消
private static final int DISTANCE_Y_CANCEL = 50;
//对话框管理工具类
private DialogManager mDialogManager;
//录音管理工具类
private AudioManager mAudioManager;
//记录录音时间
private float mTime;
// 是否触发longClick
private boolean mReady;
//录音准备
private static final int MSG_AUDIO_PREPARED = 0x110;
//音量发生改变
private static final int MSG_VOICE_CHANGED = 0x111;
//取消提示对话框
private static final int MSG_DIALOG_DIMISS = 0x112;

/**
 * @description 获取音量大小的线程
 * @author ldm
 * @time 2016/6/25 9:30
 * @param
 */
private Runnable mGetVoiceLevelRunnable = new Runnable() {

public void run() {
  while (isRecording) {//判断正在录音
   try {
    Thread.sleep(100);
    mTime += 0.1f;//录音时间计算
    mHandler.sendEmptyMessage(MSG_VOICE_CHANGED);//每0.1秒发送消息
   } catch (InterruptedException e) {
    e.printStackTrace();
   }
  }
 }
};

private Handler mHandler = new Handler() {

@Override
 public void handleMessage(Message msg) {
  switch (msg.what) {
   case MSG_AUDIO_PREPARED:
    //显示对话框
    mDialogManager.showRecordingDialog();
    isRecording = true;
    // 开启一个线程计算录音时间
    new Thread(mGetVoiceLevelRunnable).start();
    break;
   case MSG_VOICE_CHANGED:
    //更新声音
    mDialogManager.updateVoiceLevel(mAudioManager.getVoiceLevel(7));
    break;
   case MSG_DIALOG_DIMISS:
    //取消对话框
    mDialogManager.dimissDialog();
    break;
  }
  super.handleMessage(msg);
 }
};

public AudioRecorderButton(Context context, AttributeSet attrs) {
 super(context, attrs);
 mDialogManager = new DialogManager(context);
 //录音文件存放地址
 String dir = Environment.getExternalStorageDirectory() + "/ldm_voice";
 mAudioManager = AudioManager.getInstance(dir);
 mAudioManager.setOnAudioStateListener(new AudioManager.AudioStateListener() {
  public void wellPrepared() {
   mHandler.sendEmptyMessage(MSG_AUDIO_PREPARED);
  }
 });

// 由于这个类是button所以在构造方法中添加监听事件
 setOnLongClickListener(new OnLongClickListener() {

public boolean onLongClick(View v) {
   mReady = true;
   mAudioManager.prepareAudio();
   return false;
  }
 });
}

public AudioRecorderButton(Context context) {
 this(context, null);
}

/**
 * @description 录音完成后的回调
 * @author ldm
 * @time 2016/6/25 11:18
 * @param
*/
public interface AudioFinishRecorderCallBack {
 void onFinish(float seconds, String filePath);
}

private AudioFinishRecorderCallBack finishRecorderCallBack;

public void setFinishRecorderCallBack(AudioFinishRecorderCallBack listener) {
 finishRecorderCallBack = listener;
}

/**
 * @param
 * @description 处理Button的OnTouchEvent事件
 * @author ldm
 * @time 2016/6/25 9:35
 */
@Override
public boolean onTouchEvent(MotionEvent event) {
 //获取TouchEvent状态
 int action = event.getAction();
 // 获得x轴坐标
 int x = (int) event.getX();
 // 获得y轴坐标
 int y = (int) event.getY();

switch (action) {
  case MotionEvent.ACTION_DOWN://手指按下
   changeState(STATE_RECORDING);
   break;
  case MotionEvent.ACTION_MOVE://手指移动
   if (isRecording) {
    //根据x,y的坐标判断是否需要取消
    if (wantToCancle(x, y)) {
     changeState(STATE_CANCEL);
    } else {
     changeState(STATE_RECORDING);
    }
   }

break;
  case MotionEvent.ACTION_UP://手指放开
   if (!mReady) {
    reset();
    return super.onTouchEvent(event);
   }
   if (!isRecording || mTime < 0.6f) {//如果时间少于0.6s,则提示录音过短
    mDialogManager.tooShort();
    mAudioManager.cancel();
    // 延迟显示对话框
    mHandler.sendEmptyMessageDelayed(MSG_DIALOG_DIMISS, 1000);
   } else if (mCurrentState == STATE_RECORDING) {
    //如果状态为正在录音,则结束录制
    mDialogManager.dimissDialog();
    mAudioManager.release();

if (finishRecorderCallBack != null) {
     finishRecorderCallBack.onFinish(mTime, mAudioManager.getCurrentFilePath());
    }

} else if (mCurrentState == STATE_CANCEL) { // 想要取消
    mDialogManager.dimissDialog();
    mAudioManager.cancel();
   }
   reset();
   break;

}
 return super.onTouchEvent(event);
}

/**
 * 恢复状态及标志位
 */
private void reset() {
 isRecording = false;
 mTime = 0;
 mReady = false;
 changeState(STATE_NORMAL);
}

private boolean wantToCancle(int x, int y) {
 // 超过按钮的宽度
 if (x < 0 || x > getWidth()) {
  return true;
 }
 // 超过按钮的高度
 if (y < -DISTANCE_Y_CANCEL || y > getHeight() + DISTANCE_Y_CANCEL) {
  return true;
 }

return false;
}

/**
 * @param
 * @description 根据状态改变Button显示
 * @author ldm
 * @time 2016/6/25 9:36
 */
private void changeState(int state) {
 if (mCurrentState != state) {
  mCurrentState = state;
  switch (state) {
   case STATE_NORMAL:
    setBackgroundResource(R.drawable.btn_recorder_normal);
    setText(R.string.str_recorder_normal);
    break;

case STATE_RECORDING:
    setBackgroundResource(R.drawable.btn_recorder_recording);
    setText(R.string.str_recorder_recording);
    if (isRecording) {
     mDialogManager.recording();
    }
    break;

case STATE_CANCEL:
    setBackgroundResource(R.drawable.btn_recorder_recording);
    mDialogManager.wantToCancel();
    setText(R.string.str_recorder_want_cancel);
    break;
  }
 }
}
}

—-对话框管理工具类——


/**
 * @description 对话框管理工具类
 * @author ldm
 * @time 2016/6/25 11:53
 * @param
*/
public class DialogManager {
//弹出对话框
private Dialog mDialog;
//录音图标
private ImageView mIcon;
//音量显示 图标
private ImageView mVoice;
//对话框上提示文字
private TextView mLable;
//上下文对象
private Context mContext;

public DialogManager(Context context) {
 this.mContext = context;
}

/**
 * @param
 * @description 显示对话框
 * @author ldm
 * @time 2016/6/25 9:56
 */
public void showRecordingDialog() {
 //根据指定sytle实例化Dialog
 mDialog = new Dialog(mContext, R.style.AudioDialog);
 LayoutInflater inflater = LayoutInflater.from(mContext);
 View view = inflater.inflate(R.layout.dialog_recorder, null);
 mDialog.setContentView(view);
 mIcon = (ImageView) view.findViewById(R.id.id_recorder_dialog_icon);
 mVoice = (ImageView) view.findViewById(R.id.id_recorder_dialog_voice);
 mLable = (TextView) view.findViewById(R.id.id_recorder_dialog_label);
 mDialog.show();
}

/**
 * @param
 * @description 正在录音状态的对话框
 * @author ldm
 * @time 2016/6/25 10:08
 */
public void recording() {
 if (mDialog != null && mDialog.isShowing()) {
  mIcon.setVisibility(View.VISIBLE);
  mVoice.setVisibility(View.VISIBLE);
  mLable.setVisibility(View.VISIBLE);
  mIcon.setImageResource(R.drawable.recorder);
  mLable.setText("手指上滑,取消发送");
 }
}

/**
 * @param
 * @description 取消录音状态对话框
 * @author ldm
 * @time 2016/6/25 10:08
 */
public void wantToCancel() {
 if (mDialog != null && mDialog.isShowing()) {
  mIcon.setVisibility(View.VISIBLE);
  mVoice.setVisibility(View.GONE);
  mLable.setVisibility(View.VISIBLE);
  mIcon.setImageResource(R.drawable.cancel);
  mLable.setText("松开手指,取消发送");
 }
}

/**
 * @param
 * @description时间过短提示的对话框
 * @author ldm
 * @time 2016/6/25 10:09
 */
public void tooShort() {
 if (mDialog != null && mDialog.isShowing()) { //显示状态
  mIcon.setVisibility(View.VISIBLE);
  mVoice.setVisibility(View.GONE);
  mLable.setVisibility(View.VISIBLE);
  mIcon.setImageResource(R.drawable.voice_to_short);
  mLable.setText("录音时间过短");
 }
}

/**
 * @param
 * @description
 * @author ldm
 * @time 2016/6/25 取消(关闭)对话框
 */
public void dimissDialog() {
 if (mDialog != null && mDialog.isShowing()) { //显示状态
  mDialog.dismiss();
  mDialog = null;
 }
}

// 显示更新音量级别的对话框
public void updateVoiceLevel(int level) {
 if (mDialog != null && mDialog.isShowing()) { //显示状态
  mIcon.setVisibility(View.VISIBLE);
  mVoice.setVisibility(View.VISIBLE);
  mLable.setVisibility(View.VISIBLE);
  //设置图片的id,我们放在drawable中的声音图片是以v+数字格式的
  int resId = mContext.getResources().getIdentifier("v" + level, "drawable", mContext.getPackageName());
  mVoice.setImageResource(resId);
 }
}

}

—-声音播放工具类——


/**
* @param
* @author ldm
* @description 播放声音工具类
* @time 2016/6/25 11:29
*/
public class MediaPlayerManager {
//播放音频API类:MediaPlayer
private static MediaPlayer mMediaPlayer;
//是否暂停
private static boolean isPause;

/**
 * @param
 * filePath:文件路径
 * onCompletionListener:播放完成监听
 * @description 播放声音
 * @author ldm
 * @time 2016/6/25 11:30
 */
public static void playSound(String filePath, MediaPlayer.OnCompletionListener onCompletionListener) {
 if (mMediaPlayer == null) {
  mMediaPlayer = new MediaPlayer();
  //设置一个error *
  mMediaPlayer.setOnErrorListener(new MediaPlayer.OnErrorListener() {

public boolean onError(MediaPlayer arg0, int arg1, int arg2) {
    mMediaPlayer.reset();
    return false;
   }
  });
 } else {
  mMediaPlayer.reset();
 }

try {
  mMediaPlayer.setAudioStreamType(android.media.AudioManager.STREAM_MUSIC);
  mMediaPlayer.setOnCompletionListener(onCompletionListener);
  mMediaPlayer.setDataSource(filePath);
  mMediaPlayer.prepare();
  mMediaPlayer.start();
 } catch (Exception e) {

}
}

/**
 * @param
 * @description 暂停播放
 * @author ldm
 * @time 2016/6/25 11:31
 */
public static void pause() {
 if (mMediaPlayer != null && mMediaPlayer.isPlaying()) { //正在播放的时候
  mMediaPlayer.pause();
  isPause = true;
 }
}

/**
 * @param
 * @description 重新播放
 * @author ldm
 * @time 2016/6/25 11:31
 */
public static void resume() {
 if (mMediaPlayer != null && isPause) {
  mMediaPlayer.start();
  isPause = false;
 }
}

/**
 * @param
 * @description 释放操作
 * @author ldm
 * @time 2016/6/25 11:32
 */
public static void release() {
 if (mMediaPlayer != null) {
  mMediaPlayer.release();
  mMediaPlayer = null;
 }
}

—–录音操作工具类—–


/**
* @param
* @author ldm
* @description 录音管理工具类
* @time 2016/6/25 9:39
*/
public class AudioManager {
//AudioRecord: 主要是实现边录边播(AudioRecord+AudioTrack)以及对音频的实时处理。
// 优点:可以语音实时处理,可以实现各种音频的封装
private MediaRecorder mMediaRecorder;
//录音文件
private String mDir;
//当前录音文件目录
private String mCurrentFilePath;
//单例模式
private static AudioManager mInstance;
//是否准备好
private boolean isPrepare;

//私有构造方法
private AudioManager(String dir) {
 mDir = dir;
}

//对外公布获取实例的方法
public static AudioManager getInstance(String dir) {
 if (mInstance == null) {
  synchronized (AudioManager.class) {
   if (mInstance == null) {
    mInstance = new AudioManager(dir);
   }
  }
 }
 return mInstance;
}

/**
 * @param
 * @author ldm
 * @description 录音准备工作完成回调接口
 * @time 2016/6/25 11:14
 */
public interface AudioStateListener {
 void wellPrepared();
}

public AudioStateListener mAudioStateListener;

/**
 * @param
 * @description 供外部类调用的设置回调方法
 * @author ldm
 * @time 2016/6/25 11:14
 */
public void setOnAudioStateListener(AudioStateListener listener) {
 mAudioStateListener = listener;
}

/**
 * @param
 * @description 录音准备工作
 * @author ldm
 * @time 2016/6/25 11:15
 */
public void prepareAudio() {
 try {
  isPrepare = false;
  File dir = new File(mDir);
  if (!dir.exists()) {
   dir.mkdirs();//文件不存在,则创建文件
  }
  String fileName = generateFileName();
  File file = new File(dir, fileName);
  mCurrentFilePath = file.getAbsolutePath();
  mMediaRecorder = new MediaRecorder();
  // 设置输出文件路径
  mMediaRecorder.setOutputFile(file.getAbsolutePath());
  // 设置MediaRecorder的音频源为麦克风
  mMediaRecorder.setAudioSource(MediaRecorder.AudioSource.MIC);
  // 设置音频格式为RAW_AMR
  mMediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.RAW_AMR);
  // 设置音频编码为AMR_NB
  mMediaRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AMR_NB);
  // 准备录音
  mMediaRecorder.prepare();
  // 开始,必需在prepare()后调用
  mMediaRecorder.start();
  // 准备完成
  isPrepare = true;
  if (mAudioStateListener != null) {
   mAudioStateListener.wellPrepared();
  }
 } catch (Exception e) {
  e.printStackTrace();
 }
}

/**
 * @param
 * @description 随机生成录音文件名称
 * @author ldm
 * @time 2016/6/25 、
 */
private String generateFileName() {
 //随机生成不同的UUID
 return UUID.randomUUID().toString() + ".amr";
}

/**
 * @param
 * @description 获取音量值
 * @author ldm
 * @time 2016/6/25 9:49
 */
public int getVoiceLevel(int maxlevel) {
 if (isPrepare) {
  try {
   // getMaxAmplitude返回的数值最大是32767
   return maxlevel * mMediaRecorder.getMaxAmplitude() / 32768 + 1;//返回结果1-7之间
  } catch (Exception e) {
   e.printStackTrace();
  }
 }
 return 1;
}

/**
 * @param
 * @description 释放资源
 * @author ldm
 * @time 2016/6/25 9:50
 */
public void release() {
 mMediaRecorder.stop();
 mMediaRecorder.reset();
 mMediaRecorder = null;
}

/**
 * @param
 * @description 录音取消
 * @author ldm
 * @time 2016/6/25 9:51
 */
public void cancel() {
 release();
 if (mCurrentFilePath != null) {
  //取消录音后删除对应文件
  File file = new File(mCurrentFilePath);
  file.delete();
  mCurrentFilePath = null;
 }

}

/**
 * @param
 * @description 获取当前文件路径
 * @author ldm
 * @time 2016/6/25 9:51
 */
public String getCurrentFilePath() {

return mCurrentFilePath;
}
}

代码中有注释,就不贴图了,和微信语音聊天界面一样的,所以叫仿微信嘛,呵呵。运行了也可以看到效果。所有代码可以从这里下载:http://xiazai.jb51.net/201611/yuanma/AndroidWXchat(jb51.net).rar

标签:Android,微信,语音聊天
0
投稿

猜你喜欢

  • Java synchronized锁升级jol过程详解

    2023-04-15 04:58:51
  • C#中事件的定义和使用

    2022-02-15 13:34:00
  • Java实现简单邮件发送功能

    2023-08-15 00:53:23
  • Android多个TAB选项卡切换效果

    2022-04-10 03:03:15
  • Android TextView字幕效果实例

    2022-03-18 14:55:13
  • java rocketmq--消息的产生(普通消息)

    2023-10-19 08:51:50
  • Unity游戏开发之2048游戏的实现

    2023-04-19 00:11:33
  • Java数据结构之线段树详解

    2022-09-03 08:13:32
  • C#简单的加密类实例

    2022-12-15 11:31:18
  • Android使用元数据实现配置信息的传递方法详细介绍

    2023-11-02 15:25:48
  • Java注解方式之防止重复请求

    2023-05-29 16:30:51
  • C#中的应用程序接口介绍及实现,密封类与密封方法

    2023-10-24 07:37:41
  • 关于Mybatis-Plus Update更新策略问题

    2022-04-14 19:29:24
  • 简单了解Java synchronized关键字同步

    2022-07-25 10:38:05
  • springboot +rabbitmq+redis实现秒杀示例

    2022-04-21 02:29:28
  • RocketMQ源码分析之Broker过期消息清理机制

    2023-06-10 15:47:43
  • C#通过PInvoke调用c++函数的备忘录的实例详解

    2023-11-25 12:53:08
  • 浅谈Java自定义类加载器及JVM自带的类加载器之间的交互关系

    2021-09-12 23:37:24
  • 使用@Order控制配置类/AOP/方法/字段的加载顺序详解

    2023-09-05 17:34:35
  • c#实现获取字符串阵列中元素最长或最短的长度

    2022-08-22 08:54:01
  • asp之家 软件编程 m.aspxhome.com