1、项目界面展示:

2、项目代码整体结构简析:

(1)AudioRecorderButton类(录音按钮):State:STATE_NORMAL【正常状态】、STATE_RECORDERING【正在录音的状态】、STATE_WANT_TO_CALCEL【想要放弃录音的装填】;

(2)AudioDialogManage类(录音过程中的提示对话框):Style:RECORDERING【正在录音】、WANT_TO_CANCEL【取消提示对话框】、TOO_SHAORT【录音时间过短提醒对话框】;

(3)AudioManage类(控制录音):prepare()(end prepare-—》callback)【去录音】, cancel()【取消录音】, release()【正常结束】(-—》callbackToActivity), getVoiceLevel()【获得音量大小】;

伪码展示:

class AudioRecorderButton
{onTouchEvent(){DOWN: // 按下changeButtonState(STATE_RECORDERING);onLongClick -> AudioManage.prepare() -> end prepare-> AudioDialogManage.showDialog(RECORDERING)MOVE: // 滑动if(wantCancel(x,y)){changeButtonState(STATE_WANT_TO_CALCEL);       //更新ButtonAudioDialogManage.showDialog(WANT_TO_CANCEL);  //更新Dialog}else{changeButtonState(STATE_RECORDERING);AudioDialogManage.showDialog(RECORDERING);}UP:   // 抬起if(wantCancel == curState)          //取消{AudioManage.cancel();}if(STATE_RECORDERING = curState)    //正常结束{AudioManage.release();callbackToActivity(path,time);}......}
}

1、主界面布局:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"android:layout_width="match_parent"android:layout_height="match_parent"  android:orientation="vertical" ><!-- 语音列表 --><ListViewandroid:id="@+id/voiceNotesListView"android:layout_width="match_parent"android:layout_height="0dp"android:layout_weight="1"android:background="#ebebeb"android:divider="@null"android:dividerHeight="10dp" ></ListView><!-- 底部按钮布局 --><FrameLayoutandroid:layout_width="match_parent"android:layout_height="wrap_content" ><songshi.suishou.tool.AudioRecorderButtonandroid:id="@+id/recorderButton"android:layout_width="match_parent"android:layout_height="wrap_content"android:layout_marginLeft="50dp"android:layout_marginRight="50dp"android:layout_marginTop="6dp"android:layout_marginBottom="7dp"android:gravity="center"android:padding="5dp"android:minHeight="0dp"android:background="@drawable/btn_recorder_normal"android:text="@string/str_recorder_normal"android:textColor="#727272"></songshi.suishou.tool.AudioRecorderButton><View android:layout_width="match_parent"android:layout_height="1dp"android:background="#ccc"/></FrameLayout></LinearLayout>
<string name="str_recorder_normal">按住 说话</string>
<string name="str_recorder_recording">松开 结束</string>
<string name="str_recorder_want_cancel">滑动手指,取消记录</string>

2、AudioRecorderButton类:

package com.example.voicenotes;import com.example.voicenotes.AudioManage.AudioStateListenter;import android.content.Context;
import android.os.Environment;
import android.os.Handler;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
import android.widget.Button;public class AudioRecorderButton extends Button implements AudioStateListenter {private static final int STATE_NORMAL = 1;           //默认状态private static final int STATE_RECORDERING = 2;      //录音状态private static final int STATE_WANT_TO_CALCEL = 3;   //取消状态private int mCurState = STATE_NORMAL;  //当前状态private boolean isRecordering = false; // 是否已经开始录音private boolean mReady; // 是否触发onLongClickprivate static final int DISTANCE_Y_CANCEL = 50;private AudioDialogManage audioDialogManage;private AudioManage mAudioManage;//构造方法public AudioRecorderButton(Context context) {super(context, null);// TODO Auto-generated constructor stub}public AudioRecorderButton(Context context, AttributeSet attrs) {super(context, attrs);audioDialogManage = new AudioDialogManage(getContext());String dir = Environment.getExternalStorageDirectory()+ "/VoiceRecorder";// 此处需要判断是否有存储卡mAudioManage = AudioManage.getInstance(dir);mAudioManage.setOnAudioStateListenter(this);setOnLongClickListener(new OnLongClickListener() {@Overridepublic boolean onLongClick(View v) {// TODO Auto-generated method stubmReady = true;// 真正显示应该在audio end prepared以后mAudioManage.prepareAudio();//return true;return false;}});// TODO Auto-generated constructor stub}//录音完成后的回调public interface AudioFinishRecorderListenter{void onFinish(float seconds,String FilePath);}private AudioFinishRecorderListenter mListenter;public void setAudioFinishRecorderListenter(AudioFinishRecorderListenter listenter){this.mListenter=listenter;}//复写onTouchEvent@Overridepublic boolean onTouchEvent(MotionEvent event) {int action = event.getAction();   //获取当前Actionint x = (int) event.getX();int y = (int) event.getY();switch (action) {case MotionEvent.ACTION_DOWN:changeState(STATE_RECORDERING);break;case MotionEvent.ACTION_MOVE:// 根据X、Y的坐标,判断是否想要取消if (isRecordering) {if (wantToCancel(x, y)) {changeState(STATE_WANT_TO_CALCEL);} else {changeState(STATE_RECORDERING);}}break;case MotionEvent.ACTION_UP:if (!mReady) {   //没有触发onLongClickreset();return super.onTouchEvent(event);}if (!isRecordering || mTime < 0.5f) {audioDialogManage.tooShort();mAudioManage.cancel();mHandler.sendEmptyMessageDelayed(MSG_DIALOG_DIMISS, 1300);// 延迟,1.3秒以后关闭时间过短对话框} /*else if (mTime < 0.5f) {audioDialogManage.tooShort();isRecordering=false;mAudioManage.cancel();mHandler.sendEmptyMessageDelayed(MSG_DIALOG_DIMISS, 1300);// 延迟}*/else if (mCurState == STATE_RECORDERING) { //正常录制结束audioDialogManage.dimissDialog();// releasemAudioManage.release();// callbackToActif(mListenter!=null){mListenter.onFinish(mTime, mAudioManage.getCurrentFilePath());}              } else if (mCurState == STATE_WANT_TO_CALCEL) {// cancelaudioDialogManage.dimissDialog();mAudioManage.cancel();}reset();break;}return super.onTouchEvent(event);}// 恢复状态以及一些标志位private void reset() {isRecordering = false;mReady = false;mTime = 0;changeState(STATE_NORMAL);}private boolean wantToCancel(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;}//改变Button的背景和文本private void changeState(int state) {if (mCurState != state) {mCurState = state;switch (state) {case STATE_NORMAL:setBackgroundResource(R.drawable.btn_recorder_normal);setText(R.string.str_recorder_normal);break;case STATE_RECORDERING:setBackgroundResource(R.drawable.btn_recorder_recordering);setText(R.string.str_recorder_recording);if (isRecordering) {// 更新Dialog.recording()audioDialogManage.recording();}break;case STATE_WANT_TO_CALCEL:setBackgroundResource(R.drawable.btn_recorder_recordering);setText(R.string.str_recorder_want_cancel);// 更新Dialog.wantCancel()audioDialogManage.wantToCancel();break;}}}private static final int MSG_AUDIO_PREPARED = 0x110;private static final int MSG_VOICE_CHANGE = 0x111;private static final int MSG_DIALOG_DIMISS = 0x112;private float mTime;// 获取音量大小的Runnableprivate Runnable mGetVoiceLevelRunnable = new Runnable() {@Overridepublic void run() {// TODO Auto-generated method stubwhile (isRecordering) {try {Thread.sleep(100);mTime += 0.1f;mHandler.sendEmptyMessage(MSG_VOICE_CHANGE);} catch (InterruptedException e) {// TODO Auto-generated catch blocke.printStackTrace();}}}};private Handler mHandler = new Handler() {public void handleMessage(android.os.Message msg) {switch (msg.what) {case MSG_AUDIO_PREPARED:audioDialogManage.showRecorderingDialog();isRecordering = true;new Thread(mGetVoiceLevelRunnable).start();break;case MSG_VOICE_CHANGE:audioDialogManage.updateVoiceLevel(mAudioManage.getVoiceLevel(7));break;case MSG_DIALOG_DIMISS:audioDialogManage.dimissDialog();break;}};};@Overridepublic void wellPrepared() {// TODO Auto-generated method stubmHandler.sendEmptyMessage(MSG_AUDIO_PREPARED);}
}

3、AudioDialogManage类:

Dialog布局:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"android:layout_width="wrap_content"android:layout_height="wrap_content"android:background="@drawable/dialog_loading_bg"android:gravity="center"android:orientation="vertical"android:padding="20dp" ><LinearLayoutandroid:layout_width="wrap_content"android:layout_height="wrap_content"android:orientation="horizontal" ><ImageViewandroid:id="@+id/recorder_dialog_icon"android:layout_width="wrap_content"android:layout_height="wrap_content"android:src="@drawable/recorder"android:visibility="visible" /><ImageViewandroid:id="@+id/recorder_dialog_voice"android:layout_width="wrap_content"android:layout_height="wrap_content"android:src="@drawable/v1"android:visibility="visible" /></LinearLayout><TextViewandroid:id="@+id/recorder_dialog_label"android:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_marginTop="5dp"android:text="手指滑动,取消录音"android:textColor="#FFFFFFFF" /></LinearLayout>

AudioDialogManage类:

package com.example.voicenotes;import android.app.Dialog;
import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.ImageView;
import android.widget.TextView;public class AudioDialogManage {private Dialog mDialog;private ImageView mIcon;private ImageView mVoice;private TextView mLabel;private Context mContext;public AudioDialogManage(Context context) {this.mContext = context;}//默认的对话框的显示public void showRecorderingDialog() {mDialog = new Dialog(mContext, R.style.Theme_AudioDialog);LayoutInflater inflater = LayoutInflater.from(mContext);View view = inflater.inflate(R.layout.tools_voice_notes_dialog_recorder, null);mDialog.setContentView(view);mIcon = (ImageView) mDialog.findViewById(R.id.recorder_dialog_icon);mVoice = (ImageView) mDialog.findViewById(R.id.recorder_dialog_voice);mLabel = (TextView) mDialog.findViewById(R.id.recorder_dialog_label);mDialog.show();}//正在录音时,Dialog的显示public void recording() {if (mDialog != null && mDialog.isShowing()) {mIcon.setVisibility(View.VISIBLE);mVoice.setVisibility(View.VISIBLE);mLabel.setVisibility(View.VISIBLE);mIcon.setImageResource(R.drawable.recorder);mLabel.setText("手指滑动,取消录音");}}public void wantToCancel() {if (mDialog != null && mDialog.isShowing()) {mIcon.setVisibility(View.VISIBLE);mVoice.setVisibility(View.GONE);mLabel.setVisibility(View.VISIBLE);mIcon.setImageResource(R.drawable.cancel);mLabel.setText("松开手指,取消录音");}}//录音时间过短public void tooShort() {if (mDialog != null && mDialog.isShowing()) {mIcon.setVisibility(View.VISIBLE);mVoice.setVisibility(View.GONE);mLabel.setVisibility(View.VISIBLE);mIcon.setImageResource(R.drawable.voice_to_short);mLabel.setText("录音时间过短");}}public void dimissDialog() {if (mDialog != null && mDialog.isShowing()) {mDialog.dismiss();mDialog = null;}   }//通过Level更新Voice的图片V1——V7public void updateVoiceLevel(int level) {if (mDialog != null && mDialog.isShowing()) {//mIcon.setVisibility(View.VISIBLE);//mVoice.setVisibility(View.VISIBLE);//mLabel.setVisibility(View.VISIBLE);int resId=mContext.getResources().getIdentifier("v"+level, "drawable", mContext.getPackageName());mVoice.setImageResource(resId);}}
}

4、AudioManage类:

MediaRecorder:http://developer.android.com/reference/android/media/MediaRecorder.html

MediaRecorder状态转换图:

AudioManage类:

package com.example.voicenotes;import java.io.File;
import java.io.IOException;
import java.util.UUID;import android.media.MediaRecorder;public class AudioManage {private MediaRecorder mMediaRecorder;private String mDir; // 文件夹的名称private String mCurrentFilePath;private static AudioManage mInstance;private boolean isPrepared; // 标识MediaRecorder准备完毕private AudioManage(String dir) {mDir = dir;}// 回调准备完毕public interface AudioStateListenter {void wellPrepared(); // prepared完毕}public AudioStateListenter mListenter;public void setOnAudioStateListenter(AudioStateListenter audioStateListenter) {mListenter = audioStateListenter;}// 单例实现 AudioManage//DialogManage主要管理Dialog,Dialog主要依赖Context,而且此Context必须是Activity的Context,//DialogManage写成单例实现,将是Application级别的,将无法释放,容易造成内存泄露,甚至导致错误public static AudioManage getInstance(String dir) {if (mInstance == null) {synchronized (AudioManage.class) { // 同步if (mInstance == null) {mInstance = new AudioManage(dir);}}}return mInstance;}// 准备public void prepareAudio() {try {isPrepared = 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());// 设置输出文件mMediaRecorder.setAudioSource(MediaRecorder.AudioSource.MIC);// 设置MediaRecorder的音频源为麦克风mMediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.AMR_NB);// 设置音频的格式mMediaRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AMR_NB);// 设置音频的编码为AMR_NBmMediaRecorder.prepare();mMediaRecorder.start();isPrepared = true; // 准备结束if (mListenter != null) {mListenter.wellPrepared();}} catch (IllegalStateException e) {// TODO Auto-generated catch blocke.printStackTrace();} catch (IOException e) {// TODO Auto-generated catch blocke.printStackTrace();}}// 随机生成文件名称private String GenerateFileName() {// TODO Auto-generated method stubreturn UUID.randomUUID().toString() + ".amr"; // 音频文件格式}// 获得音量等级public int getVoiceLevel(int maxLevel) {if (isPrepared) {try {return maxLevel * mMediaRecorder.getMaxAmplitude() / 32768 + 1;} catch (Exception e) {// TODO Auto-generated catch block// e.printStackTrace();}}return 1;}// 释放public void release() {mMediaRecorder.stop();mMediaRecorder.release();mMediaRecorder = null;}// 取消public void cancel() {release();if (mCurrentFilePath != null) {File file = new File(mCurrentFilePath);file.delete();    //删除录音文件mCurrentFilePath = null;}}public String getCurrentFilePath() {// TODO Auto-generated method stubreturn mCurrentFilePath;}
}

5、MediaManage类:主要管理语音的播放、暂停、恢复、停止

package com.example.voicenotes;import java.io.IOException;import android.media.AudioManager;
import android.media.MediaPlayer;
import android.media.MediaPlayer.OnErrorListener;public class MediaManage {private static MediaPlayer mMediaPlayer;private static boolean isPause;public static void playSound(String filePath,MediaPlayer.OnCompletionListener onCompletionListenter){if(mMediaPlayer==null){mMediaPlayer=new MediaPlayer();mMediaPlayer.setOnErrorListener( new OnErrorListener() {@Overridepublic boolean onError(MediaPlayer mp, int what, int extra) {// TODO Auto-generated method stubmMediaPlayer.reset();return false;}});}else{mMediaPlayer.reset();}try {mMediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);mMediaPlayer.setOnCompletionListener(onCompletionListenter);mMediaPlayer.setDataSource(filePath);mMediaPlayer.prepare();mMediaPlayer.start();} catch (IllegalArgumentException e) {// TODO Auto-generated catch blocke.printStackTrace();} catch (SecurityException e) {// TODO Auto-generated catch blocke.printStackTrace();} catch (IllegalStateException e) {// TODO Auto-generated catch blocke.printStackTrace();} catch (IOException e) {// TODO Auto-generated catch blocke.printStackTrace();}}public static void pause(){if(mMediaPlayer!=null && mMediaPlayer.isPlaying()){mMediaPlayer.pause();isPause=true;}}public static void resume(){if(mMediaPlayer!=null && isPause){mMediaPlayer.start();isPause=false;}}public static void release(){if(mMediaPlayer!=null){mMediaPlayer.release();mMediaPlayer=null;}}
}

6、MainActivity:

ListView布局:

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"android:layout_width="match_parent"android:layout_height="60dp"android:layout_marginTop="5dp" ><ImageViewandroid:id="@+id/voice_robot"android:layout_width="40dp"android:layout_height="40dp"android:layout_alignParentLeft="true"android:layout_centerVertical="true"android:layout_marginLeft="5dp"android:src="@drawable/robot" /><FrameLayoutandroid:id="@+id/recorder_length"android:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_centerVertical="true"android:layout_toRightOf="@id/voice_robot"android:background="@drawable/popup_inline_error" ><View android:id="@+id/recorder_anim"android:layout_width="25dp"android:layout_height="25dp"android:layout_gravity="center_vertical|left"android:background="@drawable/presence_audio_online"/></FrameLayout><TextView android:id="@+id/recorder_time"android:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_centerVertical="true"android:layout_toRightOf="@id/recorder_length"android:text=""android:layout_marginLeft="3dp"android:textColor="#ff777777"/>
</RelativeLayout>

AudioRecorderAdapter:

package com.example.voicenotes;import java.util.List;import android.content.Context;
import android.util.DisplayMetrics;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.view.WindowManager;
import android.widget.ArrayAdapter;
import android.widget.TextView;import com.example.voicenotes.MainActivity.Recorder;public class AudioRecorderAdapter extends ArrayAdapter<Recorder> {private List<Recorder> mDatas;
<span style="white-space:pre"> </span>private Context mContext;private int mMinItemWidth;private int mMaxItemWidth;private LayoutInflater mInflater;public AudioRecorderAdapter(Context context, List<Recorder> datas) {super(context, -1, datas);<span style="white-space:pre">      </span>mContext=context;
<span style="white-space:pre">     </span>mDatas=datas;
<span style="white-space:pre">     </span>
<span style="white-space:pre">     </span>mInflater=LayoutInflater.from(context);WindowManager windowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);DisplayMetrics outMetrics = new DisplayMetrics();windowManager.getDefaultDisplay().getMetrics(outMetrics);mMaxItemWidth = (int) (outMetrics.widthPixels * 0.7f);mMinItemWidth = (int) (outMetrics.widthPixels * 0.15f);}@Overridepublic View getView(int position, View convertView, ViewGroup parent) {// TODO Auto-generated method stubViewHolder holder = null;if (convertView == null) {convertView = mInflater.inflate(R.layout.tools_voice_notes_recorder_item, parent, false);holder=new ViewHolder();holder.seconds=(TextView) convertView.findViewById(R.id.recorder_time);holder.mLength=convertView.findViewById(R.id.recorder_length);convertView.setTag(holder);}else{holder=(ViewHolder) convertView.getTag();}holder.seconds.setText(Math.round(getItem(position).getTime())+"\"");ViewGroup.LayoutParams lp=holder.mLength.getLayoutParams();lp.width=(int) (mMinItemWidth+(mMaxItemWidth/60f * getItem(position).getTime()));return convertView;}private class ViewHolder {TextView seconds;View mLength;}
}

MainActivity:

package com.example.voicenotes;import java.util.ArrayList;
import java.util.List;import android.app.Activity;
import android.graphics.drawable.AnimationDrawable;
import android.media.MediaPlayer;
import android.os.Bundle;
import android.view.View;
import android.widget.AdapterView;
import android.widget.AdapterView.OnItemClickListener;
import android.widget.ArrayAdapter;
import android.widget.ListView;import com.example.voicenotes.AudioRecorderButton.AudioFinishRecorderListenter;public class MainActivity extends Activity {private ListView mListView;private ArrayAdapter<Recorder> mAdapter;private List<Recorder> mDatas = new ArrayList<Recorder>();private AudioRecorderButton mAudioRecorderButton;private View mAnimView;@Overrideprotected void onCreate(Bundle savedInstanceState) {// TODO Auto-generated method stubsuper.onCreate(savedInstanceState);setContentView(R.layout.activity_main);mListView = (ListView) findViewById(R.id.voiceNotesListView);mAudioRecorderButton = (AudioRecorderButton) findViewById(R.id.recorderButton);mAudioRecorderButton.setAudioFinishRecorderListenter(new AudioFinishRecorderListenter() {@Overridepublic void onFinish(float seconds, String FilePath) {// TODO Auto-generated method stubRecorder recorder = new Recorder(seconds, FilePath);mDatas.add(recorder);mAdapter.notifyDataSetChanged();mListView.setSelection(mDatas.size() - 1);}});mAdapter = new AudioRecorderAdapter(this, mDatas);mListView.setAdapter(mAdapter);mListView.setOnItemClickListener(new OnItemClickListener() {@Overridepublic void onItemClick(AdapterView<?> parent, View view,int position, long id) {if (mAnimView != null) {mAnimView.setBackgroundResource(R.drawable.adj);mAnimView = null;}// 播放动画mAnimView = view.findViewById(R.id.recorder_anim);mAnimView.setBackgroundResource(R.drawable.audio_play_anim);AnimationDrawable anim = (AnimationDrawable) mAnimView.getBackground();anim.start();// 播放音频MediaManage.playSound(mDatas.get(position).filePath,new MediaPlayer.OnCompletionListener() {@Overridepublic void onCompletion(MediaPlayer mp) {mAnimView.setBackgroundResource(R.drawable.adj);}});}});}public class Recorder {float time;String filePath;public Recorder(float time, String filePath) {super();this.time = time;this.filePath = filePath;}public float getTime() {return time;}public void setTime(float time) {this.time = time;}public String getFilePath() {return filePath;}public void setFilePath(String filePath) {this.filePath = filePath;}}@Overrideprotected void onPause() {// TODO Auto-generated method stubsuper.onPause();MediaManage.pause();}@Overrideprotected void onResume() {// TODO Auto-generated method stubsuper.onResume();MediaManage.resume();}@Overrideprotected void onDestroy() {// TODO Auto-generated method stubsuper.onDestroy();MediaManage.release();}
}

全部代码:https://github.com/songshimvp/VoiceNotes

Android仿微信语音聊天相关推荐

  1. android 仿微信语音聊天

    android 仿微信语音聊天 跟着imooc老师学习 代码地址: https://github.com/tingsky9985/Weixin_Recorder

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

    这篇文章主要为大家详细介绍了Android仿微信语音聊天界面设计代码,具有一定的参考价值,感兴趣的小伙伴们可以参考一下 有段时间没有看视频了,昨天晚上抽了点空时间,又看了下鸿洋大神的视频教程,又抽时间 ...

  3. android仿微信语音聊天功能,Android仿微信发送语音消息的功能及示例代码

    微信的发送语音是有一个向上取消的,我们使用ontouchlistener来监听手势,然后做出相应的操作就行了. 直接上代码: //语音操作对象 private mediaplayer mplayer ...

  4. Android 仿微信语音聊天音量大小显示控件

    某日用微信语音功能聊天,发现当我使用语音功能时,会弹出一个窗口,窗口中间有一个控件会实时的显示我说话声音的大小(即分贝).当时觉得挺好玩,决定也仿制一个,效果如下 分析控件显示效果,可判断左边是一个i ...

  5. Android 仿微信语音聊天,正式加入字节跳动

    /** 恢复标志位 */ private void resetState() { isRecording = false; isReady = false; changeState(STATE_NOR ...

  6. Android 仿微信语音聊天,flutter项目结构

    4.录音类里有两个成员:录音长度,录音路径. 下面贴一下代码: 自定义Button package com.zms.wechatrecorder.view; import com.zms.wechat ...

  7. Android仿微信语音聊天demo

    其实我接触android时间也不是很久,但是发现android远远比我们想象的要有趣并且复杂很多,所以还是要多花点时间来写一写这些demo例子,这个程序是我从慕课网上学来的,因为毕竟要自己手写,才能体 ...

  8. java实现仿微信app聊天功能_Android仿微信语音聊天功能

    本文实例讲述了Android仿微信语音聊天功能代码.分享给大家供大家参考.具体如下: 项目效果如下: 具体代码如下: AudioManager.java package com.xuliugen.we ...

  9. android高仿微信聊天页面,Android 高仿微信语音聊天页面高斯模糊(毛玻璃效果)

    目前的应用市场上,使用毛玻璃效果的APP随处可见,比如用过微信语音聊天的人可以发现,语音聊天页面就使用了高斯模糊效果. 先看下效果图: 仔细观察上图,我们可以发现,背景图以用户头像为模板,对其进行了高 ...

最新文章

  1. Rushcrm:客户关系管理适合的才是好的
  2. python手机版安卓-用Python实现自动化操作Android手机
  3. Android性能优化典范
  4. 测试手机信号格数软件,超详细教程之教你如何查询手机信号的强度
  5. JavaScript 变量及数据类型
  6. IoT:电子密本ECB和DES模式详解
  7. c2065c语言错误,C语言常见错误
  8. 2021年危险化学品经营单位主要负责人考试技巧及危险化学品经营单位主要负责人模拟考试题库
  9. python分组求和_利用pandas进行分组求和
  10. 计算机英语拼读法,常用计算机英语词汇:DVD
  11. this关键字与super关键字
  12. Python 时间序列数据平滑去噪 Savitzky-Golay滤波器
  13. QQ音乐会员免费领取
  14. MacOS下AndroidStudio无法启动
  15. win7取消计算机密码怎么设置,Windows7取消开机密码怎么设置_Win7怎么取消开机密码?-192路由网...
  16. SaaS ToB产品的易用性设计2
  17. cacti nagios nginx squid等怎么读?
  18. braft-editor 富文本编辑器在谷歌复制图片出现两张
  19. java毕业设计车辆违规信息管理系统Mybatis+系统+数据库+调试部署
  20. 两边同时取对数求复合函数_取对数求导法的例题 取对数求导法

热门文章

  1. 电脑找不到网络许可管理器_许可到网络
  2. 文本分类:4、工程经验
  3. 深度分析: Google 和 Apple 从来就不是死对头
  4. 单片机---1.仿真实现跑马灯(从左往右,在从右往左)
  5. 广州润衡网吧装饰,很牛的网吧装饰
  6. MySQL使用group by分组查询每组最新的一笔数据
  7. 盘点国内6大抗DDOS攻击服务商
  8. ODBC连接数据库使用动态密码
  9. 网络安全工程师的简单介绍
  10. 编译调试 chromium/v8