欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

Android仿微信錄音功能

 更新時(shí)間:2019年11月16日 09:10:11   作者:暴風(fēng)戰(zhàn)斧  
這篇文章主要為大家詳細(xì)介紹了Android仿微信錄音功能,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下

提要:需求是開發(fā)類似微信發(fā)語音的功能,沒有語音轉(zhuǎn)文字。網(wǎng)上看了一些代碼,不能拿來直接用,部分代碼邏輯有問題,所以想把自己的代碼貼出來,僅供參考。

功能:

a、設(shè)置最大錄音時(shí)長和錄音倒計(jì)時(shí)(為了方便測試,最大時(shí)長設(shè)置為15秒,開始倒計(jì)時(shí)設(shè)置為7秒)

b、在錄音之前檢查錄音和存儲(chǔ)權(quán)限

源碼:

1、錄音對(duì)話框管理類DialogManager:

/**
 * 功能:錄音對(duì)話框管理類
 */
public class DialogManager {
  private AlertDialog.Builder builder;
  private AlertDialog dialog;
  private ImageView mIcon;
  private ImageView mVoice;
  private TextView mLabel;
 
  private Context context;
 
  /**
   * 構(gòu)造方法
   *
   * @param context Activity級(jí)別的Context
   */
  public DialogManager(Context context) {
    this.context = context;
  }
 
  /**
   * 顯示錄音的對(duì)話框
   */
  public void showRecordingDialog() {
    builder = new AlertDialog.Builder(context, R.style.AudioRecorderDialogStyle);
    LayoutInflater inflater = LayoutInflater.from(context);
    View view = inflater.inflate(R.layout.audio_recorder_dialog, null);
    mIcon = view.findViewById(R.id.iv_dialog_icon);
    mVoice = view.findViewById(R.id.iv_dialog_voice);
    mLabel = view.findViewById(R.id.tv_dialog_label);
 
    builder.setView(view);
    dialog = builder.create();
    dialog.show();
    dialog.setCanceledOnTouchOutside(false);
  }
 
  /**
   * 正在播放時(shí)的狀態(tài)
   */
  public void recording() {
    if (dialog != null && dialog.isShowing()) { //顯示狀態(tài)
      mIcon.setVisibility(View.VISIBLE);
      mVoice.setVisibility(View.VISIBLE);
      mLabel.setVisibility(View.VISIBLE);
 
      mIcon.setImageResource(R.drawable.ic_audio_recorder);
      mVoice.setImageResource(R.drawable.ic_audio_v1);
      mLabel.setText(R.string.audio_record_dialog_up_to_cancel);
    }
  }
 
  /**
   * 顯示想取消的對(duì)話框
   */
  public void wantToCancel() {
    if (dialog != null && dialog.isShowing()) { //顯示狀態(tài)
      mIcon.setVisibility(View.VISIBLE);
      mVoice.setVisibility(View.GONE);
      mLabel.setVisibility(View.VISIBLE);
 
      mIcon.setImageResource(R.drawable.ic_audio_cancel);
      mLabel.setText(R.string.audio_record_dialog_release_to_cancel);
    }
  }
 
  /**
   * 顯示時(shí)間過短的對(duì)話框
   */
  public void tooShort() {
    if (dialog != null && dialog.isShowing()) { //顯示狀態(tài)
      mIcon.setVisibility(View.VISIBLE);
      mVoice.setVisibility(View.GONE);
      mLabel.setVisibility(View.VISIBLE);
 
      mLabel.setText(R.string.audio_record_dialog_too_short);
    }
  }
 
  // 顯示取消的對(duì)話框
  public void dismissDialog() {
    if (dialog != null && dialog.isShowing()) { //顯示狀態(tài)
      dialog.dismiss();
      dialog = null;
    }
  }
 
  /**
   * 顯示更新音量級(jí)別的對(duì)話框
   *
   * @param level 1-7
   */
  public void updateVoiceLevel(int level) {
    if (dialog != null && dialog.isShowing()) { //顯示狀態(tài)
      mIcon.setVisibility(View.VISIBLE);
      mVoice.setVisibility(View.VISIBLE);
      mLabel.setVisibility(View.VISIBLE);
 
      int resId = context.getResources().getIdentifier("ic_audio_v" + level, "drawable", context.getPackageName());
      mVoice.setImageResource(resId);
    }
  }
 
  public void updateTime(int time) {
    if (dialog != null && dialog.isShowing()) { //顯示狀態(tài)
      mIcon.setVisibility(View.VISIBLE);
      mVoice.setVisibility(View.VISIBLE);
      mLabel.setVisibility(View.VISIBLE);
      mLabel.setText(time + "s");
    }
  }
}

2、錄音管理類AudioManager

 /**
 * 功能:錄音管理類
 */
public class AudioManager {
  private MediaRecorder mMediaRecorder;
  private String mDir;
  private String mCurrentFilePath;
 
  private static AudioManager mInstance;
 
  private boolean isPrepared;
 
  private AudioManager(String dir) {
    this.mDir = dir;
  }
 
  //單例模式:在這里實(shí)例化AudioManager并傳入錄音文件地址
  public static AudioManager getInstance(String dir) {
    if (mInstance == null) {
      synchronized (AudioManager.class) {
        if (mInstance == null) {
          mInstance = new AudioManager(dir);
        }
      }
    }
    return mInstance;
  }
 
  /**
   * 回調(diào)準(zhǔn)備完畢
   */
  public interface AudioStateListener {
    void wellPrepared();
  }
 
  public AudioStateListener mListener;
 
  /**
   * 回調(diào)方法
   */
  public void setOnAudioStateListener(AudioStateListener listener) {
    mListener = listener;
  }
 
  /**
   * 準(zhǔn)備
   */
  public void prepareAudio() {
    try {
      isPrepared = false;
      File dir = FileUtils.createNewFile(mDir);
      String fileName = generateFileName();
 
      File file = new File(dir, fileName);
      mCurrentFilePath = file.getAbsolutePath();
      Logger.t("AudioManager").i("audio file name :" + mCurrentFilePath);
 
      mMediaRecorder = new MediaRecorder();
      //設(shè)置輸出文件
      mMediaRecorder.setOutputFile(mCurrentFilePath);
      //設(shè)置MediaRecorder的音頻源為麥克風(fēng)
      mMediaRecorder.setAudioSource(MediaRecorder.AudioSource.MIC);
      //設(shè)置音頻格式
      mMediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.MPEG_4);
      //設(shè)置音頻的格式為AAC
      mMediaRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AAC);
      //準(zhǔn)備錄音
      mMediaRecorder.prepare();
      //開始
      mMediaRecorder.start();
      //準(zhǔn)備結(jié)束
      isPrepared = true;
      if (mListener != null) {
        mListener.wellPrepared();
      }
    } catch (Exception e) {
      e.printStackTrace();
    }
  }
 
  /**
   * 隨機(jī)生成文件的名稱
   */
  private String generateFileName() {
    return UUID.randomUUID().toString() + ".m4a";
  }
 
  public int getVoiceLevel(int maxLevel) {
    if (isPrepared) {
      try {
        //獲得最大的振幅getMaxAmplitude() 1-32767
        return maxLevel * mMediaRecorder.getMaxAmplitude() / 32768 + 1;
      } catch (Exception e) {
      }
    }
    return 1;
  }
 
  /**
   * 釋放資源
   */
  public void release() {
    if (mMediaRecorder != null) {
      mMediaRecorder.stop();
      mMediaRecorder.release();
      mMediaRecorder = null;
    }
  }
 
  public void cancel() {
    release();
    if (mCurrentFilePath != null) {
      File file = new File(mCurrentFilePath);
      FileUtils.deleteFile(file);
      mCurrentFilePath = null;
    }
  }
 
  public String getCurrentFilePath() {
    return mCurrentFilePath;
  }
}

3、自定義錄音按鈕AudioRecorderButton

/**
 * 功能:錄音按鈕
 */
public class AudioRecorderButton extends AppCompatButton {
  private Context mContext;
  //取消錄音Y軸位移
  private static final int DISTANCE_Y_CANCEL = 80;
  //錄音最大時(shí)長限制
  private static final int AUDIO_RECORDER_MAX_TIME = 15;
  //錄音倒計(jì)時(shí)時(shí)間
  private static final int AUDIO_RECORDER_COUNT_DOWN = 7;
  //狀態(tài)
  private static final int STATE_NORMAL = 1;// 默認(rèn)的狀態(tài)
  private static final int STATE_RECORDING = 2;// 正在錄音
  private static final int STATE_WANT_TO_CANCEL = 3;// 希望取消
  //當(dāng)前的狀態(tài)
  private int mCurrentState = STATE_NORMAL;
  //已經(jīng)開始錄音
  private boolean isRecording = false;
  //是否觸發(fā)onLongClick
  private boolean mReady;
 
  private DialogManager mDialogManager;
  private AudioManager mAudioManager;
  private android.media.AudioManager audioManager;
 
  public AudioRecorderButton(Context context) {
    this(context, null);
  }
 
  public AudioRecorderButton(Context context, AttributeSet attrs) {
    super(context, attrs);
    this.mContext = context;
    mDialogManager = new DialogManager(context);
    audioManager = (android.media.AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
 
    String dir = SdUtils.getCustomFolder("Audios");//創(chuàng)建文件夾
    mAudioManager = AudioManager.getInstance(dir);
    mAudioManager.setOnAudioStateListener(new AudioManager.AudioStateListener() {
      @Override
      public void wellPrepared() {
        mHandler.sendEmptyMessage(MSG_AUDIO_PREPARED);
      }
    });
    //按鈕長按 準(zhǔn)備錄音 包括start
    setOnLongClickListener(new OnLongClickListener() {
      @Override
      public boolean onLongClick(View v) {
        //先判斷有沒有錄音和存儲(chǔ)權(quán)限,有則開始錄音,沒有就申請(qǐng)權(quán)限
        int hasAudioPermission = ContextCompat.checkSelfPermission(mContext, Manifest.permission.RECORD_AUDIO);
        int hasStoragePermission = ContextCompat.checkSelfPermission(mContext, Manifest.permission.WRITE_EXTERNAL_STORAGE);
        if (hasAudioPermission == PackageManager.PERMISSION_GRANTED && hasStoragePermission == PackageManager.PERMISSION_GRANTED) {
          mReady = true;
          mAudioManager.prepareAudio();
        } else {
          RxPermissions permissions = new RxPermissions((FragmentActivity) mContext);
          Disposable disposable = permissions.request(Manifest.permission.RECORD_AUDIO, Manifest.permission.WRITE_EXTERNAL_STORAGE)
              .subscribe(new Consumer<Boolean>() {
                @Override
                public void accept(Boolean granted) {
                  if (!granted) {
                    ToastUtils.showShort("發(fā)送語音功能需要賦予錄音和存儲(chǔ)權(quán)限");
                  }
                }
              });
        }
        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;
  //錄音時(shí)長
  private float mTime;
 
  //獲取音量大小的Runnable
  private Runnable mGetVoiceLevelRunnable = new Runnable() {
    @Override
    public void run() {
      while (isRecording) {
        try {
          Thread.sleep(100);
          mTime += 0.1f;
          mHandler.sendEmptyMessage(MSG_VOICE_CHANGED);
          if (mTime >= AUDIO_RECORDER_MAX_TIME) {//如果時(shí)間超過60秒,自動(dòng)結(jié)束錄音
            while (!mThreadFlag) {//記錄已經(jīng)結(jié)束了錄音,不需要再次結(jié)束,以免出現(xiàn)問題
              mDialogManager.dismissDialog();
              mAudioManager.release();
              if (audioFinishRecorderListener != null) {
                //先回調(diào),再Reset,不然回調(diào)中的時(shí)間是0
                audioFinishRecorderListener.onFinish(mTime, mAudioManager.getCurrentFilePath());
                mHandler.sendEmptyMessage(MSG_TIME_OUT);
              }
              mThreadFlag = !mThreadFlag;
            }
            isRecording = false;
          } else if (mTime >= AUDIO_RECORDER_COUNT_DOWN) {
            mHandler.sendEmptyMessage(UPDATE_TIME);
          }
        } catch (InterruptedException e) {
          e.printStackTrace();
        }
      }
    }
  };
 
  private Handler mHandler = new Handler(new Handler.Callback() {
    @Override
    public boolean handleMessage(Message msg) {
      switch (msg.what) {
        case MSG_AUDIO_PREPARED:
          mDialogManager.showRecordingDialog();
          isRecording = true;
          new Thread(mGetVoiceLevelRunnable).start();
          break;
        case MSG_VOICE_CHANGED:
          mDialogManager.updateVoiceLevel(mAudioManager.getVoiceLevel(7));
          break;
        case MSG_DIALOG_DISMISS:
          mDialogManager.dismissDialog();
          break;
        case MSG_TIME_OUT:
          reset();
          break;
        case UPDATE_TIME:
          int countDown = (int) (AUDIO_RECORDER_MAX_TIME - mTime);
          mDialogManager.updateTime(countDown);
          break;
      }
      return true;
    }
  });
 
  /**
   * 錄音完成后的回調(diào)
   */
  public interface AudioFinishRecorderListener {
    /**
     * @param seconds 時(shí)長
     * @param filePath 文件
     */
    void onFinish(float seconds, String filePath);
  }
 
  private AudioFinishRecorderListener audioFinishRecorderListener;
 
  public void setAudioFinishRecorderListener(AudioFinishRecorderListener listener) {
    audioFinishRecorderListener = listener;
  }
 
  android.media.AudioManager.OnAudioFocusChangeListener onAudioFocusChangeListener = new android.media.AudioManager.OnAudioFocusChangeListener() {
    @Override
    public void onAudioFocusChange(int focusChange) {
      if (focusChange == android.media.AudioManager.AUDIOFOCUS_LOSS) {
        audioManager.abandonAudioFocus(onAudioFocusChangeListener);
      }
    }
  };
 
  public void myRequestAudioFocus() {
    audioManager.requestAudioFocus(onAudioFocusChangeListener, android.media.AudioManager.STREAM_MUSIC, android.media.AudioManager.AUDIOFOCUS_GAIN_TRANSIENT);
  }
 
  @Override
  public boolean onTouchEvent(MotionEvent event) {
    Logger.t("AudioManager").i("x :" + event.getX() + "-Y:" + event.getY());
    switch (event.getAction()) {
      case MotionEvent.ACTION_DOWN:
        mThreadFlag = false;
        isRecording = true;
        changeState(STATE_RECORDING);
        myRequestAudioFocus();
        break;
      case MotionEvent.ACTION_MOVE:
        if (isRecording) {
          //根據(jù)想x,y的坐標(biāo),判斷是否想要取消
          if (event.getY() < 0 && Math.abs(event.getY()) > DISTANCE_Y_CANCEL) {
            changeState(STATE_WANT_TO_CANCEL);
          } else {
            changeState(STATE_RECORDING);
          }
        }
        break;
      case MotionEvent.ACTION_UP:
        //如果longClick 沒觸發(fā)
        if (!mReady) {
          reset();
          return super.onTouchEvent(event);
        }
        //觸發(fā)了onLongClick 沒準(zhǔn)備好,但是已經(jīng)prepared已經(jīng)start
        //所以消除文件夾
        if (!isRecording || mTime < 1.0f) {
          mDialogManager.tooShort();
          mAudioManager.cancel();
          mHandler.sendEmptyMessageDelayed(MSG_DIALOG_DISMISS, 1000);
        } else if (mCurrentState == STATE_RECORDING) {//正常錄制結(jié)束
          mDialogManager.dismissDialog();
          mAudioManager.release();
          if (audioFinishRecorderListener != null) {
            audioFinishRecorderListener.onFinish(mTime, mAudioManager.getCurrentFilePath());
          }
        } else if (mCurrentState == STATE_WANT_TO_CANCEL) {
          mDialogManager.dismissDialog();
          mAudioManager.cancel();
        }
        reset();
        audioManager.abandonAudioFocus(onAudioFocusChangeListener);
        break;
    }
    return super.onTouchEvent(event);
  }
 
  /**
   * 恢復(fù)狀態(tài) 標(biāo)志位
   */
  private void reset() {
    isRecording = false;
    mTime = 0;
    mReady = false;
    changeState(STATE_NORMAL);
  }
 
  /**
   * 改變狀態(tài)
   */
  private void changeState(int state) {
    if (mCurrentState != state) {
      mCurrentState = state;
      switch (state) {
        case STATE_NORMAL:
          setText(R.string.audio_record_button_normal);
          break;
        case STATE_RECORDING:
          if (isRecording) {
            mDialogManager.recording();
          }
          setText(R.string.audio_record_button_recording);
          break;
        case STATE_WANT_TO_CANCEL:
          mDialogManager.wantToCancel();
          setText(R.string.audio_record_button_cancel);
          break;
      }
    }
  }
}

4、DialogStyle

<!--App Base Theme-->
<style name="AppThemeParent" parent="Theme.AppCompat.Light.NoActionBar">
  <!--不顯示狀態(tài)欄:22之前-->
  <item name="android:windowNoTitle">true</item>
  <item name="android:windowAnimationStyle">@style/ActivityAnimTheme</item><!--Activity動(dòng)畫-->
  <item name="actionOverflowMenuStyle">@style/MenuStyle</item><!--toolbar菜單樣式-->
</style>
 
<!--Dialog式的Activity-->
<style name="ActivityDialogStyle" parent="AppThemeParent">
  <item name="android:windowBackground">@android:color/transparent</item>
  <!-- 浮于Activity之上 -->
  <item name="android:windowIsFloating">true</item>
  <!-- 邊框 -->
  <item name="android:windowFrame">@null</item>
  <!-- Dialog以外的區(qū)域模糊效果 -->
  <item name="android:backgroundDimEnabled">true</item>
  <!-- 半透明 -->
  <item name="android:windowIsTranslucent">true</item>
  <!-- Dialog進(jìn)入及退出動(dòng)畫 -->
  <item name="android:windowAnimationStyle">@style/ActivityDialogAnimation</item>
</style>
 
<!--Audio Recorder Dialog-->
<style name="AudioRecorderDialogStyle" parent="ActivityDialogStyle">
  <!-- Dialog以外的區(qū)域模糊效果 -->
  <item name="android:backgroundDimEnabled">false</item>
</style>
 
<!-- Dialog動(dòng)畫:漸入漸出-->
<style name="ActivityDialogAnimation" parent="@android:style/Animation.Dialog">
  <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:android="http://schemas.android.com/apk/res/android"
  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:id="@+id/iv_dialog_icon"
      android:layout_width="wrap_content"
      android:layout_height="wrap_content"
      android:src="@drawable/ic_audio_recorder" />
 
    <ImageView
      android:id="@+id/iv_dialog_voice"
      android:layout_width="wrap_content"
      android:layout_height="wrap_content"
      android:src="@drawable/ic_audio_v1" />
 
  </LinearLayout>
 
  <TextView
    android:id="@+id/tv_dialog_label"
    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、用到的字符串

<!--AudioRecord-->
<string name="audio_record_button_normal">按住&#160;說話</string>
<string name="audio_record_button_recording">松開&#160;結(jié)束</string>
<string name="audio_record_button_cancel">松開手指&#160;取消發(fā)送</string>
<string name="audio_record_dialog_up_to_cancel">手指上劃,取消發(fā)送</string>
<string name="audio_record_dialog_release_to_cancel">松開手指,取消發(fā)送</string>
<string name="audio_record_dialog_too_short">錄音時(shí)間過短</string>

7、使用:按鈕的樣式不需要寫在自定義Button中,方便使用

<com.kidney.base_library.view.audioRecorder.AudioRecorderButton
  android:id="@+id/btn_audio_recorder"
  android:layout_width="match_parent"
  android:layout_height="wrap_content"
  android:text="@string/audio_record_button_normal" />
 
 AudioRecorderButton audioRecorderButton = findViewById(R.id.btn_audio_recorder);
 audioRecorderButton.setAudioFinishRecorderListener(new AudioRecorderButton.AudioFinishRecorderListener() {
   @Override
   public void onFinish(float seconds, String filePath) {
     Logger.i(seconds + "秒:" + filePath);
   }
 });

 以上就是本文的全部內(nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。

相關(guān)文章

  • Android開發(fā)中Activity屬性設(shè)置小結(jié)

    Android開發(fā)中Activity屬性設(shè)置小結(jié)

    Android應(yīng)用開發(fā)中會(huì)經(jīng)常遇到Activity組件的使用,下面就來講解下Activity組件。Activity的生命周期、通信方式和IntentFilter等內(nèi)容,并提供了一些日常開發(fā)中經(jīng)常用到的關(guān)于Activity的技巧和方法。通過本文,你可以進(jìn)一步了接Android中Activity的運(yùn)作方式。
    2015-05-05
  • Android懸浮窗的實(shí)現(xiàn)步驟

    Android懸浮窗的實(shí)現(xiàn)步驟

    最近想做一個(gè)懸浮窗秒表的功能,所以看下懸浮窗具體的實(shí)現(xiàn)步驟,接下來通過本文給大家介紹Android懸浮窗的實(shí)現(xiàn),需要的朋友可以參考下
    2024-01-01
  • Android 實(shí)現(xiàn)秒轉(zhuǎn)換成時(shí)分秒的方法

    Android 實(shí)現(xiàn)秒轉(zhuǎn)換成時(shí)分秒的方法

    這篇文章主要介紹了Android 實(shí)現(xiàn)秒轉(zhuǎn)換成時(shí)分秒的方法,本文通過實(shí)例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下
    2020-05-05
  • Flutter封裝組動(dòng)畫混合動(dòng)畫AnimatedGroup示例詳解

    Flutter封裝組動(dòng)畫混合動(dòng)畫AnimatedGroup示例詳解

    這篇文章主要為大家介紹了Flutter封裝組動(dòng)畫混合動(dòng)畫AnimatedGroup示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2023-01-01
  • Android為應(yīng)用添加數(shù)字角標(biāo)的簡單實(shí)現(xiàn)

    Android為應(yīng)用添加數(shù)字角標(biāo)的簡單實(shí)現(xiàn)

    應(yīng)用的角標(biāo)是用來標(biāo)記有多少條提醒沒讀,本篇文章主要介紹了Android為應(yīng)用添加角標(biāo)的簡單實(shí)現(xiàn),有興趣的可以了解一下。
    2017-04-04
  • Android表格自定義控件使用詳解

    Android表格自定義控件使用詳解

    這篇文章主要為大家詳細(xì)介紹了Android表格自定義控件的使用方法,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2022-03-03
  • 解決Android studio3.6安裝后gradle Download失敗(構(gòu)建不成功)

    解決Android studio3.6安裝后gradle Download失敗(構(gòu)建不成功)

    這篇文章主要介紹了解決Android studio3.6安裝后gradle Download失敗(構(gòu)建不成功),文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2020-03-03
  • 詳解Flutter自定義應(yīng)用程序內(nèi)鍵盤的實(shí)現(xiàn)方法

    詳解Flutter自定義應(yīng)用程序內(nèi)鍵盤的實(shí)現(xiàn)方法

    本文將展示如何利用Flutter創(chuàng)建自定義鍵盤小部件,用于在自己的應(yīng)用程序中的Flutter TextField中輸入文本,感興趣的小伙伴可以了解一下
    2022-06-06
  • Android開發(fā)listview選中高亮簡單實(shí)現(xiàn)代碼分享

    Android開發(fā)listview選中高亮簡單實(shí)現(xiàn)代碼分享

    這篇文章主要介紹了Android開發(fā)listview選中高亮簡單實(shí)現(xiàn)代碼分享,具有一定借鑒價(jià)值,需要的朋友可以參考下
    2018-01-01
  • 深度剖析Android Binder IPC機(jī)制

    深度剖析Android Binder IPC機(jī)制

    Android系統(tǒng)的成功離不開其強(qiáng)大的IPC(Inter-Process Communication)機(jī)制,其中最引人注目的就是Binder,本文將深入探討B(tài)inder的技術(shù)原理,解釋其工作方式以及相關(guān)的關(guān)鍵概念
    2023-10-10

最新評(píng)論