Android开发--仿微信语音对讲录音
在此感谢鸿洋大神,因为我这是在慕课上看大神的视频做出来的。
代码中我已经添加了很多很多注释,不光是为了大家,也是为了自己能够更加透彻的理解该功能
支持原创,也不算原创了哈哈~
http://blog.csdn.net/lhk147852369/article/details/78658055
注意注意:
Android 6.0动态获取录音权限,我并没有加上,所以你们需要在写完代码后,运行时在权限管理中指定该权限
否则会崩溃哦~~, 当然你们可以改变as中的targerversion<23就可以了
话不多说,直接上效果图:
MainActivity :
- public class MainActivity extends AppCompatActivity {
- private ListView mListView;
- private ArrayAdapter<Recorder> mAdapter;
- private List<Recorder> mDatas =new ArrayList<>();
- private AudioRecorderButton mAudioRecorderButton;
- private View mAnimView;
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- setContentView(R.layout.activity_main);
- initView();
- setListViewAdapter();
- }
- private void initView(){
- mListView = findViewById(R.id.id_listview);
- mAudioRecorderButton = findViewById(R.id.id_recorder_button);
- mAudioRecorderButton.setAudioFinishRecorderListener(new AudioRecorderButton.AudioFinishRecorderListener() {
- @Override
- public void onFinish(float seconds, String filePath) {
- //每完成一次录音
- Recorder recorder = new Recorder(seconds,filePath);
- mDatas.add(recorder);
- //更新adapter
- mAdapter.notifyDataSetChanged();
- //设置listview 位置
- mListView.setSelection(mDatas.size()-1);
- }
- });
- }
- private void setListViewAdapter(){
- mAdapter = new RecorderAdapter(this, mDatas);
- mListView.setAdapter(mAdapter);
- mListView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
- @Override
- public 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.id_recorder_anim);
- mAnimView.setBackgroundResource(R.drawable.play_anim);
- AnimationDrawable animation = (AnimationDrawable) mAnimView.getBackground();
- animation.start();
- //播放音频 完成后改回原来的background
- MediaManager.playSound(mDatas.get(position).filePath, new MediaPlayer.OnCompletionListener() {
- @Override
- public void onCompletion(MediaPlayer mp) {
- mAnimView.setBackgroundResource(R.drawable.adj);
- }
- });
- }
- });
- }
- /**
- * 根据生命周期 管理播放录音
- */
- @Override
- protected void onPause() {
- super.onPause();
- MediaManager.pause();
- }
- @Override
- protected void onResume() {
- super.onResume();
- MediaManager.resume();
- }
- @Override
- protected void onDestroy() {
- super.onDestroy();
- MediaManager.release();
- }
- //数据类
- class Recorder{
- float time;
- String 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;
- }
- public Recorder(float time, String filePath) {
- super();
- this.time = time;
- this.filePath = filePath;
- }
- }
- }
主页面:
需要注意:改下自定义button的包名哦,不然会找不到的
- <?xml version="1.0" encoding="utf-8"?>
- <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:app="http://schemas.android.com/apk/res-auto"
- xmlns:tools="http://schemas.android.com/tools"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:orientation="vertical"
- tools:context="com.lgoutech.weixin_recorder.MainActivity">
- <ListView
- android:layout_width="match_parent"
- android:layout_height="0dp"
- android:layout_weight="1"
- android:id="@+id/id_listview"
- android:background="#ebebeb"
- android:divider="@null"
- android:dividerHeight="10dp"
- ></ListView>
- <LinearLayout
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:orientation="vertical"
- >
- <View
- android:layout_width="wrap_content"
- android:layout_height="1dp"
- android:background="#ccc"
- />
- <com.lgoutech.weixin_recorder.view.AudioRecorderButton
- android:id="@+id/id_recorder_button"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:layout_marginLeft="50dp"
- android:layout_marginRight="50dp"
- android:gravity="center"
- android:layout_marginTop="7dp"
- android:layout_marginBottom="7dp"
- android:padding="5dp"
- android:text="@string/str_recorder_normal"
- android:textColor="#727272"
- android:background="@drawable/btn_recorder_normal"
- android:minHeight="0dp"
- >
- </com.lgoutech.weixin_recorder.view.AudioRecorderButton>
- </LinearLayout>
- </LinearLayout>
自定义的dialog、xml文件:
- <?xml version="1.0" encoding="utf-8"?>
- <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:app="http://schemas.android.com/apk/res-auto"
- xmlns:tools="http://schemas.android.com/tools"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:orientation="vertical"
- android:padding="20dp"
- android:gravity="center"
- android:background="@drawable/dialog_loading_bg"
- tools:context="com.lgoutech.weixin_recorder.MainActivity">
- <LinearLayout
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:orientation="horizontal"
- >
- <ImageView
- android:id="@+id/id_recorder_dialog_icon"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:src="@drawable/recorder"
- android:visibility="visible"
- />
- <ImageView
- android:id="@+id/id_recorder_dialog_voice"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:src="@drawable/v1"
- android:visibility="visible"
- />
- </LinearLayout>
- <TextView
- android:id="@+id/id_recorder_dialog_label"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_marginTop="5dp"
- android:text="手指上划,取消发送"
- android:textColor="#FFFFFF"
- />
- </LinearLayout>
ListView的Item布局:
- <?xml version="1.0" encoding="utf-8"?>
- <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:app="http://schemas.android.com/apk/res-auto"
- xmlns:tools="http://schemas.android.com/tools"
- android:layout_width="match_parent"
- android:layout_height="60dp"
- android:layout_marginTop="5dp"
- tools:context="com.lgoutech.weixin_recorder.MainActivity">
- <ImageView
- android:id="@+id/id_icon"
- android:layout_width="40dp"
- android:layout_height="40dp"
- android:layout_alignParentRight="true"
- android:layout_centerVertical="true"
- android:layout_marginRight="5dp"
- android:src="@drawable/icon"
- />
- <LinearLayout
- android:id="@+id/id_recorder_length"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_centerVertical="true"
- android:orientation="vertical"
- android:layout_toLeftOf="@id/id_icon"
- android:background="@drawable/chatto_bg_focused"
- >
- <View
- android:id="@+id/id_recorder_anim"
- android:layout_width="25dp"
- android:layout_height="25dp"
- android:layout_gravity="center_vertical|right"
- android:background="@drawable/adj"
- />
- </LinearLayout>
- <TextView
- android:id="@+id/id_recorder_time"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_toLeftOf="@id/id_recorder_length"
- android:layout_marginRight="3dp"
- android:textColor="#ff777777"
- android:layout_centerVertical="true"
- />
- </RelativeLayout>
录音管理类:
- public class AudioManager {
- private MediaRecorder mMediaRecorder;
- private String mDir;
- private String mCurrentFilePath;
- private static AudioManager mInstance;
- private boolean isPrepared;
- public AudioManager(String dir){
- mDir = dir;
- };
- /**
- * 回调准备完毕
- */
- public interface AudioStateListener {
- void wellPrepared();
- }
- public AudioStateListener mListener;
- public void setOnAudioStateListener(AudioStateListener listener){
- mListener = listener;
- }
- public static AudioManager getInstance(String dir){
- if (mInstance == null) {
- synchronized (AudioManager.class) {
- if (mInstance == null) {
- mInstance = new AudioManager(dir);
- }
- }
- }
- return mInstance;
- }
- /**
- * 准备
- */
- public void prepareAudio() {
- try {
- isPrepared = false;
- File dir = new File(mDir);
- if (!dir.exists()) {
- dir.mkdir();
- }
- 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);
- //设置音频格式
- mMediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.RAW_AMR);
- //设置音频的格式为amr
- mMediaRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AMR_NB);
- mMediaRecorder.prepare();
- mMediaRecorder.start();
- //准备结束
- isPrepared = true;
- if (mListener != null) {
- mListener.wellPrepared();
- }
- } catch (IOException e) {
- e.printStackTrace();
- }
- }
- // 生成UUID唯一标示符
- // 算法的核心思想是结合机器的网卡、当地时间、一个随即数来生成GUID
- // .amr音频文件
- private String generateFileName() {
- return UUID.randomUUID().toString()+".amr";
- }
- public int getVoiceLevel(int maxLevel) {
- if (isPrepared) {
- //获得最大的振幅getMaxAmplitude() 1-32767
- try {
- return maxLevel * mMediaRecorder.getMaxAmplitude()/32768+1;
- } catch (Exception e) {
- }
- }
- 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() {
- return mCurrentFilePath;
- }
- }
最重要的自定义按钮,很多逻辑都在这里:
- /**
- * 自定义按钮 实现录音等功能
- * Created by Administrator on 2017/11/28.
- */
- @SuppressLint("AppCompatCustomView")
- public class AudioRecorderButton extends Button implements AudioManager.AudioStateListener {
- //手指滑动 距离
- private static final int DISTANCE_Y_CANCEL = 50;
- //状态
- private static final int STATE_NORMAL = 1;
- private static final int STATE_RECORDING = 2;
- private static final int STATE_WANT_TO_CANCEL = 3;
- //当前状态
- private int mCurState = STATE_NORMAL;
- //已经开始录音
- private boolean isRecording = false;
- private DialogManager mDialogManager;
- private AudioManager mAudioManager;
- private float mTime;
- //是否触发onlongclick
- private boolean mReady;
- public AudioRecorderButton(Context context) {
- this(context, null);
- }
- public AudioRecorderButton(Context context, AttributeSet attrs) {
- super(context, attrs);
- mDialogManager = new DialogManager(getContext());
- //偷个懒,并没有判断 是否存在, 是否可读。
- String dir = Environment.getExternalStorageDirectory() + "/recorder_audios";
- mAudioManager = new AudioManager(dir);
- mAudioManager.setOnAudioStateListener(this);
- //按钮长按 准备录音 包括start
- setOnLongClickListener(new OnLongClickListener() {
- @Override
- public boolean onLongClick(View v) {
- mReady = true;
- mAudioManager.prepareAudio();
- return false;
- }
- });
- }
- /**
- * 录音完成后的回调
- */
- public interface AudioFinishRecorderListener{
- //时长 和 文件
- void onFinish(float seconds,String filePath);
- }
- private AudioFinishRecorderListener mListener;
- public void setAudioFinishRecorderListener (AudioFinishRecorderListener listener){
- mListener = listener;
- }
- //获取音量大小的Runnable
- private Runnable mGetVoiceLevelRunnable = new Runnable() {
- @Override
- public void run() {
- while (isRecording) {
- try {
- Thread.sleep(100);
- mTime += 0.1;
- mHandler.sendEmptyMessage(MSG_VOICE_CHANGED);
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- }
- }
- };
- private static final int MSG_AUDIO_PREPARED = 0X110;
- private static final int MSG_VOICE_CHANGED = 0X111;
- private static final int MSG_DIALOG_DIMISS = 0X112;
- private Handler mHandler = new Handler() {
- @Override
- public void handleMessage(Message msg) {
- switch (msg.what) {
- case MSG_AUDIO_PREPARED :
- //TODO 真正现实应该在audio end 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;
- }
- }
- };
- @Override
- public void wellPrepared() {
- mHandler.sendEmptyMessage(MSG_AUDIO_PREPARED);
- }
- @Override
- public boolean onTouchEvent(MotionEvent event) {
- int action = event.getAction();
- int x = (int) event.getX();
- int y = (int) event.getY();
- switch (action) {
- case MotionEvent.ACTION_DOWN:
- //TODO
- isRecording = true;
- changeState(STATE_RECORDING);
- break;
- case MotionEvent.ACTION_MOVE:
- if (isRecording) {
- //根据想x,y的坐标,判断是否想要取消
- if (wantToCancel(x, y)) {
- changeState(STATE_WANT_TO_CANCEL);
- } else {
- changeState(STATE_RECORDING);
- }
- }
- break;
- case MotionEvent.ACTION_UP:
- //如果longclick 没触发
- if (!mReady) {
- reset();
- return super.onTouchEvent(event);
- }
- //触发了onlongclick 没准备好,但是已经prepared 已经start
- //所以消除文件夹
- if(!isRecording||mTime<0.6f){
- mDialogManager.tooShort();
- mAudioManager.cancel();
- mHandler.sendEmptyMessageDelayed(MSG_DIALOG_DIMISS, 1300);
- }else if(mCurState==STATE_RECORDING){//正常录制结束
- mDialogManager.dimissDialog();
- mAudioManager.release();
- if (mListener != null) {
- mListener.onFinish(mTime,mAudioManager.getCurrentFilePath());
- }
- }else if (mCurState == STATE_RECORDING) {
- mDialogManager.dimissDialog();
- //release
- //callbacktoAct
- } else if (mCurState == STATE_WANT_TO_CANCEL) {
- mDialogManager.dimissDialog();
- mAudioManager.cancel();
- //cancel
- }
- reset();
- break;
- }
- return super.onTouchEvent(event);
- }
- /**
- * 恢复状态 标志位
- */
- private void reset() {
- isRecording = false;
- mReady = false;
- changeState(STATE_NORMAL);
- mTime = 0;
- }
- private boolean wantToCancel(int x, int y) {
- //如果左右滑出 button
- if (x < 0 || x > getWidth()) {
- return true;
- }
- //如果上下滑出 button 加上我们自定义的距离
- if (y < -DISTANCE_Y_CANCEL || y > getHeight() + DISTANCE_Y_CANCEL) {
- return true;
- }
- return false;
- }
- //改变状态
- 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_RECORDING:
- setBackgroundResource(R.drawable.btn_recording);
- setText(R.string.str_recorder_recording);
- if (isRecording) {
- mDialogManager.recording();
- }
- break;
- case STATE_WANT_TO_CANCEL:
- setBackgroundResource(R.drawable.btn_recording);
- setText(R.string.str_recorder_want_cancel);
- mDialogManager.wantToCancel();
- break;
- }
- }
- }
- }
Dialog管理类:
- public class DialogManager {
- private Dialog mDialog;
- private ImageView mIcon;
- private ImageView mVoice;
- private TextView mLable;
- private Context mContext;
- public DialogManager(Context context){
- mContext = context;
- }
- public void showRecordingDialog(){
- mDialog = new Dialog(mContext, R.style.Theme_AudioDialog);
- LayoutInflater inflater = LayoutInflater.from(mContext);
- View view=inflater.inflate(R.layout.dialog_recorder,null);
- mDialog.setContentView(view);
- mIcon = view.findViewById(R.id.id_recorder_dialog_icon);
- mVoice = view.findViewById(R.id.id_recorder_dialog_voice);
- mLable = view.findViewById(R.id.id_recorder_dialog_label);
- mDialog.show();
- }
- //正在播放时的状态
- 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("手指上划,取消发送");
- }
- }
- //想要取消
- 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("松开手指,取消发送");
- }
- }
- //录音时间太短
- 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("录音时间过短");
- }
- }
- //关闭dialog
- public void dimissDialog(){
- if (mDialog != null&&mDialog.isShowing()) {
- mDialog.dismiss();
- mDialog = null;
- }
- }
- /**
- * 通过level更新voice上的图片
- *
- * @param level
- */
- public void updateVoiceLevel(int level){
- if (mDialog != null&&mDialog.isShowing()) {
- int resId = mContext.getResources().getIdentifier("v" + level, "drawable", mContext.getPackageName());
- mVoice.setImageResource(resId);
- }
- }
- }
播放录音类:
- /**
- * Created by Administrator on 2017/11/28.
- * 播放录音类
- */
- public class MediaManager {
- private static MediaPlayer mMediaPlayer;
- private static boolean isPause;
- //播放录音
- public static void playSound(String filePath, MediaPlayer.OnCompletionListener onCompletionListener){
- if (mMediaPlayer == null) {
- mMediaPlayer = new MediaPlayer();
- //播放错误 防止崩溃
- mMediaPlayer.setOnErrorListener(new MediaPlayer.OnErrorListener() {
- @Override
- public boolean onError(MediaPlayer mp, int what, int extra) {
- mMediaPlayer.reset();
- return false;
- }
- });
- }else{
- mMediaPlayer.reset();
- }
- try {
- mMediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);
- mMediaPlayer.setOnCompletionListener(onCompletionListener);
- mMediaPlayer.setDataSource(filePath);
- mMediaPlayer.prepare();
- mMediaPlayer.start();
- } catch (IOException e) {
- e.printStackTrace();
- }
- }
- /**
- * 如果 播放时间过长,如30秒
- * 用户突然来电话了,则需要暂停
- */
- 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;
- }
- }
- /**
- * activity 被销毁 释放
- */
- public static void release(){
- if (mMediaPlayer != null) {
- mMediaPlayer.release();
- mMediaPlayer = null;
- }
- }
- }
listview 的Adapter:
- /**
- *list的adapter
- * Created by Administrator on 2017/11/28.
- */
- public class RecorderAdapter extends ArrayAdapter<Recorder> {
- //item 最小最大值
- private int mMinItemWidth;
- private int mMaxIItemWidth;
- private LayoutInflater mInflater;
- public RecorderAdapter(@NonNull Context context, List<Recorder> datas) {
- super(context,-1, datas);
- mInflater = LayoutInflater.from(context);
- //获取屏幕宽度
- WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
- DisplayMetrics outMetrics = new DisplayMetrics();
- wm.getDefaultDisplay().getMetrics(outMetrics);
- //item 设定最小最大值
- mMaxIItemWidth = (int) (outMetrics.widthPixels * 0.7f);
- mMinItemWidth = (int) (outMetrics.widthPixels * 0.15f);
- }
- @NonNull
- @Override
- public View getView(int position, @Nullable View convertView, @NonNull ViewGroup parent) {
- ViewHolder holder = null;
- if (convertView == null) {
- convertView = mInflater.inflate(R.layout.item_recorder, parent, false);
- holder = new ViewHolder();
- holder.seconds = convertView.findViewById(R.id.id_recorder_time);
- holder.length = convertView.findViewById(R.id.id_recorder_length);
- convertView.setTag(holder);
- }else {
- holder = (ViewHolder) convertView.getTag();
- }
- //设置时间 matt.round 四舍五入
- holder.seconds.setText(Math.round(getItem(position).time)+"\"");
- //设置背景的宽度
- ViewGroup.LayoutParams lp = holder.length.getLayoutParams();
- //getItem(position).time
- lp.width = (int) (mMinItemWidth + (mMaxIItemWidth / 60f*getItem(position).time));
- return convertView;
- }
- private class ViewHolder{
- TextView seconds;
- View length;
- }
- }
在string.xml中
- <string name="str_recorder_normal">按住说话</string>
- <string name="str_recorder_recording">松开结束</string>
- <string name="str_recorder_want_cancel">松开手指,取消发送</string>
在style.xml中
- <style name="Theme_AudioDialog">
- <item name="android:windowBackground">@android:color/transparent</item>
- <!--Dialog的windowFrame框为无-->
- <item name="android:windowFrame">@null</item>
- <!--是否浮现在activity之上-->
- <item name="android:windowIsFloating">true</item>
- <!--//是否半透明-->
- <item name="android:windowIsTranslucent">true</item>
- <!--/背景是否模糊显示-->
- <item name="android:backgroundDimEnabled">true</item>
- </style>
好了以上就是所有的代码了。
不懂得可以问我哦,或者自己查查百度吧。
我的注释已经很多了,理理逻辑,应该很好懂的。
源码地址:源码
图片资源:资源图片
Android开发--仿微信语音对讲录音相关推荐
- android高仿微信聊天页面,Android 高仿微信语音聊天页面高斯模糊(毛玻璃效果)
目前的应用市场上,使用毛玻璃效果的APP随处可见,比如用过微信语音聊天的人可以发现,语音聊天页面就使用了高斯模糊效果. 先看下效果图: 仔细观察上图,我们可以发现,背景图以用户头像为模板,对其进行了高 ...
- android录音声波动画,Android开发:仿微信 录音声波
最近在仿微信开发聊天界面:开发到一个有意思的小东西,简单写一写.就是录音声波这块. 首先先添加几个图片 添加如上三个声波图表 之后建立一个数组 private int[] images = {R.mi ...
- php仿微信朋友圈源码,Android开发仿微信发朋友圈浏览图片效果实例代码
这篇文章主要介绍了Android仿微信发朋友圈浏览图片效果的相关资料,需要的朋友可以参考下 先看一下效果吧: 下面就来说一下具体怎么实现的: 实现思路1.首先我们要获取数据源,数据源就是我们的每条说说 ...
- Android 开发仿微信位置分享
仿微信位置共享 效果图 源码下载 http://download.csdn.net/detail/en24414115/9319161
- Android自定义控件——仿微信语音按钮
对于更多自定义控件的介绍,可以参考这篇:Android自定义view组合控件解析 示例 就是下方那个按钮(可以忽略ios系统) 使用 在xml中直接调用即可: public class VoiceB ...
- android开发--仿微信朋友圈界面
话不多说,先来看两张效果图 看图片效果还凑合,就是"朋友圈"三个字和头像的动画过渡效果和微信朋友圈的实际效果还是有点差距,可以的话以后慢慢再优化吧,这里贴出相关的代码,可能部分代码 ...
- Android 仿微信语音录音小控件
Android 仿微信语音录音小控件 前段时间一直在做IM聊天这方面的撸码工作,涉及到了很多小控件,有时间我会慢慢给大家分享一下我的小成果,希望大家一起学习,一起进步,今天来和大家来说一下Androi ...
- android按住录音按钮_Android实现录音方法(仿微信语音、麦克风录音、发送语音、解决5.0以上BUG)...
先给大家展示下效果图,如果大家感觉不错,请参考使用方法, 效果图如下所示: 使用方法: 录音工具类:AudioRecoderUtils.java,代码如下: public class AudioRec ...
- android放微信@功能,Android仿微信语音消息的录制和播放功能
一.简述 效果: 实现功能: 长按Button时改变Button显示文字,弹出Dialog(动态更新音量),动态生成录音文件,开始录音: 监听手指动作,规定区域.录音状态下手指划出规定区域取消录音,删 ...
最新文章
- Java学生宿舍管理系统,即将毕业的兄弟有福了!
- kvo实现原理_KVC、KVO实现原理
- Android EditText
- windbg+VM 设置内核调试环境(双机调试)
- 黑马lavarel教程---12、lavarel验证码
- C语言中嵌入正则表达式
- php 单用户登录,Linux 系统的单用户模式、修复模式、跨控制台登录在系统修复中的运用...
- tkinter 菜单添加事件_tkinter学习教程(七)
- vue设置多选框默认勾选_Angular/Vue多复选框勾选问题
- Sharepoin学习笔记 —架构系列--02 Sharepoint的处理(Process)与执行模型(Trust Model) 1
- C#LeetCode刷题-树状数组
- ubuntu mysql 连接_Ubuntu 配置MySQL远程连接
- 老王讲设计模式(八)——适配器模式
- Android:Touch和Click的区别
- SLAM会议笔记(五)LLOAM
- 深度系统优化工具_HiBit Uninstaller卸载工具【win版】
- 使用Python在指定文件夹新建一个文本文档(其他类型文件也可)
- 象棋游戏java代码_象棋游戏 - java代码库 - 云代码
- 【观察】帆软:扎根于BI,收获于未来
- 区块链学习者终极指南
热门文章
- java里jlp是什么意思,JLP是什么意思
- matlab2017b怎么进行sil测试,基于Matlab的模型自动测试方法与流程
- 大数据可以揭示基因密码吗?
- 3D建模怎么学?3D游戏建模怎么学?【内附各种建模软件、学习教程】想自学的戳进来
- ERP企业管理系统有哪些运用技巧?
- 优思学院|质量人如何利用ChatGPT提升工作效率?
- 如何让企业督办管理系统对接第三方应用
- wps office2019PC版和Mac版_Mac版Office特有的功能 Windows用户别眼馋
- 大数据之kafka简介
- 51单片机 74HC154译码器制作流水灯+Proteus仿真