Android 录音机功能之读取音档绘制波形,可自订裁剪区间,录音暂停继续

Android 录音机功能之读取音档绘制波形,可自订裁剪区间,录音暂停继续    下载地址:源码

一.效果图:

 

二.实现:

1.简介:

使用多个github项目,实现了以下:

*读取手机上有的音档
*滑动自订 裁剪音档区间
*可录音暂停后继续录音
*录音同时绘制波型图

2.添加依赖:

apply plugin: 'com.android.application'

android {
    compileSdkVersion 25
    buildToolsVersion '27.0.3'
    defaultConfig {
        applicationId "com.mkjihu.audioedit"
        minSdkVersion 19
        targetSdkVersion 25
        versionCode 1
        versionName "1.0"
        testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
    }
    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }
    }
}

dependencies {
    compile fileTree(dir: 'libs', include: ['*.jar'])


    compile 'com.android.support:appcompat-v7:25.+'
    compile 'com.android.support:support-v4:25.+'

    compile 'com.android.support:recyclerview-v7:25.+'
    //--RxJava2
    compile 'io.reactivex.rxjava2:rxandroid:2.0.1'
    compile 'io.reactivex.rxjava2:rxjava:2.1.0'
    compile 'com.jakewharton.rxbinding2:rxbinding:2.0.0'

    //--MP3轉檔
    compile 'com.github.adrielcafe:AndroidAudioConverter:0.0.8'

    compile 'org.florescu.android.rangeseekbar:rangeseekbar-library:0.3.0'
    compile 'com.github.Jay-Goo:RangeSeekBar:v1.1.0'


}

设置权限:


    <uses-sdk
        android:minSdkVersion="19"
        android:targetSdkVersion="25" />

    <!-- 使用音场效果必要的权限 -->
    <uses-permission android:name="android.permission.RECORD_AUDIO" />

    <uses-permission android:name="android.permission.READ_CONTACTS" />
    <uses-permission android:name="android.permission.WRITE_CONTACTS" />
    <uses-permission android:name="android.permission.WRITE_SETTINGS" />

    <!-- 使用SD卡 -->
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
    <uses-permission android:name="android.permission.MOUNT_UNMOUNT_FILESYSTEMS" />
    <uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" />
    <uses-permission android:name="android.permission.INTERNET" />
    <!-- 允许程序重新启动其他程序
	<uses-permission android:name="android.permission.RESTART_PACKAGES" />

3.主函数:


import java.io.File;
import java.io.FilenameFilter;
import java.io.UnsupportedEncodingException;

import com.mkjihu.audioedit.Presenter.MainPresenter;
import com.mkjihu.audioedit.obj.DialogBox;

import android.Manifest;
import android.app.ProgressDialog;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.database.Cursor;
import android.os.Bundle;
import android.os.Environment;
import android.os.Handler;
import android.provider.MediaStore;
import android.support.v4.app.ActivityCompat;
import android.support.v4.content.ContextCompat;
import android.support.v4.widget.SwipeRefreshLayout;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.widget.DividerItemDecoration;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.support.v7.widget.RecyclerView.ViewHolder;
import android.util.Base64;
import android.util.Log;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.ImageView;

public class MainActivity extends AppCompatActivity implements SwipeRefreshLayout.OnRefreshListener {

	public SwipeRefreshLayout mSwipeRefreshLayout;
	public RecyclerView recycler_view;
	public ProgressDialog progressDialog;
	public MainPresenter presenter;
	
	final private int REQUEST_CODE_ASK_PERMISSIONS = 123;
	
	public ImageView imageView1;
	
	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.activity_main);
		fid();
		presenter = new MainPresenter(this);
		progressDialog = new ProgressDialog(this);
		progressDialog.setCancelable(true);
		progressDialog.setInverseBackgroundForced(false);
		progressDialog.setCanceledOnTouchOutside(false);
		progressDialog.setMessage("掃描中...");
		presenter.GetAudio();
		

		
		try {
	        if (ContextCompat.checkSelfPermission(this, Manifest.permission.READ_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {
				//申请WRITE_EXTERNAL_STORAGE权限
				ActivityCompat.requestPermissions(this, new String[]{
						Manifest.permission.READ_EXTERNAL_STORAGE
						,Manifest.permission.WRITE_EXTERNAL_STORAGE
						, Manifest.permission.READ_PHONE_STATE
						, Manifest.permission.RECORD_AUDIO
						, Manifest.permission.MODIFY_AUDIO_SETTINGS
						, Manifest.permission.WRITE_CONTACTS}, REQUEST_CODE_ASK_PERMISSIONS);
	        }
		} catch (Exception e) {
			// TODO: handle exception
		}
		
		
	}
	
	
	
  	
	//權限同意返回
	@Override
	public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
		switch (requestCode) {
			case REQUEST_CODE_ASK_PERMISSIONS:
				if(verifyPermissions(grantResults)){
					Log.i("!!!", "同意");
				}
				else {
					Log.i("!!!", "不同意");
					DialogBox.getAlertDialog2(this, "提示", "請至設定之應用程式,開啟權限!");
				}
				super.onRequestPermissionsResult(requestCode, permissions, grantResults);
				break;
			default:
				super.onRequestPermissionsResult(requestCode, permissions, grantResults);
		}
	}
	
	
	public boolean verifyPermissions(int[] grantResults) {
        // At least one result must be checked.
        if(grantResults.length < 1){
            return false;
        }

        // Verify that each required permission has been granted, otherwise return false.
        for (int result : grantResults) {
            if (result != PackageManager.PERMISSION_GRANTED) {
                return false;
            }
        }
        return true;
    }

	public void tosdg(String aas) {
		Intent intent = new Intent(this, SoundFilePage.class);
		intent.putExtra("va", aas);
		startActivity(intent);
	}

	
	private void fid() 
	{
		imageView1 = (ImageView)findViewById(R.id.imageView1);
		recycler_view = (RecyclerView)findViewById(R.id.recycler_view);
		recycler_view.setLayoutManager(new LinearLayoutManager(this));
		//下面这行代码就是添加分隔线的方法
		recycler_view.addItemDecoration(new DividerItemDecoration(this,DividerItemDecoration.VERTICAL));
		mSwipeRefreshLayout = (SwipeRefreshLayout)findViewById(R.id.mSwipeRefreshLayout);
		//设置下拉出现小圆圈是否是缩放出现,出现的位置,最大的下拉位置
		mSwipeRefreshLayout.setProgressViewOffset(true, 0, 200);
		//设置下拉圆圈的大小,两个值 LARGE, DEFAULT
		//mSwipeRefreshLayout.setSize(SwipeRefreshLayout.LARGE);
		// 设置下拉圆圈上的颜色,蓝色、绿色、橙色、红色
		mSwipeRefreshLayout.setColorSchemeResources(
		    android.R.color.holo_blue_bright,
		    android.R.color.holo_green_light,
		    android.R.color.holo_orange_light,
		    android.R.color.holo_red_light);
		mSwipeRefreshLayout.setOnRefreshListener(this);	
		
		imageView1.setOnClickListener(new OnClickListener() {
			@Override
			public void onClick(View v) {
				Intent intent = new Intent(MainActivity.this, RecordingPage.class);
				startActivity(intent);
			}
		});
		
	}
	public void adasp(RecyclerView.Adapter<ViewHolder> adapter) {
		recycler_view.setAdapter(adapter);
	}
	
	public void offRefresh() {
		mSwipeRefreshLayout.setRefreshing(false);
		//iwillPaint.dissdig();
	}
	public void openRefresh() {
		mSwipeRefreshLayout.setRefreshing(true);
	}
	@Override
	public void onRefresh() {
		//下拉更新執行
		
		new Handler().postDelayed(new Runnable()  {
			@Override
			public void run()  {
				presenter.GetAudio();
			}
		}, 1000);
	
	}
	@Override
	protected void onDestroy() {
		presenter.disposable.dispose();
		super.onDestroy();
	}
	
	
}

4.布局:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/LinearLayout1"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    tools:context="com.mkjihu.audioedit.MainActivity" >
    <FrameLayout
        android:layout_width="match_parent"
        android:layout_height="?actionBarSize"
        android:background="@color/colorPrimary" >

        <ImageView
            android:id="@+id/imageView1"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_gravity="right|center"
            android:layout_marginRight="8dp"
            android:src="@drawable/uin" />

        <TextView
            android:id="@+id/textView1"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_gravity="center"
            android:text="可讀音頻列表"
            android:textColor="#FFFFFF"
            android:textSize="16sp" />

    </FrameLayout>
    <android.support.v4.widget.SwipeRefreshLayout
        android:id="@+id/mSwipeRefreshLayout"
        android:layout_width="match_parent"
        android:layout_height="match_parent" >

		<android.support.v7.widget.RecyclerView
		    
    		android:id="@+id/recycler_view"
    		android:layout_width="match_parent"
    		android:layout_height="match_parent"
    		android:clipToPadding="false"
    		android:scrollbarStyle="outsideOverlay"
    		android:scrollbars="vertical" >

		</android.support.v7.widget.RecyclerView>
	</android.support.v4.widget.SwipeRefreshLayout>
    
</LinearLayout>

5.录音相关功能RecordingPage.java:

 

import java.nio.ByteBuffer;

import com.androidquery.AQuery;
import com.cokus.wavelibrary.draw.WaveCanvas;
import com.cokus.wavelibrary.view.WaveSurfaceView;
import com.mkjihu.audioedit.Presenter.RecordingPresenter;
import com.mkjihu.audioedit.view.Chronometer;

import android.app.ProgressDialog;
import android.app.AlertDialog.Builder;
import android.content.DialogInterface;
import android.content.Intent;
import android.graphics.PixelFormat;
import android.net.Uri;
import android.os.Bundle;
import android.os.SystemClock;
import android.support.v7.app.AppCompatActivity;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.EditText;
import android.widget.Toast;

public class RecordingPage extends AppCompatActivity {

	public AQuery aq;
	
	public WaveSurfaceView waveSfv;
	public WaveCanvas waveCanvas;
	private ProgressDialog logdialogs;
	public RecordingPresenter presenter;
	public int RegType = 0;
	private String fileName = "MergePCM";//完成合併後的文件名;
	
	private EditText editText;
	public Chronometer chronometer1;
	public long escapeTime = 0;
	
	//https://github.com/adrielcafe/AndroidAudioConverter
	
	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.activity_recording_page);
		aq = new AQuery(this);
		fid();
		presenter = new RecordingPresenter(this,waveSfv);
		aq.id(R.id.bt1).clicked(ls);
		aq.id(R.id.bt2).clicked(ls);
		aq.id(R.id.bt3).clicked(ls);
		aq.id(R.id.bt1).text("開始");
		RegType = 0;
	}
	
	public OnClickListener ls = new OnClickListener() {
		
		@Override
		public void onClick(View v) {
			switch (v.getId()) {
			case R.id.bt1:
				waveSfv.setVisibility(View.VISIBLE);
				if (RegType==0) {//按下开始
					
					aq.id(R.id.bt1).text("暫停");
					presenter.StartReg();
					RegType = 1;
					chronometer1.setBase(SystemClock.elapsedRealtime() + escapeTime);
					chronometer1.start();
				}
				else {//按下暫停
					
					aq.id(R.id.bt1).text("開始");//-呼叫暫停
					presenter.Stop(0);//暫停
					RegType = 0;
					
					escapeTime = chronometer1.getBase() - SystemClock.elapsedRealtime();
					chronometer1.stop();
				}
				
				break;
			case R.id.bt2:
				aq.id(R.id.bt1).text("開始");
				presenter.Stop(0);//暫停
				presenter.clearFiles();
				presenter.clidview();
				RegType = 0;
				escapeTime = 0;
				
				
				chronometer1.stop();
				chronometer1.setBase(SystemClock.elapsedRealtime());
				
				
				break;
			case R.id.bt3:
				
				if (!editText.getText().toString().equals("")) {
					fileName = editText.getText().toString();
				}
				presenter.steTitle(fileName);
				if (RegType==1) {//播放狀態
					//presenter.Stop(1);
					Toast.makeText(RecordingPage.this, "請先暫停錄音", Toast.LENGTH_SHORT).show();
				}
				else{//已暫停狀態
					presenter.Stop(2);
				}
				
				break;
			}
		}
	};
	
	public void toSidie(final String Path)
	{
		
		Builder builder = new Builder(this);
        builder.setTitle("訊號轉換完成");
        builder.setCancelable(false);
        builder.setMessage("檔案路徑:"+Path);
        //設定Negative按鈕資料
        builder.setNegativeButton("確定", new DialogInterface.OnClickListener() {
            @Override
            public void onClick(DialogInterface dialog, int which) 
            { 
            	Intent intent = new Intent(RecordingPage.this, SoundFilePage.class);
        		intent.putExtra("va", Path);
        		startActivity(intent);
        		finish();
            }
        });
        builder.create().show();
	}
	

	@Override
	protected void onDestroy() {
		try {
			presenter.destroy();
		} catch (Exception e) { }
		super.onDestroy();
	}



	private void fid() {
		waveSfv = (WaveSurfaceView)findViewById(R.id.wavesfv);
		if(waveSfv != null) {
            waveSfv.setLine_off(42);
            //解决surfaceView黑色闪动效果
            waveSfv.setZOrderOnTop(true);
            waveSfv.getHolder().setFormat(PixelFormat.TRANSLUCENT);
        }
		logdialogs = new ProgressDialog(this);
		logdialogs.setCancelable(false);
		logdialogs.setInverseBackgroundForced(false);
		logdialogs.setCanceledOnTouchOutside(false);
		logdialogs.setMessage("訊號轉換中...");
		chronometer1 = (Chronometer)findViewById(R.id.chronometer1);
		editText = (EditText)findViewById(R.id.editText1);
	}
	   
	//--顯示加載
	public void showdia() {
		if(logdialogs!=null && !logdialogs.isShowing()) {
			logdialogs.show();
		}
	}			
	//此处关闭加載
	public void disdia() {
		if(logdialogs!=null && logdialogs.isShowing()) {
			logdialogs.dismiss();
		}
	}	

	//==================================================================================================================================================
	
	
	
    //轉換為短字節
    private byte[] short2byte(short[] sData) {
        int shortArrsize = sData.length;
        byte[] bytes = new byte[shortArrsize * 2];
        for (int i = 0; i < shortArrsize; i++) {
            bytes[i * 2] = (byte) (sData[i] & 0x00FF);
            bytes[(i * 2) + 1] = (byte) (sData[i] >> 8);
            sData[i] = 0;
        }
        return bytes;

    }
    
    //byte[] 轉換 short[]
    public static short[] shortMe(byte[] bytes) {
        short[] out = new short[bytes.length / 2]; // will drop last byte if odd number
        ByteBuffer bb = ByteBuffer.wrap(bytes);
        for (int i = 0; i < out.length; i++) {
            out[i] = bb.getShort();
        }
        return out;
    }
}

 6.录音布局:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/LinearLayout1"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    tools:context="com.mkjihu.audioedit.RecordingPage" >
    
    <FrameLayout
        android:layout_width="match_parent"
        android:layout_height="?actionBarSize"
        android:background="@color/colorPrimary" >

        <TextView
            android:id="@+id/gwg"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_gravity="center"
            android:text="錄音"
            android:textColor="#FFFFFF"
            android:textSize="16sp" />

    </FrameLayout>   
    
    <FrameLayout
        android:layout_width="match_parent"
        android:layout_height="150dp" >

    	<com.cokus.wavelibrary.view.WaveSurfaceView
    	    android:id="@+id/wavesfv"
    	    android:layout_width="fill_parent"
    	    android:layout_height="150dp" />
    </FrameLayout>

    <com.mkjihu.audioedit.view.Chronometer
        android:id="@+id/chronometer1"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center"
        android:layout_margin="8dp"
        android:text="Chronometer"
        android:textSize="20sp" />

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content" >

        <Button
            android:id="@+id/bt1"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:text="開始" />

        <Button
            android:id="@+id/bt2"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:text="重錄" />

        <Button
            android:id="@+id/bt3"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:text="完成" />

    </LinearLayout>

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_margin="5dp" >

        <TextView
            android:id="@+id/textView1"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="檔名:"
            android:textSize="20sp" />

        <EditText
            android:id="@+id/editText1"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:ems="10"
            android:hint="未输入将使用预设檔名" />

    </LinearLayout>

</LinearLayout>

7.自定义实现音波类:

import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Rect;
import android.util.AttributeSet;
import android.util.Log;
import android.view.SurfaceHolder;
import android.view.SurfaceView;

/**
 * 该类只是一个初始化surfaceview的封装
 */
public class WaveSurfaceView extends SurfaceView implements SurfaceHolder.Callback{
	private SurfaceHolder holder;
	private int line_off;//上下边距距离
	

    public int getLine_off() {
		return line_off;
	}


	public void setLine_off(int line_off) {
		this.line_off = line_off;
	}


	public WaveSurfaceView(Context context, AttributeSet attrs) {
		super(context, attrs);
		// TODO Auto-generated constructor stub
		this.holder = getHolder();
		holder.addCallback(this);
		
	}


    /**
     * @author cokus
     * init surfaceview
     */
    public  void initSurfaceView( final SurfaceView sfv){
    	new Thread(){
    		public void run() {
    			 Canvas canvas = sfv.getHolder().lockCanvas(  
    	                 new Rect(0, 0, sfv.getWidth(), sfv.getHeight()));// 关键:获取画布  
    	         if(canvas==null){
    	        	 return;
    	         }
    	         //canvas.drawColor(Color.rgb(241, 241, 241));// 清除背景  
    	         canvas.drawARGB(255, 239, 239, 239);
    	        
				int height = sfv.getHeight()-line_off;
    	         Paint paintLine =new Paint();
    	         Paint centerLine =new Paint();
    	         Paint circlePaint = new Paint();
    	         circlePaint.setColor(Color.rgb(246, 131, 126));
    	         circlePaint.setAntiAlias(true);
    	         
    	         canvas.drawCircle(0, line_off/4, line_off/4, circlePaint);// 上面小圆
    	         canvas.drawCircle(0, sfv.getHeight()-line_off/4, line_off/4, circlePaint);// 下面小圆
    	         canvas.drawLine(0, 0, 0, sfv.getHeight(), circlePaint);//垂直的线
    	         paintLine.setColor(Color.rgb(169, 169, 169));
    	         centerLine.setColor(Color.rgb(85, 140, 208));//-音波繪製顏色39, 199, 175
    	         canvas.drawLine(0, line_off/2, sfv.getWidth(), line_off/2, paintLine);//最上面的那根线
    	         canvas.drawLine(0, sfv.getHeight()-line_off/2-1, sfv.getWidth(), sfv.getHeight()-line_off/2-1, paintLine);//最下面的那根线  
//    	         canvas.drawLine(0, height*0.25f+20, sfv.getWidth(),height*0.25f+20, paintLine);//第二根线
//    	         canvas.drawLine(0, height*0.75f+20, sfv.getWidth(),height*0.75f+20, paintLine);//第3根线
    	         canvas.drawLine(0, height*0.5f+line_off/2, sfv.getWidth() ,height*0.5f+line_off/2, centerLine);//中心线
    	         sfv.getHolder().unlockCanvasAndPost(canvas);// 解锁画布,提交画好的图像
    		};
    	}.start();
    	
    }


	@Override
	public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
		// TODO Auto-generated method stub
		
	}

	@Override
	public void surfaceCreated(SurfaceHolder holder) {
		// TODO Auto-generated method stub
		initSurfaceView(this);
		
	}

	@Override
	public void surfaceDestroyed(SurfaceHolder holder) {
		
	}

	

}

 8.自定义时间显示控件:


import android.content.Context;
import android.os.Handler;
import android.os.Message;
import android.os.SystemClock;
import android.util.AttributeSet;
import android.widget.TextView;
import java.text.DecimalFormat;

public class Chronometer extends TextView {
    @SuppressWarnings("unused")
	private static final String TAG = "Chronometer";

    public interface OnChronometerTickListener {

        void onChronometerTick(Chronometer chronometer);
    }

    private long mBase;
    private boolean mVisible;
    private boolean mStarted;
    private boolean mRunning;
    private OnChronometerTickListener mOnChronometerTickListener;

    private static final int TICK_WHAT = 2;

    private long timeElapsed;
    
    public Chronometer(Context context) {
        this (context, null, 0);
    }

    public Chronometer(Context context, AttributeSet attrs) {
        this (context, attrs, 0);
    }

    public Chronometer(Context context, AttributeSet attrs, int defStyle) {
        super (context, attrs, defStyle);

        init();
    }

    private void init() {
        mBase = SystemClock.elapsedRealtime();
        updateText(mBase);
    }

    public void setBase(long base) {
        mBase = base;
        dispatchChronometerTick();
        updateText(SystemClock.elapsedRealtime());
    }

    public long getBase() {
        return mBase;
    }

    public void setOnChronometerTickListener(
            OnChronometerTickListener listener) {
        mOnChronometerTickListener = listener;
    }

    public OnChronometerTickListener getOnChronometerTickListener() {
        return mOnChronometerTickListener;
    }

    public void start() {
        mStarted = true;
        updateRunning();
    }

    public void stop() {
        mStarted = false;
        updateRunning();
    }


    public void setStarted(boolean started) {
        mStarted = started;
        updateRunning();
    }

    @Override
    protected void onDetachedFromWindow() {
        super .onDetachedFromWindow();
        mVisible = false;
        updateRunning();
    }

    @Override
    protected void onWindowVisibilityChanged(int visibility) {
        super .onWindowVisibilityChanged(visibility);
        mVisible = visibility == VISIBLE;
        updateRunning();
    }

    private synchronized void updateText(long now) {
        timeElapsed = now - mBase;
        
        DecimalFormat df = new DecimalFormat("00");
        
        int hours = (int)(timeElapsed / (3600 * 1000));
        int remaining = (int)(timeElapsed % (3600 * 1000));
        
        int minutes = (int)(remaining / (60 * 1000));
        remaining = (int)(remaining % (60 * 1000));
        
        int seconds = (int)(remaining / 1000);
        remaining = (int)(remaining % (1000));
        
        int milliseconds = (int)(((int)timeElapsed % 1000) / 10);
        
        String text = "";
        
        if (hours > 0) {
        	text += df.format(hours) + ":";
        }
        
       	text += df.format(minutes) + ":";
       	text += df.format(seconds) + ":";
       	//text += Integer.toString(milliseconds);
       	text +=	String.format("%02d", milliseconds);
       	
        setText(text);
    }

    private void updateRunning() {
        boolean running = mVisible && mStarted;
        if (running != mRunning) {
            if (running) {
                updateText(SystemClock.elapsedRealtime());
                dispatchChronometerTick();
                mHandler.sendMessageDelayed(Message.obtain(mHandler,TICK_WHAT), 10);
            } else {
                mHandler.removeMessages(TICK_WHAT);
            }
            mRunning = running;
        }
    }

    private Handler mHandler = new Handler() {
        public void handleMessage(Message m) {
            if (mRunning) {
                updateText(SystemClock.elapsedRealtime());
                dispatchChronometerTick();
                sendMessageDelayed(Message.obtain(this , TICK_WHAT),10);
            }
        }
    };

    void dispatchChronometerTick() {
        if (mOnChronometerTickListener != null) {
            mOnChronometerTickListener.onChronometerTick(this);
        }
    }

	public long getTimeElapsed() {
		return timeElapsed;
	}
    
}

具体代码请看:https://github.com/mkjihu/AudioWaveViewEdit

参考:

https://github.com/mkjihu/AudioWaveViewEdit

https://github.com/cokuscz/audioWaveCanvas

https://github.com/Jay-Goo/RangeSeekBar

https://github.com/google/ringdroid

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值