├── .gitignore ├── .idea ├── compiler.xml ├── copyright │ └── profiles_settings.xml ├── gradle.xml ├── misc.xml ├── modules.xml └── runConfigurations.xml ├── README.md ├── VoiceManager-master.iml ├── VoiceManager.iml ├── app ├── .gitignore ├── app.iml ├── build.gradle ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── com │ │ └── jaydenxiao │ │ └── voicemanager │ │ └── ApplicationTest.java │ └── main │ ├── AndroidManifest.xml │ ├── java │ └── com │ │ └── jaydenxiao │ │ └── voicemanager │ │ ├── Arith.java │ │ ├── BaseCommonAdapter.java │ │ ├── MainActivity.java │ │ ├── RecordVoiceButton.java │ │ ├── ViewHolderUtil.java │ │ ├── Voice.java │ │ ├── VoiceAdapter.java │ │ ├── VoiceLineView.java │ │ ├── VoiceManager.java │ │ └── VoiceTimeUtils.java │ └── res │ ├── drawable-hdpi │ ├── icon_complete.png │ ├── icon_continue.png │ ├── icon_pause.png │ ├── icon_record.png │ ├── icon_voice_record.png │ ├── voicetip_play_1.png │ ├── voicetip_play_2.png │ └── voicetip_play_3.png │ ├── drawable │ ├── playbar_style.xml │ ├── round_background.xml │ ├── round_selector.xml │ └── voice_receive.xml │ ├── layout │ ├── activity_main.xml │ ├── dialog_record_voice.xml │ └── item_voice_list.xml │ ├── menu │ └── menu_main.xml │ ├── mipmap-hdpi │ └── ic_launcher.png │ ├── mipmap-mdpi │ └── ic_launcher.png │ ├── mipmap-xhdpi │ └── ic_launcher.png │ ├── mipmap-xxhdpi │ └── ic_launcher.png │ ├── values-w820dp │ └── dimens.xml │ └── values │ ├── attrs.xml │ ├── colors.xml │ ├── dimens.xml │ ├── strings.xml │ └── styles.xml ├── art ├── Screenshot_2017-03-21-17-28-01.png ├── Screenshot_2017-03-21-17-28-05.png ├── Screenshot_2017-03-21-17-28-26.png └── art.mp4 ├── build.gradle ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── hisign.html ├── settings.gradle └── video_20180702_130810.mp4 /.gitignore: -------------------------------------------------------------------------------- 1 | .gradle 2 | /local.properties 3 | /.idea/workspace.xml 4 | /.idea/libraries 5 | .DS_Store 6 | /build 7 | /captures 8 | -------------------------------------------------------------------------------- /.idea/compiler.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /.idea/copyright/profiles_settings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /.idea/gradle.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 17 | 18 | -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 19 | -------------------------------------------------------------------------------- /.idea/modules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /.idea/runConfigurations.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 11 | 12 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | VoiceManager 2 | ============ 3 | 1.实现录音功能,提供暂停、继续切换,并且监听音量大小以波浪线呈现出现 4 | 5 | 2.实现播放功能,播放实现帧动画 6 | 7 | 用法:   8 | 9 |     1.录音   10 | 11 | VoiceManage mVoiceManage =VoiceManager.getInstance(mContext); 12 | 13 | mVoiceManage.pauseOrStartVoiceRecord();//暂停或继续 14 | 15 | mVoiceManage.stopVoiceRecord();//完成录音 16 | 17 | mVoiceManage.setVoiceRecordListener(new VoiceManager.VoiceRecordCallBack() { 18 | @Override 19 | public void recDoing(long time, String strTime) { 20 | mRecordHintTv.setText(strTime); 21 | } 22 | 23 | @Override 24 | public void recVoiceGrade(int grade) { 25 | voicLine.setVolume(grade); 26 | } 27 | 28 | @Override 29 | public void recStart(boolean init) { 30 | mIvPauseContinue.setImageResource(R.drawable.icon_pause); 31 | voicLine.setContinue(); 32 | } 33 | 34 | @Override 35 | public void recPause(String str) { 36 | mIvPauseContinue.setImageResource(R.drawable.icon_continue); 37 | voicLine.setPause(); 38 | } 39 | 40 | 41 | @Override 42 | public void recFinish(long length, String strLength, String path) { 43 | if (enRecordVoiceListener != null) { 44 | enRecordVoiceListener.onFinishRecord(length, strLength, path); 45 | } 46 | } 47 | }); 48 | 49 | 2.播放 50 | VoiceManage mVoiceManage =VoiceManager.getInstance(mContext); 51 | mVoiceManage.setVoicePlayListener(new VoiceManager.VoicePlayCallBack() { 52 | @Override 53 | public void voiceTotalLength(long time, String strTime) { 54 | 55 | } 56 | 57 | @Override 58 | public void playDoing(long time, String strTime) { 59 | 60 | 61 | } 62 | 63 | @Override 64 | public void playPause() { 65 | 66 | } 67 | 68 | @Override 69 | public void playStart() { 70 | 71 | } 72 | 73 | @Override 74 | public void playFinish() { 75 | if (voiceAnimation != null) { 76 | voiceAnimation.stop(); 77 | voiceAnimation.selectDrawable(0); 78 | } 79 | } 80 | }); 81 | mVoiceManage.startPlay(voice.getFilePath()); 82 |       83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | License 92 | ======= 93 | 94 | The MIT License (MIT) 95 | 96 | Copyright (c) 2017 jaydenxiao2016 97 | 98 | Permission is hereby granted, free of charge, to any person obtaining a copy 99 | of this software and associated documentation files (the "Software"), to deal 100 | in the Software without restriction, including without limitation the rights 101 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 102 | copies of the Software, and to permit persons to whom the Software is 103 | furnished to do so, subject to the following conditions: 104 | 105 | The above copyright notice and this permission notice shall be included in all 106 | copies or substantial portions of the Software. 107 | 108 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 109 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 110 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 111 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 112 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 113 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 114 | SOFTWARE. 115 | -------------------------------------------------------------------------------- /VoiceManager-master.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /VoiceManager.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /app/app.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 8 | 9 | 10 | 11 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | 3 | android { 4 | compileSdkVersion 25 5 | buildToolsVersion "25.0.1" 6 | 7 | defaultConfig { 8 | applicationId "com.jaydenxiao.voicemanager" 9 | minSdkVersion 15 10 | targetSdkVersion 25 11 | versionCode 1 12 | versionName "1.0" 13 | } 14 | buildTypes { 15 | release { 16 | minifyEnabled false 17 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 18 | } 19 | } 20 | } 21 | 22 | dependencies { 23 | compile fileTree(dir: 'libs', include: ['*.jar']) 24 | compile 'com.android.support:appcompat-v7:25.1.0' 25 | } 26 | -------------------------------------------------------------------------------- /app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # By default, the flags in this file are appended to flags specified 3 | # in /Users/youzehong/Library/Android/sdk/tools/proguard/proguard-android.txt 4 | # You can edit the include path and order by changing the proguardFiles 5 | # directive in build.gradle. 6 | # 7 | # For more details, see 8 | # http://developer.android.com/guide/developing/tools/proguard.html 9 | 10 | # Add any project specific keep options here: 11 | 12 | # If your project uses WebView with JS, uncomment the following 13 | # and specify the fully qualified class name to the JavaScript interface 14 | # class: 15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 16 | # public *; 17 | #} 18 | -------------------------------------------------------------------------------- /app/src/androidTest/java/com/jaydenxiao/voicemanager/ApplicationTest.java: -------------------------------------------------------------------------------- 1 | package com.jaydenxiao.voicemanager; 2 | 3 | import android.app.Application; 4 | import android.test.ApplicationTestCase; 5 | 6 | /** 7 | * Testing Fundamentals 8 | */ 9 | public class ApplicationTest extends ApplicationTestCase { 10 | public ApplicationTest() { 11 | super(Application.class); 12 | } 13 | } -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | 9 | 14 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /app/src/main/java/com/jaydenxiao/voicemanager/Arith.java: -------------------------------------------------------------------------------- 1 | package com.jaydenxiao.voicemanager; 2 | 3 | import java.math.BigDecimal; 4 | 5 | /** 6 | * Created by carlos on 2016/4/5. 7 | */ 8 | public class Arith{ 9 | 10 | //默认除法运算精度 11 | private static final int DEF_DIV_SCALE = 10; 12 | //这个类不能实例化 13 | private Arith(){ 14 | } 15 | 16 | /** 17 | * 提供精确的加法运算。 18 | * @param v1 被加数 19 | * @param v2 加数 20 | * @return 两个参数的和 21 | */ 22 | public static double add(double v1,double v2){ 23 | BigDecimal b1 = new BigDecimal(Double.toString(v1)); 24 | BigDecimal b2 = new BigDecimal(Double.toString(v2)); 25 | return b1.add(b2).doubleValue(); 26 | } 27 | /** 28 | * 提供精确的减法运算。 29 | * @param v1 被减数 30 | * @param v2 减数 31 | * @return 两个参数的差 32 | */ 33 | public static double sub(double v1,double v2){ 34 | BigDecimal b1 = new BigDecimal(Double.toString(v1)); 35 | BigDecimal b2 = new BigDecimal(Double.toString(v2)); 36 | return b1.subtract(b2).doubleValue(); 37 | } 38 | /** 39 | * 提供精确的乘法运算。 40 | * @param v1 被乘数 41 | * @param v2 乘数 42 | * @return 两个参数的积 43 | */ 44 | public static double mul(double v1,double v2){ 45 | BigDecimal b1 = new BigDecimal(Double.toString(v1)); 46 | BigDecimal b2 = new BigDecimal(Double.toString(v2)); 47 | return b1.multiply(b2).doubleValue(); 48 | } 49 | 50 | /** 51 | * 提供(相对)精确的除法运算,当发生除不尽的情况时,精确到 52 | * 小数点以后10位,以后的数字四舍五入。 53 | * @param v1 被除数 54 | * @param v2 除数 55 | * @return 两个参数的商 56 | */ 57 | public static double div(double v1,double v2){ 58 | return div(v1,v2,DEF_DIV_SCALE); 59 | } 60 | 61 | /** 62 | * 提供(相对)精确的除法运算。当发生除不尽的情况时,由scale参数指 63 | * 定精度,以后的数字四舍五入。 64 | * @param v1 被除数 65 | * @param v2 除数 66 | * @param scale 表示表示需要精确到小数点以后几位。 67 | * @return 两个参数的商 68 | */ 69 | public static double div(double v1,double v2,int scale){ 70 | if(scale<0){ 71 | throw new IllegalArgumentException( 72 | "The scale must be a positive integer or zero"); 73 | } 74 | BigDecimal b1 = new BigDecimal(Double.toString(v1)); 75 | BigDecimal b2 = new BigDecimal(Double.toString(v2)); 76 | return b1.divide(b2,scale, BigDecimal.ROUND_HALF_UP).doubleValue(); 77 | } 78 | 79 | /** 80 | * 提供精确的小数位四舍五入处理。 81 | * @param v 需要四舍五入的数字 82 | * @param scale 小数点后保留几位 83 | * @return 四舍五入后的结果 84 | */ 85 | public static double round(double v,int scale){ 86 | 87 | if(scale<0){ 88 | throw new IllegalArgumentException( 89 | "The scale must be a positive integer or zero"); 90 | } 91 | BigDecimal b = new BigDecimal(Double.toString(v)); 92 | BigDecimal one = new BigDecimal("1"); 93 | return b.divide(one,scale, BigDecimal.ROUND_HALF_UP).doubleValue(); 94 | } 95 | }; 96 | -------------------------------------------------------------------------------- /app/src/main/java/com/jaydenxiao/voicemanager/BaseCommonAdapter.java: -------------------------------------------------------------------------------- 1 | package com.jaydenxiao.voicemanager; 2 | 3 | import android.content.Context; 4 | import android.view.LayoutInflater; 5 | import android.view.View; 6 | import android.view.ViewGroup; 7 | 8 | import java.util.ArrayList; 9 | import java.util.List; 10 | 11 | /** 12 | * 类名:BaseCommonAdapter.java 13 | * 作者:xsf 14 | * 描述:base适配器 15 | */ 16 | public class BaseCommonAdapter extends android.widget.BaseAdapter { 17 | 18 | protected Context mContext; 19 | protected LayoutInflater mInflater; 20 | protected List data; 21 | 22 | public BaseCommonAdapter(Context context) { 23 | super(); 24 | mContext = context; 25 | mInflater = LayoutInflater.from(mContext); 26 | this.data = new ArrayList<>(); 27 | } 28 | 29 | public BaseCommonAdapter(Context context, List data) { 30 | super(); 31 | mContext = context; 32 | if (data != null) { 33 | this.data = data; 34 | } 35 | else { 36 | this.data = new ArrayList<>(); 37 | } 38 | mInflater = LayoutInflater.from(mContext); 39 | } 40 | /** 41 | * 设置数据源 42 | * 43 | * @param d 44 | */ 45 | public void setData(List d) { 46 | data = d; 47 | } 48 | 49 | /** 50 | * 获取数据源 51 | * 52 | * @return 53 | */ 54 | public List getData() { 55 | return data; 56 | } 57 | 58 | /** 59 | * 替换数据源 60 | * 61 | * @param d 62 | */ 63 | public void replaceAllData(List d) { 64 | if (d == null) return; 65 | data.clear(); 66 | data.addAll(d); 67 | notifyDataSetChanged(); 68 | } 69 | 70 | /** 71 | * 增加单个数据 72 | * 73 | * @param d 74 | */ 75 | public void add(T d) { 76 | if (d == null) return; 77 | if (data == null) data = new ArrayList<>(); 78 | data.add(d); 79 | notifyDataSetChanged(); 80 | } 81 | 82 | /** 83 | * 增加数据源 84 | * 85 | * @param d 86 | */ 87 | public void addAll(List d) { 88 | if (d == null) return; 89 | if (data == null) data = new ArrayList<>(); 90 | data.addAll(d); 91 | notifyDataSetChanged(); 92 | } 93 | 94 | /** 95 | * 在某个位置添加数据源 96 | * 97 | * @param position 98 | * @param d 99 | */ 100 | public void addAllAt(int position, List d) { 101 | if (d == null) return; 102 | if (data == null) data = new ArrayList<>(); 103 | data.addAll(position, d); 104 | notifyDataSetChanged(); 105 | } 106 | 107 | /** 108 | * 在某个位置添加单个数据源 109 | * 110 | * @param position 111 | * @param d 112 | */ 113 | public void addAt(int position, T d) { 114 | if (d == null) return; 115 | if (data == null) data = new ArrayList<>(); 116 | data.add(position, d); 117 | notifyDataSetChanged(); 118 | } 119 | 120 | /** 121 | * 移除某个数据源 122 | * 123 | * @param position 124 | */ 125 | public void remove(int position) { 126 | if (data == null) return; 127 | data.remove(position); 128 | notifyDataSetChanged(); 129 | } 130 | 131 | /** 132 | * 清空所有数据 133 | */ 134 | public void clearAll() { 135 | data.clear(); 136 | } 137 | 138 | @Override 139 | public int getCount() { 140 | return data.size(); 141 | } 142 | 143 | @Override 144 | public T getItem(int position) { 145 | return data.get(position); 146 | } 147 | 148 | @Override 149 | public long getItemId(int position) { 150 | return 0; 151 | } 152 | 153 | @Override 154 | public View getView(int position, View convertView, ViewGroup parent) { 155 | return null; 156 | } 157 | 158 | } 159 | -------------------------------------------------------------------------------- /app/src/main/java/com/jaydenxiao/voicemanager/MainActivity.java: -------------------------------------------------------------------------------- 1 | package com.jaydenxiao.voicemanager; 2 | 3 | import android.os.Bundle; 4 | import android.support.v7.app.AppCompatActivity; 5 | import android.widget.ListView; 6 | 7 | /** 8 | * 作者:xsf 9 | */ 10 | public class MainActivity extends AppCompatActivity { 11 | 12 | /** 13 | * 语音列表 14 | */ 15 | private ListView listView; 16 | /** 17 | * 开始录音 18 | */ 19 | private VoiceAdapter adapter; 20 | private RecordVoiceButton mBtRec; 21 | 22 | 23 | @Override 24 | protected void onCreate(Bundle savedInstanceState) { 25 | super.onCreate(savedInstanceState); 26 | setContentView(R.layout.activity_main); 27 | 28 | mBtRec = (RecordVoiceButton) findViewById(R.id.button_rec); 29 | listView = (ListView) findViewById(R.id.lv); 30 | adapter=new VoiceAdapter(this); 31 | listView.setAdapter(adapter); 32 | mBtRec.setEnrecordVoiceListener(new RecordVoiceButton.EnRecordVoiceListener() { 33 | @Override 34 | public void onFinishRecord(long length, String strLength, String filePath) { 35 | adapter.add(new Voice(length, strLength, filePath) ) ; 36 | } 37 | }); 38 | } 39 | 40 | } 41 | -------------------------------------------------------------------------------- /app/src/main/java/com/jaydenxiao/voicemanager/RecordVoiceButton.java: -------------------------------------------------------------------------------- 1 | package com.jaydenxiao.voicemanager; 2 | 3 | 4 | import android.app.Dialog; 5 | import android.content.Context; 6 | import android.os.Environment; 7 | import android.util.AttributeSet; 8 | import android.view.View; 9 | import android.widget.Button; 10 | import android.widget.ImageView; 11 | import android.widget.TextView; 12 | 13 | /** 14 | * 录音控件button 15 | */ 16 | public class RecordVoiceButton extends Button implements View.OnClickListener { 17 | 18 | private Dialog recordIndicator; 19 | private ImageView mVolumeIv,mIvPauseContinue,mIvComplete; 20 | private VoiceLineView voicLine; 21 | private TextView mRecordHintTv; 22 | private Context mContext; 23 | private EnRecordVoiceListener enRecordVoiceListener; 24 | private VoiceManager voiceManager; 25 | 26 | public RecordVoiceButton(Context context) { 27 | super(context); 28 | init(); 29 | } 30 | 31 | public RecordVoiceButton(Context context, AttributeSet attrs, int defStyle) { 32 | super(context, attrs, defStyle); 33 | this.mContext = context; 34 | init(); 35 | } 36 | 37 | public RecordVoiceButton(Context context, AttributeSet attrs) { 38 | super(context, attrs); 39 | this.mContext = context; 40 | init(); 41 | } 42 | 43 | private void init() { 44 | voiceManager =VoiceManager.getInstance(mContext); 45 | setOnClickListener(this); 46 | } 47 | 48 | /** 49 | * 设置监听 50 | * 51 | * @param enRecordVoiceListener 52 | */ 53 | public void setEnrecordVoiceListener(EnRecordVoiceListener enRecordVoiceListener) { 54 | this.enRecordVoiceListener = enRecordVoiceListener; 55 | } 56 | 57 | /** 58 | * 启动录音dialog 59 | */ 60 | private void startRecordDialog() { 61 | recordIndicator = new Dialog(getContext(), R.style.record_voice_dialog); 62 | recordIndicator.setContentView(R.layout.dialog_record_voice); 63 | recordIndicator.setCanceledOnTouchOutside(false); 64 | recordIndicator.setCancelable(false); 65 | mVolumeIv = (ImageView) recordIndicator.findViewById(R.id.iv_voice); 66 | voicLine= (VoiceLineView) recordIndicator.findViewById(R.id.voicLine); 67 | mRecordHintTv = (TextView) recordIndicator.findViewById(R.id.tv_length); 68 | mRecordHintTv.setText("00:00:00"); 69 | mIvPauseContinue= (ImageView) recordIndicator.findViewById(R.id.iv_continue_or_pause); 70 | mIvComplete= (ImageView) recordIndicator.findViewById(R.id.iv_complete); 71 | recordIndicator.show(); 72 | //暂停或继续 73 | mIvPauseContinue.setOnClickListener(new OnClickListener() { 74 | @Override 75 | public void onClick(View view) { 76 | if(voiceManager!=null){ 77 | voiceManager.pauseOrStartVoiceRecord(); 78 | } 79 | } 80 | }); 81 | //完成 82 | mIvComplete.setOnClickListener(new OnClickListener() { 83 | @Override 84 | public void onClick(View view) { 85 | if(voiceManager!=null){ 86 | voiceManager.stopVoiceRecord(); 87 | } 88 | recordIndicator.dismiss(); 89 | } 90 | }); 91 | } 92 | 93 | @Override 94 | public void onClick(View view) { 95 | startRecordDialog(); 96 | voiceManager.setVoiceRecordListener(new VoiceManager.VoiceRecordCallBack() { 97 | @Override 98 | public void recDoing(long time, String strTime) { 99 | mRecordHintTv.setText(strTime); 100 | } 101 | 102 | @Override 103 | public void recVoiceGrade(int grade) { 104 | voicLine.setVolume(grade); 105 | } 106 | 107 | @Override 108 | public void recStart(boolean init) { 109 | mIvPauseContinue.setImageResource(R.drawable.icon_pause); 110 | voicLine.setContinue(); 111 | } 112 | 113 | @Override 114 | public void recPause(String str) { 115 | mIvPauseContinue.setImageResource(R.drawable.icon_continue); 116 | voicLine.setPause(); 117 | } 118 | 119 | 120 | @Override 121 | public void recFinish(long length, String strLength, String path) { 122 | if (enRecordVoiceListener != null) { 123 | enRecordVoiceListener.onFinishRecord(length, strLength, path); 124 | } 125 | } 126 | }); 127 | voiceManager.startVoiceRecord(Environment.getExternalStorageDirectory().getPath()+"/VoiceManager/audio"); 128 | } 129 | 130 | /** 131 | * 结束回调监听 132 | */ 133 | public interface EnRecordVoiceListener { 134 | void onFinishRecord(long length, String strLength, String filePath); 135 | } 136 | 137 | 138 | } 139 | -------------------------------------------------------------------------------- /app/src/main/java/com/jaydenxiao/voicemanager/ViewHolderUtil.java: -------------------------------------------------------------------------------- 1 | package com.jaydenxiao.voicemanager; 2 | 3 | import android.util.SparseArray; 4 | import android.view.View; 5 | 6 | /** 7 | * 类名:BaseCommonAdapter.java 8 | * 描述:viewholder工具 9 | * 作者:xsf 10 | * 创建时间:2016/11/24 11 | * 最后修改时间:2016/11/24 12 | */ 13 | public class ViewHolderUtil { 14 | 15 | @SuppressWarnings("unchecked") 16 | public static T get(View view, int id) { 17 | SparseArray viewHolder = (SparseArray) view.getTag(); 18 | if (viewHolder == null) { 19 | viewHolder = new SparseArray(); 20 | view.setTag(viewHolder); 21 | } 22 | View childView = viewHolder.get(id); 23 | if (childView == null) { 24 | childView = view.findViewById(id); 25 | viewHolder.put(id, childView); 26 | } 27 | return (T) childView; 28 | } 29 | 30 | /* 31 | * 在getview里面的用法 32 | * 33 | * @Override public View getView(int position, View convertView, ViewGroup 34 | * parent) { 35 | * 36 | * if (convertView == null) { convertView = LayoutInflater.from(context) 37 | * .inflate(R.layout.banana_phone, parent, false); } 38 | * 39 | * ImageView bananaView = ViewHolderUtil.get(convertView, R.id.banana); 40 | * TextView phoneView = ViewHolderUtil.get(convertView, R.id.phone); 41 | * 42 | * BananaPhone bananaPhone = getItem(position); 43 | * phoneView.setText(bananaPhone.getPhone()); 44 | * bananaView.setImageResource(bananaPhone.getBanana()); 45 | * 46 | * return convertView; 47 | * } 48 | */ 49 | 50 | } 51 | -------------------------------------------------------------------------------- /app/src/main/java/com/jaydenxiao/voicemanager/Voice.java: -------------------------------------------------------------------------------- 1 | package com.jaydenxiao.voicemanager; 2 | 3 | /** 4 | * 类名:Voice.java 5 | * 描述: 6 | * 作者:xsf 7 | * 创建时间:2017/3/16 8 | * 最后修改时间:2017/3/16 9 | */ 10 | 11 | public class Voice { 12 | private String filePath; 13 | private long length; 14 | private String strLength; 15 | 16 | public Voice(long length,String strLength,String filePath){ 17 | this.length=length; 18 | this.strLength=strLength; 19 | this.filePath=filePath; 20 | } 21 | 22 | public String getFilePath() { 23 | return filePath; 24 | } 25 | 26 | public void setFilePath(String filePath) { 27 | this.filePath = filePath; 28 | } 29 | 30 | public long getLength() { 31 | return length; 32 | } 33 | 34 | public void setLength(long length) { 35 | this.length = length; 36 | } 37 | 38 | public String getStrLength() { 39 | return strLength; 40 | } 41 | 42 | public void setStrLength(String strLength) { 43 | this.strLength = strLength; 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /app/src/main/java/com/jaydenxiao/voicemanager/VoiceAdapter.java: -------------------------------------------------------------------------------- 1 | package com.jaydenxiao.voicemanager; 2 | 3 | import android.content.Context; 4 | import android.graphics.drawable.AnimationDrawable; 5 | import android.view.View; 6 | import android.view.ViewGroup; 7 | import android.widget.ImageView; 8 | import android.widget.LinearLayout; 9 | import android.widget.TextView; 10 | 11 | /** 12 | * 类名:VoiceAdapter.java 13 | * 描述: 14 | * 作者:xsf 15 | * 创建时间:2017/3/16 16 | * 最后修改时间:2017/3/16 17 | */ 18 | 19 | public class VoiceAdapter extends BaseCommonAdapter { 20 | // 语音动画 21 | private AnimationDrawable voiceAnimation; 22 | private VoiceManager voiceManager; 23 | private int lastPosition = -1; 24 | 25 | public VoiceAdapter(Context context) { 26 | super(context); 27 | voiceManager = VoiceManager.getInstance(context); 28 | } 29 | 30 | @Override 31 | public View getView(final int position, View convertView, ViewGroup parent) { 32 | if (convertView == null) { 33 | convertView = mInflater.inflate(R.layout.item_voice_list, parent, false); 34 | } 35 | final ImageView ivVoic = ViewHolderUtil.get(convertView, R.id.iv_voice); 36 | final LinearLayout llRoot = ViewHolderUtil.get(convertView, R.id.ll_root); 37 | TextView tvLength = ViewHolderUtil.get(convertView, R.id.tv_length); 38 | final Voice voice = getData().get(position); 39 | tvLength.setText(voice.getStrLength()); 40 | //播放 41 | llRoot.setOnClickListener(new View.OnClickListener() { 42 | @Override 43 | public void onClick(View view) { 44 | if (voiceAnimation != null) { 45 | voiceAnimation.stop(); 46 | voiceAnimation.selectDrawable(0); 47 | } 48 | if (voiceManager.isPlaying()&&lastPosition == position) { 49 | voiceManager.stopPlay(); 50 | }else{ 51 | voiceManager.stopPlay(); 52 | voiceAnimation = (AnimationDrawable) ivVoic.getBackground(); 53 | voiceAnimation.start(); 54 | voiceManager.setVoicePlayListener(new VoiceManager.VoicePlayCallBack() { 55 | @Override 56 | public void voiceTotalLength(long time, String strTime) { 57 | 58 | } 59 | 60 | @Override 61 | public void playDoing(long time, String strTime) { 62 | 63 | } 64 | 65 | @Override 66 | public void playPause() { 67 | 68 | } 69 | 70 | @Override 71 | public void playStart() { 72 | 73 | } 74 | 75 | @Override 76 | public void playFinish() { 77 | if (voiceAnimation != null) { 78 | voiceAnimation.stop(); 79 | voiceAnimation.selectDrawable(0); 80 | } 81 | } 82 | }); 83 | voiceManager.startPlay(voice.getFilePath()); 84 | } 85 | lastPosition = position; 86 | 87 | } 88 | }); 89 | 90 | return convertView; 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /app/src/main/java/com/jaydenxiao/voicemanager/VoiceLineView.java: -------------------------------------------------------------------------------- 1 | package com.jaydenxiao.voicemanager; 2 | 3 | import android.content.Context; 4 | import android.content.res.TypedArray; 5 | import android.graphics.Canvas; 6 | import android.graphics.Color; 7 | import android.graphics.Paint; 8 | import android.graphics.Path; 9 | import android.graphics.Rect; 10 | import android.util.AttributeSet; 11 | import android.view.View; 12 | 13 | import java.util.ArrayList; 14 | import java.util.LinkedList; 15 | import java.util.List; 16 | 17 | /** 18 | * Created by carlos on 2016/1/29. 19 | * 自定义声音振动曲线view 20 | */ 21 | public class VoiceLineView extends View { 22 | private final int LINE = 0; 23 | private final int RECT = 1; 24 | 25 | private int middleLineColor = Color.BLACK; 26 | private int voiceLineColor = Color.BLACK; 27 | private float middleLineHeight = 4; 28 | private Paint paint; 29 | private Paint paintVoicLine; 30 | private int mode; 31 | /** 32 | * 灵敏度 33 | */ 34 | private int sensibility = 4; 35 | 36 | private float maxVolume = 100; 37 | 38 | 39 | private float translateX = 0; 40 | private boolean isSet = false; 41 | 42 | /** 43 | * 振幅 44 | */ 45 | private float amplitude = 1; 46 | /** 47 | * 音量 48 | */ 49 | private float volume = 10; 50 | private int fineness = 1; 51 | private float targetVolume = 1; 52 | 53 | 54 | private long speedY = 50; 55 | private float rectWidth = 25; 56 | private float rectSpace = 5; 57 | private float rectInitHeight = 4; 58 | private List rectList; 59 | 60 | private long lastTime = 0; 61 | private int lineSpeed = 90; 62 | 63 | List paths = null; 64 | /** 65 | * 继续或停止震动,默认继续 66 | */ 67 | private boolean continueOrPause=true; 68 | 69 | public VoiceLineView(Context context) { 70 | super(context); 71 | } 72 | 73 | public VoiceLineView(Context context, AttributeSet attrs) { 74 | super(context, attrs); 75 | initAtts(context, attrs); 76 | } 77 | 78 | public VoiceLineView(Context context, AttributeSet attrs, int defStyleAttr) { 79 | super(context, attrs, defStyleAttr); 80 | initAtts(context, attrs); 81 | } 82 | 83 | private void initAtts(Context context, AttributeSet attrs) { 84 | TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.voiceView); 85 | mode = typedArray.getInt(R.styleable.voiceView_viewMode, 0); 86 | voiceLineColor = typedArray.getColor(R.styleable.voiceView_voiceLine, Color.BLACK); 87 | maxVolume = typedArray.getFloat(R.styleable.voiceView_maxVolume, 100); 88 | sensibility = typedArray.getInt(R.styleable.voiceView_sensibility, 4); 89 | if (mode == RECT) { 90 | rectWidth = typedArray.getDimension(R.styleable.voiceView_rectWidth, 25); 91 | rectSpace = typedArray.getDimension(R.styleable.voiceView_rectSpace, 5); 92 | rectInitHeight = typedArray.getDimension(R.styleable.voiceView_rectInitHeight, 4); 93 | } else { 94 | middleLineColor = typedArray.getColor(R.styleable.voiceView_middleLine, Color.BLACK); 95 | middleLineHeight = typedArray.getDimension(R.styleable.voiceView_middleLineHeight, 4); 96 | lineSpeed = typedArray.getInt(R.styleable.voiceView_lineSpeed, 90); 97 | fineness = typedArray.getInt(R.styleable.voiceView_fineness, 1); 98 | paths = new ArrayList<>(20); 99 | for (int i = 0; i < 20; i++) { 100 | paths.add(new Path()); 101 | } 102 | } 103 | typedArray.recycle(); 104 | } 105 | 106 | @Override 107 | protected void onDraw(Canvas canvas) { 108 | if (mode == RECT) { 109 | drawVoiceRect(canvas); 110 | run(); 111 | } else { 112 | drawMiddleLine(canvas); 113 | if(continueOrPause) { 114 | drawVoiceLine(canvas); 115 | run(); 116 | } 117 | } 118 | } 119 | 120 | private void drawMiddleLine(Canvas canvas) { 121 | if (paint == null) { 122 | paint = new Paint(); 123 | paint.setColor(middleLineColor); 124 | paint.setAntiAlias(true); 125 | } 126 | canvas.save(); 127 | canvas.drawRect(0, getHeight() / 2 - middleLineHeight / 2, getWidth(), getHeight() / 2 + middleLineHeight / 2, paint); 128 | canvas.restore(); 129 | } 130 | 131 | private void drawVoiceLine(Canvas canvas) { 132 | lineChange(); 133 | if (paintVoicLine == null) { 134 | paintVoicLine = new Paint(); 135 | paintVoicLine.setColor(voiceLineColor); 136 | paintVoicLine.setAntiAlias(true); 137 | paintVoicLine.setStyle(Paint.Style.STROKE); 138 | paintVoicLine.setStrokeWidth(2); 139 | } 140 | canvas.save(); 141 | int moveY = getHeight() / 2; 142 | for (int i = 0; i < paths.size(); i++) { 143 | paths.get(i).reset(); 144 | paths.get(i).moveTo(getWidth(), getHeight() / 2); 145 | } 146 | for (float i = getWidth() - 1; i >= 0; i -= fineness) { 147 | amplitude = 4 * volume * i / getWidth() - 4 * volume * i * i / getWidth() / getWidth(); 148 | for (int n = 1; n <= paths.size(); n++) { 149 | float sin = amplitude * (float) Math.sin((i - Math.pow(1.22, n)) * Math.PI / 180 - translateX); 150 | paths.get(n - 1).lineTo(i, (2 * n * sin / paths.size() - 15 * sin / paths.size() + moveY)); 151 | } 152 | } 153 | for (int n = 0; n < paths.size(); n++) { 154 | if (n == paths.size() - 1) { 155 | paintVoicLine.setAlpha(255); 156 | } else { 157 | paintVoicLine.setAlpha(n * 130 / paths.size()); 158 | } 159 | if (paintVoicLine.getAlpha() > 0) { 160 | canvas.drawPath(paths.get(n), paintVoicLine); 161 | } 162 | } 163 | canvas.restore(); 164 | } 165 | 166 | private void drawVoiceRect(Canvas canvas) { 167 | if (paintVoicLine == null) { 168 | paintVoicLine = new Paint(); 169 | paintVoicLine.setColor(voiceLineColor); 170 | paintVoicLine.setAntiAlias(true); 171 | paintVoicLine.setStyle(Paint.Style.STROKE); 172 | paintVoicLine.setStrokeWidth(2); 173 | } 174 | if (rectList == null) { 175 | rectList = new LinkedList<>(); 176 | } 177 | int totalWidth = (int) (rectSpace + rectWidth); 178 | if (speedY % totalWidth < 6) { 179 | Rect rect = new Rect((int) (-rectWidth - 10 - speedY + speedY % totalWidth), 180 | (int) (getHeight() / 2 - rectInitHeight / 2 - (volume == 10 ? 0 : volume / 2)), 181 | (int) (-10 - speedY + speedY % totalWidth), 182 | (int) (getHeight() / 2 + rectInitHeight / 2 + (volume == 10 ? 0 : volume / 2))); 183 | if (rectList.size() > getWidth() / (rectSpace + rectWidth) + 2) { 184 | rectList.remove(0); 185 | } 186 | rectList.add(rect); 187 | } 188 | canvas.translate(speedY, 0); 189 | for (int i = rectList.size() - 1; i >= 0; i--) { 190 | canvas.drawRect(rectList.get(i), paintVoicLine); 191 | } 192 | rectChange(); 193 | } 194 | 195 | public void setVolume(int volume) { 196 | if (volume > maxVolume * sensibility / 25) { 197 | isSet = true; 198 | this.targetVolume = getHeight() * volume / 2 / maxVolume; 199 | } 200 | } 201 | 202 | /** 203 | * 停止 204 | */ 205 | public void setPause(){ 206 | continueOrPause=false; 207 | } 208 | /** 209 | * 继续 210 | */ 211 | public void setContinue(){ 212 | continueOrPause=true; 213 | run(); 214 | } 215 | 216 | private void lineChange() { 217 | if (lastTime == 0) { 218 | lastTime = System.currentTimeMillis(); 219 | translateX += 1.5; 220 | } else { 221 | if (System.currentTimeMillis() - lastTime > lineSpeed) { 222 | lastTime = System.currentTimeMillis(); 223 | translateX += 1.5; 224 | } else { 225 | return; 226 | } 227 | } 228 | if (volume < targetVolume && isSet) { 229 | volume += getHeight() / 30; 230 | } else { 231 | isSet = false; 232 | if (volume <= 10) { 233 | volume = 10; 234 | } else { 235 | if (volume < getHeight() / 30) { 236 | volume -= getHeight() / 60; 237 | } else { 238 | volume -= getHeight() / 30; 239 | } 240 | } 241 | } 242 | } 243 | 244 | private void rectChange() { 245 | speedY += 6; 246 | if (volume < targetVolume && isSet) { 247 | volume += getHeight() / 30; 248 | } else { 249 | isSet = false; 250 | if (volume <= 10) { 251 | volume = 10; 252 | } else { 253 | if (volume < getHeight() / 30) { 254 | volume -= getHeight() / 60; 255 | } else { 256 | volume -= getHeight() / 30; 257 | } 258 | } 259 | } 260 | } 261 | 262 | public void run() { 263 | if (mode == RECT) { 264 | postInvalidateDelayed(30); 265 | } else { 266 | invalidate(); 267 | } 268 | } 269 | 270 | } -------------------------------------------------------------------------------- /app/src/main/java/com/jaydenxiao/voicemanager/VoiceManager.java: -------------------------------------------------------------------------------- 1 | package com.jaydenxiao.voicemanager; 2 | 3 | import android.annotation.SuppressLint; 4 | import android.app.Activity; 5 | import android.content.Context; 6 | import android.media.MediaPlayer; 7 | import android.media.MediaRecorder; 8 | import android.os.Environment; 9 | import android.os.Handler; 10 | import android.os.Message; 11 | import android.text.TextUtils; 12 | import android.util.Log; 13 | import android.widget.SeekBar; 14 | import android.widget.Toast; 15 | 16 | import java.io.File; 17 | import java.io.FileInputStream; 18 | import java.io.FileOutputStream; 19 | import java.io.IOException; 20 | import java.util.ArrayList; 21 | 22 | /** 23 | * 录音管理类(包括录音和播放) 24 | * 作者:xsf 25 | * 创建时间:2017/3/16 26 | * 最后修改时间:2017/3/16 27 | */ 28 | /** 29 | * 录音管理(包括播放和监听) 30 | */ 31 | public class VoiceManager { 32 | 33 | private static volatile VoiceManager voiceManager; 34 | public final int MSG_TIME_INTERVAL = 100; 35 | // 多媒体例如声音的状态 36 | public final int MEDIA_STATE_UNDEFINE = 200; 37 | public final int MEDIA_STATE_RECORD_STOP = 210; 38 | public final int MEDIA_STATE_RECORD_DOING = 220; 39 | public final int MEDIA_STATE_RECORD_PAUSE = 230; 40 | public final int MEDIA_STATE_PLAY_STOP = 310; 41 | public final int MEDIA_STATE_PLAY_DOING = 320; 42 | public final int MEDIA_STATE_PLAY_PAUSE = 330; 43 | 44 | private ArrayList mRecList = new ArrayList(); 45 | private Context mContext = null; 46 | private SeekBar mSBPlayProgress; 47 | private int mSavedState, mDeviceState = MEDIA_STATE_UNDEFINE; 48 | private MediaRecorder mMediaRecorder = null; 49 | private MediaPlayer mMediaPlayer = null; 50 | private String mRecTimePrev; 51 | private long mRecTimeSum = 0; 52 | /** 53 | * 录音文件存放的位置(文件夹) 54 | */ 55 | private String recordFilePath = ""; 56 | /** 57 | * 播放音频文件位置 58 | */ 59 | private String playFilePath; 60 | /** 61 | * 录音监听 62 | */ 63 | private VoiceRecordCallBack voiceRecordCallBack; 64 | private ObtainDecibelThread mThread; 65 | /** 66 | * 播放监听 67 | */ 68 | private VoicePlayCallBack voicePlayCallBack; 69 | 70 | private VoiceManager(Context context) { 71 | this.mContext = context; 72 | } 73 | /** 74 | * 获取单例 75 | * @param context 76 | * @return 77 | */ 78 | public static VoiceManager getInstance(Context context){ 79 | if(voiceManager == null){ 80 | synchronized(VoiceManager.class){ 81 | if(voiceManager == null){ 82 | voiceManager = new VoiceManager(context); 83 | } 84 | } 85 | } 86 | return voiceManager; 87 | } 88 | /** 89 | * 播放器结束监听 90 | */ 91 | private MediaPlayer.OnCompletionListener mPlayCompetedListener = new MediaPlayer.OnCompletionListener() { 92 | @Override 93 | public void onCompletion(MediaPlayer mp) { 94 | mDeviceState = MEDIA_STATE_PLAY_STOP; 95 | mHandler.removeMessages(MSG_TIME_INTERVAL); 96 | mMediaPlayer.stop(); 97 | mMediaPlayer.release(); 98 | if (mSBPlayProgress != null) { 99 | mSBPlayProgress.setProgress(0); 100 | } 101 | if (voicePlayCallBack != null) { 102 | voicePlayCallBack.playFinish(); 103 | } 104 | } 105 | }; 106 | /** 107 | * 播放或录音handler 108 | */ 109 | @SuppressLint("HandlerLeak") 110 | private final Handler mHandler = new Handler() { 111 | @Override 112 | public void handleMessage(Message msg) { 113 | VoiceTimeUtils ts; 114 | int current; 115 | try { 116 | switch (msg.what) { 117 | //录音 118 | case MSG_TIME_INTERVAL: 119 | if (mDeviceState == MEDIA_STATE_RECORD_DOING) { 120 | ts = VoiceTimeUtils.timeSpanToNow(mRecTimePrev); 121 | mRecTimeSum += ts.mDiffSecond; 122 | mRecTimePrev = VoiceTimeUtils.getTimeStrFromMillis(ts.mNowTime); 123 | ts = VoiceTimeUtils.timeSpanSecond(mRecTimeSum); 124 | //回调录音时间 125 | if (voiceRecordCallBack != null) { 126 | voiceRecordCallBack.recDoing(mRecTimeSum, String.format("%02d:%02d:%02d", 127 | ts.mSpanHour, ts.mSpanMinute, ts.mSpanSecond)); 128 | } 129 | mHandler.sendEmptyMessageDelayed(MSG_TIME_INTERVAL, 1000); 130 | } 131 | //播放 132 | else if (mDeviceState == MEDIA_STATE_PLAY_DOING) { 133 | current = mMediaPlayer.getCurrentPosition(); 134 | if (mSBPlayProgress != null) { 135 | mSBPlayProgress.setProgress(current); 136 | } 137 | ts = VoiceTimeUtils.timeSpanSecond(current / 1000); 138 | //回调播放进度 139 | if (voicePlayCallBack != null) { 140 | voicePlayCallBack.playDoing(current / 1000, String.format("%02d:%02d:%02d", 141 | ts.mSpanHour, ts.mSpanMinute, ts.mSpanSecond)); 142 | } 143 | mHandler.sendEmptyMessageDelayed(MSG_TIME_INTERVAL, 1000); 144 | } 145 | break; 146 | default: 147 | break; 148 | } 149 | } catch (Exception e) { 150 | } 151 | } 152 | }; 153 | /*********************************录音操作begin***************************/ 154 | /** 155 | * 录音监听 156 | * 157 | * @param callBack 158 | */ 159 | public void setVoiceRecordListener(VoiceRecordCallBack callBack) { 160 | voiceRecordCallBack = callBack; 161 | } 162 | 163 | /** 164 | * 开始录音(外部调) 165 | * 166 | * @param filePath 音频存放文件夹 167 | */ 168 | public void startVoiceRecord(String filePath) { 169 | if (!isSDCardAvailable()) return ; 170 | this.recordFilePath = filePath; 171 | startVoiceRecord(true); 172 | } 173 | 174 | /** 175 | * 继续或暂停录音 176 | */ 177 | public void pauseOrStartVoiceRecord() { 178 | if (mDeviceState == MEDIA_STATE_RECORD_DOING) { 179 | mDeviceState = MEDIA_STATE_RECORD_PAUSE; 180 | stopRecorder(mMediaRecorder, true); 181 | mMediaRecorder = null; 182 | voiceRecordCallBack.recPause("已暂停"); 183 | } else { 184 | startVoiceRecord(false); 185 | } 186 | } 187 | 188 | /** 189 | * 完成录音 190 | */ 191 | public void stopVoiceRecord() { 192 | try { 193 | mHandler.removeMessages(MSG_TIME_INTERVAL); 194 | mDeviceState = MEDIA_STATE_RECORD_STOP; 195 | stopRecorder(mMediaRecorder, true); 196 | mMediaRecorder = null; 197 | if (VoiceTimeUtils.timeSpanSecond(mRecTimeSum).mSpanSecond == 0) { 198 | Toast.makeText(mContext, "时间过短", Toast.LENGTH_SHORT).show(); 199 | } else { 200 | File file = getOutputVoiceFile(mRecList); 201 | if (file != null && file.length() > 0) { 202 | cleanFieArrayList(mRecList); 203 | //TODO 这里可以返回数据 setResult 204 | final VoiceTimeUtils ts = VoiceTimeUtils.timeSpanSecond(mRecTimeSum); 205 | //完成录音 206 | if (voiceRecordCallBack != null) { 207 | voiceRecordCallBack.recFinish(mRecTimeSum, String.format("%02d:%02d:%02d", 208 | ts.mSpanHour, ts.mSpanMinute, ts.mSpanSecond),file.getAbsolutePath()); 209 | } 210 | } 211 | } 212 | 213 | } catch (Exception e) { 214 | e.printStackTrace(); 215 | } 216 | } 217 | 218 | /** 219 | * 开始录音(内部调) 220 | * 221 | * @param init 是否是初始化录音还是暂停后再录音 222 | */ 223 | private void startVoiceRecord(boolean init) { 224 | if (!isSDCardAvailable()) return; 225 | if (init) { 226 | mRecTimeSum = 0; 227 | cleanFieArrayList(mRecList); 228 | } 229 | //录音前停止播放回调 230 | if(voicePlayCallBack!=null){ 231 | voicePlayCallBack.playFinish(); 232 | } 233 | stopRecorder(mMediaRecorder, true); 234 | mMediaRecorder = null; 235 | 236 | stopMedia(mMediaPlayer, true); 237 | mMediaPlayer = null; 238 | mMediaRecorder = new MediaRecorder(); 239 | File file = prepareRecorder(mMediaRecorder, true); 240 | if (file != null) { 241 | //开始录音回调 242 | if (voiceRecordCallBack != null) { 243 | voiceRecordCallBack.recStart(init); 244 | } 245 | mDeviceState = MEDIA_STATE_RECORD_DOING; 246 | mRecTimePrev = VoiceTimeUtils.getTimeStrFromMillis(System.currentTimeMillis()); 247 | mRecList.add(file); 248 | 249 | mHandler.removeMessages(MSG_TIME_INTERVAL); 250 | mHandler.sendEmptyMessage(MSG_TIME_INTERVAL); 251 | } 252 | } 253 | 254 | /** 255 | * 监听录音声音频率大小 256 | */ 257 | private class ObtainDecibelThread extends Thread { 258 | 259 | private volatile boolean running = true; 260 | 261 | public void exit() { 262 | running = false; 263 | } 264 | @Override 265 | public void run() { 266 | while (running) { 267 | try { 268 | Thread.sleep(100); 269 | } catch (InterruptedException e) { 270 | e.printStackTrace(); 271 | } 272 | if (mMediaRecorder == null || !running) { 273 | break; 274 | } 275 | try { 276 | final double ratio = mMediaRecorder.getMaxAmplitude()/150; 277 | if (ratio != 0&&voiceRecordCallBack!=null) { 278 | ((Activity)mContext).runOnUiThread(new Runnable() { 279 | @Override 280 | public void run() { 281 | double db=0;// 分贝 282 | if (ratio > 1) 283 | db = (int) (20 * Math.log10(ratio)); 284 | voiceRecordCallBack.recVoiceGrade((int)db); 285 | } 286 | }); 287 | } 288 | } catch (RuntimeException e) { 289 | e.printStackTrace(); 290 | } 291 | 292 | } 293 | } 294 | 295 | } 296 | 297 | /** 298 | * 合并录音 299 | * 300 | * @param list 301 | * @return 302 | */ 303 | private File getOutputVoiceFile(ArrayList list) { 304 | String mMinute1 = VoiceTimeUtils.getTime(); 305 | File recDirFile = recAudioDir(recordFilePath); 306 | 307 | // 创建音频文件,合并的文件放这里 308 | File resFile = new File(recDirFile, mMinute1 + ".amr"); 309 | FileOutputStream fileOutputStream = null; 310 | try { 311 | fileOutputStream = new FileOutputStream(resFile); 312 | } catch (IOException e) { 313 | } 314 | // list里面为暂停录音 所产生的 几段录音文件的名字,中间几段文件的减去前面的6个字节头文件 315 | for (int i = 0; i < list.size(); i++) { 316 | File file = list.get(i); 317 | try { 318 | FileInputStream fileInputStream = new FileInputStream(file); 319 | byte[] myByte = new byte[fileInputStream.available()]; 320 | // 文件长度 321 | int length = myByte.length; 322 | // 头文件 323 | if (i == 0) { 324 | while (fileInputStream.read(myByte) != -1) { 325 | fileOutputStream.write(myByte, 0, length); 326 | } 327 | } 328 | // 之后的文件,去掉头文件就可以了 329 | else { 330 | while (fileInputStream.read(myByte) != -1) { 331 | fileOutputStream.write(myByte, 6, length - 6); 332 | } 333 | } 334 | fileOutputStream.flush(); 335 | fileInputStream.close(); 336 | } catch (Exception e) { 337 | e.printStackTrace(); 338 | } 339 | } 340 | // 结束后关闭流 341 | try { 342 | fileOutputStream.close(); 343 | } catch (IOException e) { 344 | e.printStackTrace(); 345 | } 346 | 347 | return resFile; 348 | } 349 | 350 | /** 351 | * 清空暂停录音所产生的几段录音文件 352 | * 353 | * @param list 354 | */ 355 | private void cleanFieArrayList(ArrayList list) { 356 | for (File file : list) { 357 | file.delete(); 358 | } 359 | list.clear(); 360 | } 361 | 362 | /*********************************录音操作end***************************/ 363 | 364 | 365 | /*********************************播放操作end***************************/ 366 | /** 367 | * 播放监听 368 | * 369 | * @param callBack 370 | */ 371 | public void setVoicePlayListener(VoicePlayCallBack callBack) { 372 | voicePlayCallBack = callBack; 373 | } 374 | 375 | /** 376 | * 播放SeekBar监听 377 | * 378 | * @param seekBar 379 | */ 380 | public void setSeekBarListener(SeekBar seekBar) { 381 | mSBPlayProgress = seekBar; 382 | if (mSBPlayProgress != null) { 383 | mSBPlayProgress.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() { 384 | @Override 385 | public void onStartTrackingTouch(SeekBar seekBar) { 386 | mHandler.removeMessages(MSG_TIME_INTERVAL); 387 | mSavedState = mDeviceState; 388 | if (mSavedState == MEDIA_STATE_PLAY_DOING) { 389 | pauseMedia(mMediaPlayer); 390 | } 391 | } 392 | 393 | @Override 394 | public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) { 395 | mHandler.removeMessages(MSG_TIME_INTERVAL); 396 | VoiceTimeUtils ts = VoiceTimeUtils.timeSpanSecond(progress / 1000); 397 | //播放进度 398 | if (voicePlayCallBack != null) { 399 | voicePlayCallBack.playDoing(progress / 1000, String.format("%02d:%02d:%02d", 400 | ts.mSpanHour, ts.mSpanMinute, ts.mSpanSecond)); 401 | } 402 | } 403 | 404 | @Override 405 | public void onStopTrackingTouch(SeekBar seekBar) { 406 | seektoMedia(mMediaPlayer, mSBPlayProgress.getProgress()); 407 | 408 | if (mSavedState == MEDIA_STATE_PLAY_DOING) { 409 | playMedia(mMediaPlayer); 410 | mHandler.sendEmptyMessage(MSG_TIME_INTERVAL); 411 | } 412 | } 413 | }); 414 | } 415 | } 416 | 417 | /** 418 | * 开始播放(外部调) 419 | * 420 | * @param filePath 音频存放文件夹 421 | */ 422 | public void startPlay(String filePath) { 423 | if (TextUtils.isEmpty(filePath)|| !new File(filePath).exists()) 424 | { 425 | if (voicePlayCallBack != null) { 426 | voicePlayCallBack.playFinish(); 427 | } 428 | Toast.makeText(mContext,"文件不存在",Toast.LENGTH_SHORT).show(); 429 | return; 430 | }else { 431 | playFilePath = filePath; 432 | startPlay(true); 433 | } 434 | } 435 | /** 436 | * 开始播放(内部调) 437 | * 438 | * @param init 439 | */ 440 | private void startPlay(boolean init) { 441 | try { 442 | stopRecorder(mMediaRecorder, true); 443 | mMediaRecorder = null; 444 | 445 | stopMedia(mMediaPlayer, true); 446 | mMediaPlayer = null; 447 | 448 | mMediaPlayer = new MediaPlayer(); 449 | mMediaPlayer.setOnCompletionListener(mPlayCompetedListener); 450 | 451 | if (prepareMedia(mMediaPlayer, playFilePath)) { 452 | mDeviceState = MEDIA_STATE_PLAY_DOING; 453 | //总时间长度 454 | long totalTime = mMediaPlayer.getDuration() / 1000; 455 | VoiceTimeUtils ts = VoiceTimeUtils.timeSpanSecond(totalTime); 456 | String voiceLength = String.format("%02d:%02d:%02d", 457 | ts.mSpanHour, ts.mSpanMinute, ts.mSpanSecond); 458 | //播放进度回调 459 | if (voicePlayCallBack != null) { 460 | voicePlayCallBack.voiceTotalLength(totalTime, voiceLength); 461 | voicePlayCallBack.playDoing(0, "00:00:00"); 462 | } 463 | if (mSBPlayProgress != null) { 464 | mSBPlayProgress.setMax(Math.max(1, mMediaPlayer.getDuration())); 465 | } 466 | if (init) { 467 | if (mSBPlayProgress != null) { 468 | mSBPlayProgress.setProgress(0); 469 | } 470 | seektoMedia(mMediaPlayer, 0); 471 | } else { 472 | seektoMedia(mMediaPlayer, mSBPlayProgress.getProgress()); 473 | } 474 | if (playMedia(mMediaPlayer)) { 475 | mHandler.removeMessages(MSG_TIME_INTERVAL); 476 | mHandler.sendEmptyMessage(MSG_TIME_INTERVAL); 477 | } 478 | } 479 | } catch (Exception e) { 480 | Log.e("播放出错了", e.getMessage()); 481 | } 482 | } 483 | 484 | /** 485 | * 继续或暂停播放 486 | */ 487 | public void continueOrPausePlay() { 488 | if (mDeviceState == MEDIA_STATE_PLAY_DOING) { 489 | mDeviceState = MEDIA_STATE_PLAY_PAUSE; 490 | pauseMedia(mMediaPlayer); 491 | //暂停 492 | if (voicePlayCallBack != null) { 493 | voicePlayCallBack.playPause(); 494 | } 495 | } else if (mDeviceState == MEDIA_STATE_PLAY_PAUSE) { 496 | mDeviceState = MEDIA_STATE_PLAY_DOING; 497 | playMedia(mMediaPlayer); 498 | //播放中 499 | mHandler.removeMessages(MSG_TIME_INTERVAL); 500 | mHandler.sendEmptyMessage(MSG_TIME_INTERVAL); 501 | } else if (mDeviceState == MEDIA_STATE_PLAY_STOP) { 502 | //播放 503 | if (!TextUtils.isEmpty(playFilePath)) { 504 | startPlay(false); 505 | } 506 | } 507 | } 508 | 509 | /** 510 | * 停止播放 511 | */ 512 | public void stopPlay() { 513 | mHandler.removeMessages(MSG_TIME_INTERVAL); 514 | mDeviceState = MEDIA_STATE_PLAY_STOP; 515 | stopMedia(mMediaPlayer, true); 516 | mMediaPlayer = null; 517 | } 518 | 519 | /** 520 | * 是否在播放中 521 | * @return 522 | */ 523 | public boolean isPlaying(){ 524 | return mDeviceState == MEDIA_STATE_PLAY_DOING; 525 | } 526 | 527 | /*********************************播放操作end***************************/ 528 | 529 | 530 | /** 531 | * 播放录音准备工作 532 | * 533 | * @param mp 534 | * @param file 535 | * @return 536 | */ 537 | private boolean prepareMedia(MediaPlayer mp, String file) { 538 | boolean result = false; 539 | try { 540 | mp.setDataSource(file); 541 | mp.prepare(); 542 | result = true; 543 | } catch (Exception e) { 544 | } 545 | 546 | return result; 547 | } 548 | 549 | /** 550 | * 播放录音开始 551 | * 552 | * @param mp 553 | * @return 554 | */ 555 | private boolean playMedia(MediaPlayer mp) { 556 | boolean result = false; 557 | try { 558 | if (mp != null) { 559 | mp.start(); 560 | result = true; 561 | } 562 | } catch (Exception e) { 563 | } 564 | 565 | return result; 566 | } 567 | 568 | /** 569 | * 拖动播放进度条 570 | * 571 | * @param mp 572 | * @param pos 573 | * @return 574 | */ 575 | private boolean seektoMedia(MediaPlayer mp, int pos) { 576 | boolean result = false; 577 | try { 578 | if (mp != null && pos >= 0) { 579 | mp.seekTo(pos); 580 | result = true; 581 | } 582 | } catch (Exception e) { 583 | } 584 | return result; 585 | } 586 | 587 | /** 588 | * 停止播放 589 | * 590 | * @param mp 591 | * @param release 592 | * @return 593 | */ 594 | private boolean stopMedia(MediaPlayer mp, boolean release) { 595 | boolean result = false; 596 | try { 597 | if (mp != null) { 598 | mp.stop(); 599 | 600 | if (release) { 601 | mp.release(); 602 | 603 | 604 | } 605 | result = true; 606 | } 607 | } catch (Exception e) { 608 | } 609 | 610 | return result; 611 | } 612 | 613 | /** 614 | * 暂停播放 615 | * 616 | * @param mp 617 | * @return 618 | */ 619 | private boolean pauseMedia(MediaPlayer mp) { 620 | boolean result = false; 621 | 622 | try { 623 | if (mp != null) { 624 | mp.pause(); 625 | result = true; 626 | } 627 | } catch (Exception e) { 628 | } 629 | 630 | return result; 631 | } 632 | 633 | /** 634 | * 最后停止录音 635 | * 636 | * @param mr 637 | * @param release 638 | * @return 639 | */ 640 | private boolean stopRecorder(MediaRecorder mr, boolean release) { 641 | boolean result = false; 642 | try { 643 | if (mr != null) { 644 | mr.stop(); 645 | if (release) { 646 | mr.release(); 647 | } 648 | result = true; 649 | } 650 | if(mThread!=null){ 651 | mThread.exit(); 652 | mThread=null; 653 | } 654 | } catch (Exception e) { 655 | if(mThread!=null){ 656 | mThread.exit(); 657 | mThread=null; 658 | } 659 | } 660 | return result; 661 | } 662 | 663 | /** 664 | * 录音准备工作 ,开始录音 665 | * 666 | * @param mr 667 | * @param start 668 | * @return 669 | */ 670 | @SuppressWarnings("deprecation") 671 | private File prepareRecorder(MediaRecorder mr, boolean start) { 672 | File recFile = null; 673 | if (mr == null) return null; 674 | try { 675 | String path = recAudioDir(recordFilePath).getAbsolutePath(); 676 | recFile = new File(path, VoiceTimeUtils.getTime() + ".amr"); 677 | mr.setAudioSource(MediaRecorder.AudioSource.MIC); 678 | mr.setOutputFormat(MediaRecorder.OutputFormat.RAW_AMR); 679 | mr.setAudioEncoder(MediaRecorder.AudioEncoder.AMR_NB); 680 | mr.setOutputFile(recFile.getAbsolutePath()); 681 | mr.prepare(); 682 | if (start) { 683 | mr.start(); 684 | if (mThread == null) { 685 | mThread = new ObtainDecibelThread(); 686 | mThread.start(); 687 | } 688 | } 689 | } catch (Exception e) { 690 | e.printStackTrace(); 691 | } 692 | return recFile; 693 | } 694 | 695 | /** 696 | * 停止录音和播放 697 | */ 698 | public void stopRecordAndPlay(){ 699 | stopRecorder(mMediaRecorder, true); 700 | mMediaRecorder = null; 701 | stopMedia(mMediaPlayer, true); 702 | mMediaPlayer = null; 703 | } 704 | 705 | /** 706 | * 录音回调监听 707 | */ 708 | public interface VoiceRecordCallBack { 709 | //录音中 710 | void recDoing(long time, String strTime); 711 | //录音中的声音频率等级 712 | void recVoiceGrade(int grade); 713 | //录音开始 714 | void recStart(boolean init); 715 | 716 | //录音暂停 717 | void recPause(String str); 718 | 719 | //录音结束 720 | void recFinish(long length, String strLength, String path); 721 | } 722 | 723 | /** 724 | * 播放录音回调监听 725 | */ 726 | public interface VoicePlayCallBack { 727 | 728 | /** 729 | * 音频长度 730 | * 指定的某个时间段,以秒为单位 731 | */ 732 | void voiceTotalLength(long time, String strTime); 733 | 734 | /** 735 | * 播放中 736 | * 指定的某个时间段,以秒为单位 737 | */ 738 | void playDoing(long time, String strTime); 739 | 740 | //播放暂停 741 | void playPause(); 742 | 743 | //播放开始 744 | void playStart(); 745 | 746 | //播放结束 747 | void playFinish(); 748 | } 749 | 750 | /** 751 | * SD卡是否可用 752 | */ 753 | public static boolean isSDCardAvailable() { 754 | return Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState()); 755 | } 756 | 757 | public static File recAudioDir(String path) { 758 | File file = new File(path); 759 | if (!file.exists()) { 760 | file.mkdirs(); 761 | } 762 | return file; 763 | } 764 | } 765 | -------------------------------------------------------------------------------- /app/src/main/java/com/jaydenxiao/voicemanager/VoiceTimeUtils.java: -------------------------------------------------------------------------------- 1 | package com.jaydenxiao.voicemanager; 2 | 3 | import android.annotation.SuppressLint; 4 | import android.text.TextUtils; 5 | 6 | import java.text.ParseException; 7 | import java.text.SimpleDateFormat; 8 | import java.util.Date; 9 | import java.util.Locale; 10 | 11 | public class VoiceTimeUtils { 12 | /** 13 | * 指定的某个时间,以毫秒为单位 14 | */ 15 | public long mOldTime = 0; 16 | 17 | /** 18 | * 当前时间,以毫秒为单位 19 | */ 20 | public long mNowTime = 0; 21 | 22 | /** 23 | * 指定的某个时间和当前时间之间的时间间隔,以秒为单位 24 | */ 25 | public long mDiffSecond = 0; 26 | 27 | /** 28 | * 时间间隔的天数 29 | */ 30 | public long mSpanDay = 0; 31 | 32 | /** 33 | * 时间间隔的小时数 34 | */ 35 | public long mSpanHour = 0; 36 | 37 | /** 38 | * 时间间隔的分钟数 39 | */ 40 | public long mSpanMinute = 0; 41 | 42 | /** 43 | * 时间间隔的秒数 44 | */ 45 | public long mSpanSecond = 0; 46 | 47 | /** 48 | * 时间间隔的天数,以多少天的文字形式输出 49 | */ 50 | public String mSpanDayText = ""; 51 | 52 | /** 53 | * 时间间隔的小时数,以多少小时的文字形式输出 54 | */ 55 | public String mSpanHourText = ""; 56 | 57 | /** 58 | * 时间间隔的分钟数,以多少分钟的文字形式输出 59 | */ 60 | public String mSpanMinuteText = ""; 61 | 62 | /** 63 | * 构造函数 64 | */ 65 | private VoiceTimeUtils() { 66 | } 67 | 68 | /** 69 | * 某个时间段长度 70 | * 71 | * @param second 指定的某个时间段,以秒为单位 72 | */ 73 | public static VoiceTimeUtils timeSpanSecond(long second) { 74 | VoiceTimeUtils ts = new VoiceTimeUtils(); 75 | long remain; 76 | 77 | try { 78 | ts.mDiffSecond = second; // 秒 79 | 80 | // 过期天数 81 | ts.mSpanDay = ts.mDiffSecond / (24 * 3600); 82 | remain = ts.mDiffSecond % (24 * 3600); 83 | 84 | // 过期小时数 85 | if (remain > 0) { 86 | ts.mSpanHour = remain / 3600; 87 | remain = remain % 3600; 88 | } 89 | 90 | // 过期分钟数 91 | if (remain > 0) { 92 | ts.mSpanMinute = remain / 60; 93 | } 94 | 95 | // 过期秒数 96 | ts.mSpanSecond = remain % 60; 97 | 98 | if (ts.mSpanDay > 0) { 99 | ts.mSpanDayText = ts.mSpanDay + "天"; 100 | } 101 | 102 | if (ts.mSpanHour > 0) { 103 | ts.mSpanHourText = ts.mSpanHour + "小时"; 104 | } 105 | 106 | if (ts.mSpanMinute > 0) { 107 | ts.mSpanMinuteText = ts.mSpanMinute + "分钟"; 108 | } 109 | } catch (Exception e) { 110 | } 111 | 112 | return ts; 113 | } 114 | 115 | /** 116 | * 指定某个时间到当前时间的时间段 117 | * 118 | * @param time 指定的某个时间,格式为yyyy-MM-dd HH:mm:ss 119 | */ 120 | public static VoiceTimeUtils timeSpanToNow(String time) { 121 | VoiceTimeUtils ts = new VoiceTimeUtils(); 122 | SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.getDefault()); 123 | long remain; 124 | 125 | try { 126 | if (TextUtils.isEmpty(time)) 127 | return ts; 128 | 129 | ts.mOldTime = sdf.parse(time).getTime(); 130 | ts.mNowTime = System.currentTimeMillis(); 131 | ts.mDiffSecond = Math.abs((ts.mNowTime - ts.mOldTime) / 1000); // 秒 132 | 133 | // 过期天数 134 | ts.mSpanDay = ts.mDiffSecond / (24 * 3600); 135 | remain = ts.mDiffSecond % (24 * 3600); 136 | 137 | // 过期小时数 138 | if (remain > 0) { 139 | ts.mSpanHour = remain / 3600; 140 | remain = remain % 3600; 141 | } 142 | 143 | // 过期分钟数 144 | if (remain > 0) { 145 | ts.mSpanMinute = remain / 60; 146 | } 147 | 148 | // 过期秒数 149 | ts.mSpanSecond = remain % 60; 150 | 151 | if (ts.mSpanDay > 0) { 152 | ts.mSpanDayText = ts.mSpanDay + "天"; 153 | } 154 | 155 | if (ts.mSpanHour > 0) { 156 | ts.mSpanHourText = ts.mSpanHour + "小时"; 157 | } 158 | 159 | if (ts.mSpanMinute > 0) { 160 | ts.mSpanMinuteText = ts.mSpanMinute + "分钟"; 161 | } 162 | } catch (ParseException e1) { 163 | } catch (Exception e2) { 164 | } 165 | 166 | return ts; 167 | } 168 | 169 | /** 170 | * 将以毫秒为单位的时间转换成字符串格式的时间 171 | * 172 | * @param time 时间,以毫秒为单位 173 | */ 174 | public static String getTimeStrFromMillis(long time) { 175 | SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.getDefault()); 176 | String result = sdf.format(new Date(time)); 177 | return result; 178 | } 179 | 180 | /** 181 | * 当前时间格式 182 | */ 183 | @SuppressLint("SimpleDateFormat") 184 | public static String getTime() { 185 | SimpleDateFormat formatter = new SimpleDateFormat("yyyyMMdd-HHmmss"); 186 | Date curDate = new Date(System.currentTimeMillis()); 187 | String time = formatter.format(curDate); 188 | return time; 189 | } 190 | } 191 | -------------------------------------------------------------------------------- /app/src/main/res/drawable-hdpi/icon_complete.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jaydenxiao2016/VoiceManager/ed8d2838f5d2f3853f3fc1ced695ed13f9a51675/app/src/main/res/drawable-hdpi/icon_complete.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-hdpi/icon_continue.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jaydenxiao2016/VoiceManager/ed8d2838f5d2f3853f3fc1ced695ed13f9a51675/app/src/main/res/drawable-hdpi/icon_continue.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-hdpi/icon_pause.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jaydenxiao2016/VoiceManager/ed8d2838f5d2f3853f3fc1ced695ed13f9a51675/app/src/main/res/drawable-hdpi/icon_pause.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-hdpi/icon_record.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jaydenxiao2016/VoiceManager/ed8d2838f5d2f3853f3fc1ced695ed13f9a51675/app/src/main/res/drawable-hdpi/icon_record.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-hdpi/icon_voice_record.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jaydenxiao2016/VoiceManager/ed8d2838f5d2f3853f3fc1ced695ed13f9a51675/app/src/main/res/drawable-hdpi/icon_voice_record.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-hdpi/voicetip_play_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jaydenxiao2016/VoiceManager/ed8d2838f5d2f3853f3fc1ced695ed13f9a51675/app/src/main/res/drawable-hdpi/voicetip_play_1.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-hdpi/voicetip_play_2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jaydenxiao2016/VoiceManager/ed8d2838f5d2f3853f3fc1ced695ed13f9a51675/app/src/main/res/drawable-hdpi/voicetip_play_2.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-hdpi/voicetip_play_3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jaydenxiao2016/VoiceManager/ed8d2838f5d2f3853f3fc1ced695ed13f9a51675/app/src/main/res/drawable-hdpi/voicetip_play_3.png -------------------------------------------------------------------------------- /app/src/main/res/drawable/playbar_style.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 7 | 8 | 9 | 10 | 11 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/round_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/round_selector.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/voice_receive.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 7 | 10 | 13 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 6 | 7 | 13 | 14 | 25 | 26 | -------------------------------------------------------------------------------- /app/src/main/res/layout/dialog_record_voice.xml: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | 14 | 30 | 31 | 38 | 39 | 50 | 51 | 59 | 60 | 67 | 68 | 69 | -------------------------------------------------------------------------------- /app/src/main/res/layout/item_voice_list.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 15 | 16 | 22 | -------------------------------------------------------------------------------- /app/src/main/res/menu/menu_main.xml: -------------------------------------------------------------------------------- 1 | 4 | 6 | 7 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jaydenxiao2016/VoiceManager/ed8d2838f5d2f3853f3fc1ced695ed13f9a51675/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jaydenxiao2016/VoiceManager/ed8d2838f5d2f3853f3fc1ced695ed13f9a51675/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jaydenxiao2016/VoiceManager/ed8d2838f5d2f3853f3fc1ced695ed13f9a51675/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jaydenxiao2016/VoiceManager/ed8d2838f5d2f3853f3fc1ced695ed13f9a51675/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/values-w820dp/dimens.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 64dp 6 | 7 | -------------------------------------------------------------------------------- /app/src/main/res/values/attrs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /app/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #00000000 4 | #FFFFFF 5 | -------------------------------------------------------------------------------- /app/src/main/res/values/dimens.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 16dp 4 | 16dp 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | VoiceManager 3 | 4 | Hello world! 5 | Settings 6 | 7 | -------------------------------------------------------------------------------- /app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 7 | 15 | 16 | -------------------------------------------------------------------------------- /art/Screenshot_2017-03-21-17-28-01.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jaydenxiao2016/VoiceManager/ed8d2838f5d2f3853f3fc1ced695ed13f9a51675/art/Screenshot_2017-03-21-17-28-01.png -------------------------------------------------------------------------------- /art/Screenshot_2017-03-21-17-28-05.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jaydenxiao2016/VoiceManager/ed8d2838f5d2f3853f3fc1ced695ed13f9a51675/art/Screenshot_2017-03-21-17-28-05.png -------------------------------------------------------------------------------- /art/Screenshot_2017-03-21-17-28-26.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jaydenxiao2016/VoiceManager/ed8d2838f5d2f3853f3fc1ced695ed13f9a51675/art/Screenshot_2017-03-21-17-28-26.png -------------------------------------------------------------------------------- /art/art.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jaydenxiao2016/VoiceManager/ed8d2838f5d2f3853f3fc1ced695ed13f9a51675/art/art.mp4 -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | // Top-level build file where you can add configuration options common to all sub-projects/modules. 2 | 3 | buildscript { 4 | repositories { 5 | jcenter() 6 | } 7 | dependencies { 8 | classpath 'com.android.tools.build:gradle:2.2.3' 9 | 10 | // NOTE: Do not place your application dependencies here; they belong 11 | // in the individual module build.gradle files 12 | } 13 | } 14 | 15 | allprojects { 16 | repositories { 17 | jcenter() 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | # Project-wide Gradle settings. 2 | 3 | # IDE (e.g. Android Studio) users: 4 | # Gradle settings configured through the IDE *will override* 5 | # any settings specified in this file. 6 | 7 | # For more details on how to configure your build environment visit 8 | # http://www.gradle.org/docs/current/userguide/build_environment.html 9 | 10 | # Specifies the JVM arguments used for the daemon process. 11 | # The setting is particularly useful for tweaking memory settings. 12 | # Default value: -Xmx10248m -XX:MaxPermSize=256m 13 | # org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8 14 | 15 | # When configured, Gradle will run in incubating parallel mode. 16 | # This option should only be used with decoupled projects. More details, visit 17 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects 18 | # org.gradle.parallel=true -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jaydenxiao2016/VoiceManager/ed8d2838f5d2f3853f3fc1ced695ed13f9a51675/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Wed Apr 10 15:27:10 PDT 2013 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=https\://services.gradle.org/distributions/gradle-2.14.1-all.zip 7 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | ############################################################################## 4 | ## 5 | ## Gradle start up script for UN*X 6 | ## 7 | ############################################################################## 8 | 9 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 10 | DEFAULT_JVM_OPTS="" 11 | 12 | APP_NAME="Gradle" 13 | APP_BASE_NAME=`basename "$0"` 14 | 15 | # Use the maximum available, or set MAX_FD != -1 to use that value. 16 | MAX_FD="maximum" 17 | 18 | warn ( ) { 19 | echo "$*" 20 | } 21 | 22 | die ( ) { 23 | echo 24 | echo "$*" 25 | echo 26 | exit 1 27 | } 28 | 29 | # OS specific support (must be 'true' or 'false'). 30 | cygwin=false 31 | msys=false 32 | darwin=false 33 | case "`uname`" in 34 | CYGWIN* ) 35 | cygwin=true 36 | ;; 37 | Darwin* ) 38 | darwin=true 39 | ;; 40 | MINGW* ) 41 | msys=true 42 | ;; 43 | esac 44 | 45 | # For Cygwin, ensure paths are in UNIX format before anything is touched. 46 | if $cygwin ; then 47 | [ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --unix "$JAVA_HOME"` 48 | fi 49 | 50 | # Attempt to set APP_HOME 51 | # Resolve links: $0 may be a link 52 | PRG="$0" 53 | # Need this for relative symlinks. 54 | while [ -h "$PRG" ] ; do 55 | ls=`ls -ld "$PRG"` 56 | link=`expr "$ls" : '.*-> \(.*\)$'` 57 | if expr "$link" : '/.*' > /dev/null; then 58 | PRG="$link" 59 | else 60 | PRG=`dirname "$PRG"`"/$link" 61 | fi 62 | done 63 | SAVED="`pwd`" 64 | cd "`dirname \"$PRG\"`/" >&- 65 | APP_HOME="`pwd -P`" 66 | cd "$SAVED" >&- 67 | 68 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 69 | 70 | # Determine the Java command to use to start the JVM. 71 | if [ -n "$JAVA_HOME" ] ; then 72 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 73 | # IBM's JDK on AIX uses strange locations for the executables 74 | JAVACMD="$JAVA_HOME/jre/sh/java" 75 | else 76 | JAVACMD="$JAVA_HOME/bin/java" 77 | fi 78 | if [ ! -x "$JAVACMD" ] ; then 79 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 80 | 81 | Please set the JAVA_HOME variable in your environment to match the 82 | location of your Java installation." 83 | fi 84 | else 85 | JAVACMD="java" 86 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 87 | 88 | Please set the JAVA_HOME variable in your environment to match the 89 | location of your Java installation." 90 | fi 91 | 92 | # Increase the maximum file descriptors if we can. 93 | if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then 94 | MAX_FD_LIMIT=`ulimit -H -n` 95 | if [ $? -eq 0 ] ; then 96 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 97 | MAX_FD="$MAX_FD_LIMIT" 98 | fi 99 | ulimit -n $MAX_FD 100 | if [ $? -ne 0 ] ; then 101 | warn "Could not set maximum file descriptor limit: $MAX_FD" 102 | fi 103 | else 104 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 105 | fi 106 | fi 107 | 108 | # For Darwin, add options to specify how the application appears in the dock 109 | if $darwin; then 110 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 111 | fi 112 | 113 | # For Cygwin, switch paths to Windows format before running java 114 | if $cygwin ; then 115 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 116 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 117 | 118 | # We build the pattern for arguments to be converted via cygpath 119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 120 | SEP="" 121 | for dir in $ROOTDIRSRAW ; do 122 | ROOTDIRS="$ROOTDIRS$SEP$dir" 123 | SEP="|" 124 | done 125 | OURCYGPATTERN="(^($ROOTDIRS))" 126 | # Add a user-defined pattern to the cygpath arguments 127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 129 | fi 130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 131 | i=0 132 | for arg in "$@" ; do 133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 135 | 136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 138 | else 139 | eval `echo args$i`="\"$arg\"" 140 | fi 141 | i=$((i+1)) 142 | done 143 | case $i in 144 | (0) set -- ;; 145 | (1) set -- "$args0" ;; 146 | (2) set -- "$args0" "$args1" ;; 147 | (3) set -- "$args0" "$args1" "$args2" ;; 148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;; 149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 154 | esac 155 | fi 156 | 157 | # Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules 158 | function splitJvmOpts() { 159 | JVM_OPTS=("$@") 160 | } 161 | eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS 162 | JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" 163 | 164 | exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" 165 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @if "%DEBUG%" == "" @echo off 2 | @rem ########################################################################## 3 | @rem 4 | @rem Gradle startup script for Windows 5 | @rem 6 | @rem ########################################################################## 7 | 8 | @rem Set local scope for the variables with windows NT shell 9 | if "%OS%"=="Windows_NT" setlocal 10 | 11 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 12 | set DEFAULT_JVM_OPTS= 13 | 14 | set DIRNAME=%~dp0 15 | if "%DIRNAME%" == "" set DIRNAME=. 16 | set APP_BASE_NAME=%~n0 17 | set APP_HOME=%DIRNAME% 18 | 19 | @rem Find java.exe 20 | if defined JAVA_HOME goto findJavaFromJavaHome 21 | 22 | set JAVA_EXE=java.exe 23 | %JAVA_EXE% -version >NUL 2>&1 24 | if "%ERRORLEVEL%" == "0" goto init 25 | 26 | echo. 27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 28 | echo. 29 | echo Please set the JAVA_HOME variable in your environment to match the 30 | echo location of your Java installation. 31 | 32 | goto fail 33 | 34 | :findJavaFromJavaHome 35 | set JAVA_HOME=%JAVA_HOME:"=% 36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 37 | 38 | if exist "%JAVA_EXE%" goto init 39 | 40 | echo. 41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 42 | echo. 43 | echo Please set the JAVA_HOME variable in your environment to match the 44 | echo location of your Java installation. 45 | 46 | goto fail 47 | 48 | :init 49 | @rem Get command-line arguments, handling Windowz variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | if "%@eval[2+2]" == "4" goto 4NT_args 53 | 54 | :win9xME_args 55 | @rem Slurp the command line arguments. 56 | set CMD_LINE_ARGS= 57 | set _SKIP=2 58 | 59 | :win9xME_args_slurp 60 | if "x%~1" == "x" goto execute 61 | 62 | set CMD_LINE_ARGS=%* 63 | goto execute 64 | 65 | :4NT_args 66 | @rem Get arguments from the 4NT Shell from JP Software 67 | set CMD_LINE_ARGS=%$ 68 | 69 | :execute 70 | @rem Setup the command line 71 | 72 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 73 | 74 | @rem Execute Gradle 75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 76 | 77 | :end 78 | @rem End local scope for the variables with windows NT shell 79 | if "%ERRORLEVEL%"=="0" goto mainEnd 80 | 81 | :fail 82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 83 | rem the _cmd.exe /c_ return code! 84 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 85 | exit /b 1 86 | 87 | :mainEnd 88 | if "%OS%"=="Windows_NT" endlocal 89 | 90 | :omega 91 | -------------------------------------------------------------------------------- /hisign.html: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 6 | 35 | 36 | 37 |
38 | 39 |
40 | 41 | 42 | -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | include ':app' 2 | -------------------------------------------------------------------------------- /video_20180702_130810.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jaydenxiao2016/VoiceManager/ed8d2838f5d2f3853f3fc1ced695ed13f9a51675/video_20180702_130810.mp4 --------------------------------------------------------------------------------