Android簡易的仿微信聊天的語音播放控件

說到Android音頻播放,可謂108式,方案眾多,這里有一篇總結(jié)Android幾種播放音頻的方式的文章,傳送門。本文旨在熟悉MediaPlayer的使用,遂此控件封裝采用MediaPlayer實現(xiàn)。

下面先看下效果圖:

事件詳情3.png

當(dāng)然,如果你看了上面推薦的文章,會發(fā)現(xiàn)這種簡單的播放需求,應(yīng)該用AsyncPlayer更合適。。對此我只能說,不要在意那些細(xì)節(jié)。

廢話不多說,先來分析下需求,

從界面上看,一個圖片,一個音頻時長顯示的text,我選擇了使用TextView,圖片正好設(shè)作drawableLeft/drawableStart;其次需要一個播放的動畫,很簡單采用一個計時器,循環(huán)切換圖片資源,所以你可能需要這樣三張圖片:


audio_icon1.png
audio_icon2.png
audio_icon3.png

一 .計時器實現(xiàn)

說到計時器,你可能會想到如下一些類:Handler, Timer,TimerTask,Runnable, Thread, AlarmManager。

譬如Timer與TimerTask結(jié)合,Handler與TimerTask結(jié)合,Handler與Thread(利用Thread.sleep(time)),Handler與Runnable(Handler的postDelayed(Runnable r, long delayMillis)方法),Handler自身的sendEmptyMessageDelayed方法,都能制造一個死循環(huán)來達到計時的效果。值得注意的是要在必要的時機(eg onDestroy..),cancel timer 或者 handler removeAllCallback。。移除消息訂閱。

另外,倒計時可以使用CountDownTimer,都比較簡單。如果使用過RxJava,對其有所了解的話,也可以使用RxJava里的timer,非常強大。

二 .MediaPlayer的使用

這里本來想寫一個使用詳解的,后來用MD畫表格快吐了,原諒我懶。。。
還是推薦大家看看開發(fā)API文檔省事 :

http://www.android-doc.com/reference/android/media/MediaPlayer.html

https://developer.android.google.cn/reference/android/media/MediaPlayer.html

Android API 指南

如果你不想看嚶glish,這里還有一篇MediaPlayer詳解,基本就是API文檔里面的那些東西了

三.控件封裝

一切準(zhǔn)備就緒...
說了這么多廢話,It's time to show the fucking code!

import android.content.Context;
import android.graphics.drawable.Drawable;
import android.media.AudioManager;
import android.media.MediaPlayer;
import android.os.Handler;
import android.support.annotation.DrawableRes;
import android.support.v7.widget.AppCompatTextView;
import android.text.TextUtils;
import android.util.AttributeSet;
import android.util.Log;
import android.view.View;

import com.hykd.hykd_manage.R;

import java.io.IOException;


/**
 * Incremental change is better than ambitious failure.
 *
 * @author : <a >MysticCoder</a>
 * @date : 2018/3/21
 * @desc : 不用時一定要調(diào)用{{@link #release()}}
 */


public class AudioPlayerView extends AppCompatTextView {

    String mUrl ;

    public boolean isHasprepared() {
        return hasprepared;
    }

    /**
     * 在非初始化狀態(tài)下調(diào)用setDataSource  會拋出IllegalStateException異常
     */
    boolean hasprepared =false;

    private MediaPlayer mediaPlayer;

    private Handler handler;
    private Runnable runnable;
    private int i;

    private int[] drawLefts = new int[]{R.mipmap.audio_icon1, R.mipmap.audio_icon2,R.mipmap.audio_icon3};

    public AudioPlayerView(Context context, AttributeSet attrs) {
        super(context, attrs);

        initMediaPlayer();
    }
    private void initMediaPlayer(){

        try {
            mediaPlayer = new MediaPlayer();
            mediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);
        } catch (Exception e) {
            Log.e("mediaPlayer", " init error", e);
        }

        if (mediaPlayer != null){
            mediaPlayer.setOnPreparedListener(new MediaPlayer.OnPreparedListener() {
                @Override
                public void onPrepared(MediaPlayer mp) {

                    hasprepared = true;
                    setText(getDuration());
                }
            });
        }
        mediaPlayer.setOnErrorListener(new MediaPlayer.OnErrorListener() {
            @Override
            public boolean onError(MediaPlayer mp, int what, int extra) {
                mediaPlayer.reset();
                return false;
            }
        });
        mediaPlayer.setOnCompletionListener(new MediaPlayer.OnCompletionListener() {
            @Override
            public void onCompletion(MediaPlayer mp) {

                stopAnim();
            }
        });
        setClick();

    }

    public void setUrl(String url){

        mUrl = url;
        try {
            mediaPlayer.setDataSource(url);
            mediaPlayer.prepare();
        } catch (IOException e) {
            e.printStackTrace();
            Log.e("mediaPlayer", " set dataSource error", e);
        } catch (IllegalStateException e){
            Log.e("mediaPlayer", " set dataSource error", e);
        }
    }

    /**
     * 用于需要設(shè)置不同的dataSource
     * 二次setdataSource的時候需要reset 將MediaPlayer恢復(fù)到Initialized狀態(tài)
     * @param url
     */
    public void resetUrl(String url){

        if (TextUtils.isEmpty(mUrl)||hasprepared){
            mediaPlayer.reset();
        }
        setUrl(url);
    }

    public String getDuration(){

        int duration = mediaPlayer.getDuration();

        if (duration == -1){
            return "";
        }else {

            int sec = duration/1000;

            int m = sec/60;
            int s = sec%60;
            return m+":"+s;
        }

    }
    private void startAnim() {

        i = 0;
        if (handler == null) {
            handler = new Handler();
        }
        if (runnable == null) {
            runnable = new Runnable() {
                @Override
                public void run() {

                    handler.postDelayed(this, 500);
                    setDrawableLeft(drawLefts[i % 3]);
                    i++;
                }
            };
        }

            handler.removeCallbacks(runnable);
            handler.postDelayed(runnable, 500);
    }

    private void stopAnim(){

        setDrawableLeft(drawLefts[2]);
        if (handler != null){
            handler.removeCallbacks(runnable);
        }
    }

    /**
     *  設(shè)置 drawableLeft
     *
     * @param
     * @param id
     */
    private void setDrawableLeft(@DrawableRes int id) {
        Drawable leftDrawable = getResources().getDrawable(id);
        leftDrawable.setBounds(0, 0, leftDrawable.getMinimumWidth(), leftDrawable.getMinimumHeight());
        setCompoundDrawables(leftDrawable, null, null, null);

    }

   private void setClick(){

        setOnClickListener(new OnClickListener() {
            @Override
            public void onClick(View v) {

//                第一次調(diào)用start播放正常. 然后調(diào)用stop().停止播放.然后再調(diào)start 放不了

//
                //方案一: 先stop,再Reset ,stop換成pause也行
//                if (mediaPlayer.isPlaying()){
//                    mediaPlayer.stop();
//                    stopAnim();
//                }else {
//                    mediaPlayer.reset();
//                    setUrl(mUrl);
//                    startAnim();
//                    mediaPlayer.start();
//
//                }

                // 采取方案二  pause  代替 stop
                if (mediaPlayer.isPlaying()){
                    mediaPlayer.pause();
                    stopAnim();
                }else {
                    mediaPlayer.seekTo(0);
                    startAnim();
                    mediaPlayer.start();
                }

                }

        });
   }

    /**
     * 釋放資源
     */
    public  void release() {
        if ( mediaPlayer != null) {
            mediaPlayer.stop();
            mediaPlayer.release();
            mediaPlayer = null;
        }

        if (handler != null){
            handler.removeCallbacks(runnable);
        }
    }

}

四.碎碎念

一定要仔細(xì)看MediaPlayer的API,熟悉下面這張狀態(tài)圖!

這張狀態(tài)轉(zhuǎn)換圖清晰的描述了MediaPlayer的各個狀態(tài),也列舉了主要的方法的調(diào)用時序,每種方法只能在一些特定的狀態(tài)下使用,如果使用時MediaPlayer的狀態(tài)不正確則會引發(fā)IllegalStateException異常。


狀態(tài)圖

  • TextView 沒有直接setDrawable的方法,代碼里實現(xiàn)了一個
  • player.stop了之后不能直接start,遂采用暫停pause 實現(xiàn)停止播放;非Initialized狀態(tài),調(diào)用setDataSource()方法,會拋出IllegalStateException異常,所以提供了resetUrl,當(dāng)需要重設(shè)數(shù)據(jù)源(網(wǎng)絡(luò)音頻url,或本地path)時調(diào)用;在不合適的時候調(diào)用prepare()或prepareAsync()方法也會拋出IllegalStateException異常;prepare()或prepareAsync()方法,這兩個方法一個是同步的一個是異步的,調(diào)用后會回調(diào)OnPreparedListener進入Prepared的狀態(tài)。。。
  • 不用的時候,(比如actiity destroy)一定要釋放資源,提供release();
  • 再次強調(diào)

一定要仔細(xì)看MediaPlayer的API

MediaPlayer詳解

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 179,323評論 25 708
  • ¥開啟¥ 【iAPP實現(xiàn)進入界面執(zhí)行逐一顯】 〖2017-08-25 15:22:14〗 《//首先開一個線程,因...
    小菜c閱讀 7,383評論 0 17
  • “一日之計在于晨”,早餐是一天中最重要的一餐。早餐距離前一晚餐的時間最長,體內(nèi)儲存的糖原已經(jīng)消耗殆盡,應(yīng)及時補充,...
    米樂麻麻2014閱讀 1,164評論 5 12

友情鏈接更多精彩內(nèi)容