SoFunction
Updated on 2025-04-10

Android imitation WeChat recording function

Abstract: The requirement is to develop functions similar to WeChat's voice sending, without voice to text. I have read some codes online, but they cannot be used directly. Some code logic is problematic, so I want to post my own code for reference only.

Function:

a. Set the maximum recording time and recording countdown (for the convenience of testing, the maximum duration is set to 15 seconds, and the start countdown is set to 7 seconds)

b. Check recording and storage permissions before recording

Source code:

1. DialogManager for recording dialog management:

/**
  * Function: Recording dialog management class
  */
public class DialogManager {
  private  builder;
  private AlertDialog dialog;
  private ImageView mIcon;
  private ImageView mVoice;
  private TextView mLabel;
 
  private Context context;
 
  /**
    * Constructing method
    *
    * @param context Activity level Context
    */
  public DialogManager(Context context) {
     = context;
  }
 
  /**
    * Display the recording dialog box
    */
  public void showRecordingDialog() {
    builder = new (context, );
    LayoutInflater inflater = (context);
    View view = (.audio_recorder_dialog, null);
    mIcon = (.iv_dialog_icon);
    mVoice = (.iv_dialog_voice);
    mLabel = (.tv_dialog_label);
 
    (view);
    dialog = ();
    ();
    (false);
  }
 
  /**
    * Status when playing
    */
  public void recording() {
    if (dialog != null && ()) { //Show status      ();
      ();
      ();
 
      (.ic_audio_recorder);
      (.ic_audio_v1);
      (.audio_record_dialog_up_to_cancel);
    }
  }
 
  /**
    * Show the dialog box you want to cancel
    */
  public void wantToCancel() {
    if (dialog != null && ()) { //Show status      ();
      ();
      ();
 
      (.ic_audio_cancel);
      (.audio_record_dialog_release_to_cancel);
    }
  }
 
  /**
    * Display dialog box with too short time
    */
  public void tooShort() {
    if (dialog != null && ()) { //Show status      ();
      ();
      ();
 
      (.audio_record_dialog_too_short);
    }
  }
 
  // Show the canceled dialog box  public void dismissDialog() {
    if (dialog != null && ()) { //Show status      ();
      dialog = null;
    }
  }
 
  /**
    * Displays the dialog box for updating volume level
    *
    * @param level 1-7
    */
  public void updateVoiceLevel(int level) {
    if (dialog != null && ()) { //Show status      ();
      ();
      ();
 
      int resId = ().getIdentifier("ic_audio_v" + level, "drawable", ());
      (resId);
    }
  }
 
  public void updateTime(int time) {
    if (dialog != null && ()) { //Show status      ();
      ();
      ();
      (time + "s");
    }
  }
}

2. AudioManager for recording management

 /**
  * Function: Recording management
  */
public class AudioManager {
  private MediaRecorder mMediaRecorder;
  private String mDir;
  private String mCurrentFilePath;
 
  private static AudioManager mInstance;
 
  private boolean isPrepared;
 
  private AudioManager(String dir) {
     = dir;
  }
 
  //Singleton Mode: Instantiate AudioManager here and pass in the recording file address  public static AudioManager getInstance(String dir) {
    if (mInstance == null) {
      synchronized () {
        if (mInstance == null) {
          mInstance = new AudioManager(dir);
        }
      }
    }
    return mInstance;
  }
 
  /**
    * Callback is ready
    */
  public interface AudioStateListener {
    void wellPrepared();
  }
 
  public AudioStateListener mListener;
 
  /**
    * Callback method
    */
  public void setOnAudioStateListener(AudioStateListener listener) {
    mListener = listener;
  }
 
  /**
    * Prepare
    */
  public void prepareAudio() {
    try {
      isPrepared = false;
      File dir = (mDir);
      String fileName = generateFileName();
 
      File file = new File(dir, fileName);
      mCurrentFilePath = ();
      ("AudioManager").i("audio file name :" + mCurrentFilePath);
 
      mMediaRecorder = new MediaRecorder();
      //Set the output file      (mCurrentFilePath);
      //Set the audio source of MediaRecorder to microphone      ();
      //Set audio format      (.MPEG_4);
      //Set the audio format to AAC      ();
      //Prepare for recording      ();
      //start      ();
      //Prepare to end      isPrepared = true;
      if (mListener != null) {
        ();
      }
    } catch (Exception e) {
      ();
    }
  }
 
  /**
    * Name of randomly generated file
    */
  private String generateFileName() {
    return ().toString() + ".m4a";
  }
 
  public int getVoiceLevel(int maxLevel) {
    if (isPrepared) {
      try {
        //Get the maximum amplitude getMaxAmplitude() 1-32767        return maxLevel * () / 32768 + 1;
      } catch (Exception e) {
      }
    }
    return 1;
  }
 
  /**
    * Free up resources
    */
  public void release() {
    if (mMediaRecorder != null) {
      ();
      ();
      mMediaRecorder = null;
    }
  }
 
  public void cancel() {
    release();
    if (mCurrentFilePath != null) {
      File file = new File(mCurrentFilePath);
      (file);
      mCurrentFilePath = null;
    }
  }
 
  public String getCurrentFilePath() {
    return mCurrentFilePath;
  }
}

3. Customize the recording button AudioRecorderButton

/**
  * Function: Recording button
  */
public class AudioRecorderButton extends AppCompatButton {
  private Context mContext;
  //Cancel the recording Y-axis displacement  private static final int DISTANCE_Y_CANCEL = 80;
  //Maximum recording time limit  private static final int AUDIO_RECORDER_MAX_TIME = 15;
  //Countdown time for recording  private static final int AUDIO_RECORDER_COUNT_DOWN = 7;
  //state  private static final int STATE_NORMAL = 1;// Default status  private static final int STATE_RECORDING = 2;// Recording  private static final int STATE_WANT_TO_CANCEL = 3;// Hope to cancel  //Current status  private int mCurrentState = STATE_NORMAL;
  //The recording has started  private boolean isRecording = false;
  //Whether onLongClick is triggered  private boolean mReady;
 
  private DialogManager mDialogManager;
  private AudioManager mAudioManager;
  private  audioManager;
 
  public AudioRecorderButton(Context context) {
    this(context, null);
  }
 
  public AudioRecorderButton(Context context, AttributeSet attrs) {
    super(context, attrs);
     = context;
    mDialogManager = new DialogManager(context);
    audioManager = () (Context.AUDIO_SERVICE);
 
    String dir = ("Audios");//Create a folder    mAudioManager = (dir);
    (new () {
      @Override
      public void wellPrepared() {
        (MSG_AUDIO_PREPARED);
      }
    });
    //Press the button to prepare for recording, including start    setOnLongClickListener(new OnLongClickListener() {
      @Override
      public boolean onLongClick(View v) {
        //First determine whether you have recording and storage permissions. If you have, start recording. If you have no permissions, apply for permissions.        int hasAudioPermission = (mContext, .RECORD_AUDIO);
        int hasStoragePermission = (mContext, .WRITE_EXTERNAL_STORAGE);
        if (hasAudioPermission == PackageManager.PERMISSION_GRANTED && hasStoragePermission == PackageManager.PERMISSION_GRANTED) {
          mReady = true;
          ();
        } else {
          RxPermissions permissions = new RxPermissions((FragmentActivity) mContext);
          Disposable disposable = (.RECORD_AUDIO, .WRITE_EXTERNAL_STORAGE)
              .subscribe(new Consumer<Boolean>() {
                @Override
                public void accept(Boolean granted) {
                  if (!granted) {
                    ("The voice sending function requires recording and storage permissions");
                  }
                }
              });
        }
        return true;
      }
    });
  }
 
  private static final int MSG_AUDIO_PREPARED = 0X110;
  private static final int MSG_VOICE_CHANGED = 0X111;
  private static final int MSG_DIALOG_DISMISS = 0X112;
  private static final int MSG_TIME_OUT = 0x113;
  private static final int UPDATE_TIME = 0x114;
 
  private boolean mThreadFlag = false;
  //Recording time  private float mTime;
 
  //Get the volume size Runnable  private Runnable mGetVoiceLevelRunnable = new Runnable() {
    @Override
    public void run() {
      while (isRecording) {
        try {
          (100);
          mTime += 0.1f;
          (MSG_VOICE_CHANGED);
          if (mTime >= AUDIO_RECORDER_MAX_TIME) {//If the time exceeds 60 seconds, the recording will automatically end            while (!mThreadFlag) {//The recording has been completed and the recording does not need to be completed again to avoid problems              ();
              ();
              if (audioFinishRecorderListener != null) {
                //Call back first, then Reset, otherwise the time in the callback will be 0                (mTime, ());
                (MSG_TIME_OUT);
              }
              mThreadFlag = !mThreadFlag;
            }
            isRecording = false;
          } else if (mTime >= AUDIO_RECORDER_COUNT_DOWN) {
            (UPDATE_TIME);
          }
        } catch (InterruptedException e) {
          ();
        }
      }
    }
  };
 
  private Handler mHandler = new Handler(new () {
    @Override
    public boolean handleMessage(Message msg) {
      switch () {
        case MSG_AUDIO_PREPARED:
          ();
          isRecording = true;
          new Thread(mGetVoiceLevelRunnable).start();
          break;
        case MSG_VOICE_CHANGED:
          ((7));
          break;
        case MSG_DIALOG_DISMISS:
          ();
          break;
        case MSG_TIME_OUT:
          reset();
          break;
        case UPDATE_TIME:
          int countDown = (int) (AUDIO_RECORDER_MAX_TIME - mTime);
          (countDown);
          break;
      }
      return true;
    }
  });
 
  /**
    * Callback after recording is completed
    */
  public interface AudioFinishRecorderListener {
    /**
      * @param seconds Duration
      * @param filePath file
      */
    void onFinish(float seconds, String filePath);
  }
 
  private AudioFinishRecorderListener audioFinishRecorderListener;
 
  public void setAudioFinishRecorderListener(AudioFinishRecorderListener listener) {
    audioFinishRecorderListener = listener;
  }
 
   onAudioFocusChangeListener = new () {
    @Override
    public void onAudioFocusChange(int focusChange) {
      if (focusChange == .AUDIOFOCUS_LOSS) {
        (onAudioFocusChangeListener);
      }
    }
  };
 
  public void myRequestAudioFocus() {
    (onAudioFocusChangeListener, .STREAM_MUSIC, .AUDIOFOCUS_GAIN_TRANSIENT);
  }
 
  @Override
  public boolean onTouchEvent(MotionEvent event) {
    ("AudioManager").i("x :" + () + "-Y:" + ());
    switch (()) {
      case MotionEvent.ACTION_DOWN:
        mThreadFlag = false;
        isRecording = true;
        changeState(STATE_RECORDING);
        myRequestAudioFocus();
        break;
      case MotionEvent.ACTION_MOVE:
        if (isRecording) {
          //Judge whether you want to cancel based on the coordinates of x and y          if (() < 0 && (()) > DISTANCE_Y_CANCEL) {
            changeState(STATE_WANT_TO_CANCEL);
          } else {
            changeState(STATE_RECORDING);
          }
        }
        break;
      case MotionEvent.ACTION_UP:
        //If longClick does not trigger        if (!mReady) {
          reset();
          return (event);
        }
        //Triggered onLongClick not ready, but prepared has started        //So eliminate folder        if (!isRecording || mTime < 1.0f) {
          ();
          ();
          (MSG_DIALOG_DISMISS, 1000);
        } else if (mCurrentState == STATE_RECORDING) {//The normal recording ends          ();
          ();
          if (audioFinishRecorderListener != null) {
            (mTime, ());
          }
        } else if (mCurrentState == STATE_WANT_TO_CANCEL) {
          ();
          ();
        }
        reset();
        (onAudioFocusChangeListener);
        break;
    }
    return (event);
  }
 
  /**
    * Recovery status Flag bit
    */
  private void reset() {
    isRecording = false;
    mTime = 0;
    mReady = false;
    changeState(STATE_NORMAL);
  }
 
  /**
    * Change state
    */
  private void changeState(int state) {
    if (mCurrentState != state) {
      mCurrentState = state;
      switch (state) {
        case STATE_NORMAL:
          setText(.audio_record_button_normal);
          break;
        case STATE_RECORDING:
          if (isRecording) {
            ();
          }
          setText(.audio_record_button_recording);
          break;
        case STATE_WANT_TO_CANCEL:
          ();
          setText(.audio_record_button_cancel);
          break;
      }
    }
  }
}

4、DialogStyle

<!--App Base Theme-->
<style name="AppThemeParent" parent="">
  <!--The status bar does not display:22Before-->
  <item name="android:windowNoTitle">true</item>
  <item name="android:windowAnimationStyle">@style/ActivityAnimTheme</item><!--ActivityAnimation-->
  <item name="actionOverflowMenuStyle">@style/MenuStyle</item><!--toolbarMenu Style-->
</style>
 
<!--DialogStyleActivity-->
<style name="ActivityDialogStyle" parent="AppThemeParent">
  <item name="android:windowBackground">@android:color/transparent</item>
  <!-- Floating onActivityAbove -->
  <item name="android:windowIsFloating">true</item>
  <!-- frame -->
  <item name="android:windowFrame">@null</item>
  <!-- DialogBlur effect outside the area -->
  <item name="android:backgroundDimEnabled">true</item>
  <!-- translucent -->
  <item name="android:windowIsTranslucent">true</item>
  <!-- Dialog进入及退出Animation -->
  <item name="android:windowAnimationStyle">@style/ActivityDialogAnimation</item>
</style>
 
<!--Audio Recorder Dialog-->
<style name="AudioRecorderDialogStyle" parent="ActivityDialogStyle">
  <!-- DialogBlur effect outside the area -->
  <item name="android:backgroundDimEnabled">false</item>
</style>
 
<!-- DialogAnimation:Gradually in and out-->
<style name="ActivityDialogAnimation" parent="@android:style/">
  <item name="android:windowEnterAnimation">@anim/fade_in</item>
  <item name="android:windowExitAnimation">@anim/fade_out</item>
</style>

5、DialogLayout

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:andro
  android:layout_width="wrap_content"
  android:layout_height="wrap_content"
  android:background="@drawable/audio_recorder_dialog_bg"
  android:gravity="center"
  android:orientation="vertical"
  android:padding="20dp">
 
  <LinearLayout
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:orientation="horizontal">
 
    <ImageView
      android:
      android:layout_width="wrap_content"
      android:layout_height="wrap_content"
      android:src="@drawable/ic_audio_recorder" />
 
    <ImageView
      android:
      android:layout_width="wrap_content"
      android:layout_height="wrap_content"
      android:src="@drawable/ic_audio_v1" />
 
  </LinearLayout>
 
  <TextView
    android:
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_marginTop="15dp"
    android:text="@string/audio_record_dialog_up_to_cancel"
    android:textColor="@color/white"
    android:textSize="15dp" />
</LinearLayout>

6. Used strings

&lt;!--AudioRecord--&gt;
&lt;string name="audio_record_button_normal"&gt;Press and hold&amp;#160;Talking</string>&lt;string name="audio_record_button_recording"&gt;release&amp;#160;End</string>&lt;string name="audio_record_button_cancel"&gt;release手指&amp;#160;Cancel send</string>&lt;string name="audio_record_dialog_up_to_cancel"&gt;Stroke on your fingers,Cancel send&lt;/string&gt;
&lt;string name="audio_record_dialog_release_to_cancel"&gt;release手指,Cancel send&lt;/string&gt;
&lt;string name="audio_record_dialog_too_short"&gt;Recording time is too short&lt;/string&gt;

7. Use: The button style does not need to be written in a custom button, which is convenient for use

&lt;.base_library.
  android:
  android:layout_width="match_parent"
  android:layout_height="wrap_content"
  android:text="@string/audio_record_button_normal" /&gt;
 
 AudioRecorderButton audioRecorderButton = findViewById(.btn_audio_recorder);
 (new () {
   @Override
   public void onFinish(float seconds, String filePath) {
     (seconds + "Second:" + filePath);
   }
 });

The above is all the content of this article. I hope it will be helpful to everyone's study and I hope everyone will support me more.