热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

Android完美实现录音编辑器

Android完美实现录音编辑器一、目标二、准备工作三、功能分析1.界面组成2.事件处理四、实现过程五、一些技术问题1.MediaPlayer2.Chronometer3.保持屏幕

Android完美实现录音编辑器

    • 一、目标
    • 二、准备工作
    • 三、功能分析
      • 1. 界面组成
      • 2. 事件处理
    • 四、实现过程
    • 五、一些技术问题
      • 1. MediaPlayer
      • 2. Chronometer
      • 3. 保持屏幕长亮
      • 4. 关闭编辑器
    • 六、开发过程回顾
    • 七、接下来
    • 八、Finally


一、目标

实现录音编辑器,为神马笔记增加录音功能做准备。
在这里插入图片描述

二、准备工作


序号准备工作描述
1Android实现录音功能汇总全面了解Android实现录音功能的各种方式,并且比较方式的优劣,最终选择MediaRecorder来实现录音。
2Android低仿iOS Messages录音波形效果使用MediaRecorder实现录音,并封装成TapeRecordView方便调用。
3Android高仿iOS Messages声音播放波形效果使用MediaPlayer播放录音,并封装成TapePlayView方便调用。
4Android高仿iOS Messages录音操作按钮实现ActionLayout作为滑动按钮。
5Android使用PopupWindow高仿iOS Messages录音弹出界面选择录音编辑器的容器,比较了Activity、Dialog、PopupWindow、FrameLayout,并选择PopupWindow作为容器。

通过5个阶段的准备工作,解决了录音相关的所有技术问题。

最后一步便是把所有功能组合到一起。

三、功能分析


1. 界面组成

整个录音编辑器界面分为2个部分。

  1. 波形
  2. 操作

界面组成控件组成
波形关闭按钮
录音波形、播放波形
时间显示
操作发送按钮
停止录音、播放录音,暂停播放

2. 事件处理


事件触发条件处理方式
布局变化通常情况下,布局不会发生变化。
长按电源键或者来电时,会隐藏软键盘,从而引起布局变化。
调整编辑器位置。
切换到后台用户按下多任务键或者Home键,或者来电时,将应用切换到后台。停止录音、停止播放

截图描述
在这里插入图片描述长按电源键,将会隐藏软键盘。

四、实现过程


序号过程
1定义PopupTape作为编辑器类,并实现show方法以显示界面
2实现录音及停止功能
3实现播放及暂停功能
4显示录音及播放时间进度
5实现关闭功能,通过关闭按钮以及用户按下返回键
6处理dispatchTouchEvent事件,实现弹出界面后可以继续操作。
7处理onGlobalLayout事件,对编辑器重新布局。
8处理onStop事件,切换到后台时自动停止录音及播放。
9增加对外事件回调接口BiConsumer,用于告知用户操作结果。
10增加属性访问接口,以插叙PopupTape属性。

五、一些技术问题


1. MediaPlayer

MediaPlayer播放过程中,调用getCurrentPosition方法获取当前播放进度。

出现时间回退的情况,出现在250毫秒~300毫秒之间。

// ..., 256, 257, 258, 258, 224, 225, 226, 227, ...

导致了绘制波形时,出现闪烁的情况。

int current = mMediaPlayer.getCurrentPosition();
this.currentPosition = (current > this.currentPosition)? current: this.currentPosition;current = this.currentPosition;

增加一个变量用于跟踪进度,防止回退。

2. Chronometer

使用Chronometer显示播放进度时,出现时间跳动的情况。

Chronometer虽然1秒回调一次,但并不是严格的1000毫秒,因此会出现累加的误差,最终导致时间跳动。

自定义Chronotimer,采用每帧刷新的方式保证时间的准确性。

public class Chronotimer extends androidx.appcompat.widget.AppCompatTextView {boolean isRunning = false;long base = 0; // base time in millisecondslong start; // start time in millisecondslong end; // end time in millisecondsPreDrawListener preDrawListener;private boolean mPreDrawRegistered;public Chronotimer(Context context) {this(context, null);}public Chronotimer(Context context, @Nullable AttributeSet attrs) {this(context, attrs, android.R.attr.textViewStyle);}public Chronotimer(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {super(context, attrs, defStyleAttr);this.preDrawListener = new PreDrawListener();}public void start() {this.isRunning = true;this.start = System.currentTimeMillis();this.end = start;this.registerForPreDraw();this.invalidate();}public void stop() {this.isRunning = false;this.end = System.currentTimeMillis();this.unregisterForPreDraw(); // must unregister OnPreDrawListener, or else draw all the time.this.invalidate();}public void setBase(long base) {this.base = base;}@Overrideprotected void onAttachedToWindow() {super.onAttachedToWindow();if (isRunning) {registerForPreDraw();}}@Overrideprotected void onDetachedFromWindow() {super.onDetachedFromWindow();this.unregisterForPreDraw();}@Overrideprotected void onDraw(Canvas canvas) {super.onDraw(canvas);if (isRunning) {this.invalidate();}// Log.w("AA", "Chronotimer onDraw = " + isRunning);}public CharSequence format(long milliseconds) {long elapse = milliseconds;elapse /= 1000; // secondsint hours = (int)(elapse / 3600);int seconds = (int)(elapse % 3600);int minute = (seconds / 60);seconds = seconds % 60;String text;if (hours > 0) {text = String.format("%d:%02d:%02d", hours, minute, seconds);} else {text = String.format("%d:%02d", minute, seconds);}return text;}private void registerForPreDraw() {if (!mPreDrawRegistered) {getViewTreeObserver().addOnPreDrawListener(preDrawListener);mPreDrawRegistered = true;}}private void unregisterForPreDraw() {getViewTreeObserver().removeOnPreDrawListener(preDrawListener);mPreDrawRegistered = false;}/****/private class PreDrawListener implements ViewTreeObserver.OnPreDrawListener {@Overridepublic boolean onPreDraw() {if (isRunning) {end = System.currentTimeMillis();}long duration = (end - start);duration += base;CharSequence text = format(duration);setText(text);return true;}}
}

3. 保持屏幕长亮

为防止锁屏自动停止录音,因此保证录音过程中屏幕长亮。

4. 关闭编辑器

与iOS Messages点击操作按钮外部区域管理编辑器不同。

必须点击关闭按钮才能关闭编辑器。

iOS Messages是聊天工具,录音内容通常较短。

神马笔记是笔记工具,用户可能进行长时间录音。

为防止用户误操作关闭编辑器,因此采用关闭按钮的方式。

六、开发过程回顾

集合了前面的所有功能,终于完成录音编辑器。

高仿了iOS Messages的录音功能:)

效果还算满意。

七、接下来

完成录音显示,及RecyclerView中多个录音的播放功能。

神马笔记实现录音笔记元素做准备。

八、Finally

佛说是经已。
长老须菩提。
及诸比丘。比丘尼。
优婆塞。优婆夷。
一切世间天人阿修罗。
闻佛所说。皆大欢喜。信受奉行。


推荐阅读
  • Android获取app应用程序大小的方法
    Android获取app应用程序大小的方法-Android对这种方法进行了封装,我们没有权限去调用这个方法,所以我们只能通过AIDL,然后利用Java的反射机制去调用系统级的方法。 ... [详细]
  • 本文详细介绍了Android中的坐标系以及与View相关的方法。首先介绍了Android坐标系和视图坐标系的概念,并通过图示进行了解释。接着提到了View的大小可以超过手机屏幕,并且只有在手机屏幕内才能看到。最后,作者表示将在后续文章中继续探讨与View相关的内容。 ... [详细]
  • 本文介绍了一款名为TimeSelector的Android日期时间选择器,采用了Material Design风格,可以在Android Studio中通过gradle添加依赖来使用,也可以在Eclipse中下载源码使用。文章详细介绍了TimeSelector的构造方法和参数说明,以及如何使用回调函数来处理选取时间后的操作。同时还提供了示例代码和可选的起始时间和结束时间设置。 ... [详细]
  • android listview OnItemClickListener失效原因
    最近在做listview时发现OnItemClickListener失效的问题,经过查找发现是因为button的原因。不仅listitem中存在button会影响OnItemClickListener事件的失效,还会导致单击后listview每个item的背景改变,使得item中的所有有关焦点的事件都失效。本文给出了一个范例来说明这种情况,并提供了解决方法。 ... [详细]
  • 带添加按钮的GridView,item的删除事件
    先上图片效果;gridView无数据时显示添加按钮,有数据时,第一格显示添加按钮,后面显示数据:布局文件:addr_manage.xml<?xmlve ... [详细]
  • 今天就跟大家聊聊有关怎么在Android应用中实现一个换肤功能,可能很多人都不太了解,为了让大家更加了解,小编给大家总结了以下内容,希望大家根 ... [详细]
  • 在Android开发中,使用Picasso库可以实现对网络图片的等比例缩放。本文介绍了使用Picasso库进行图片缩放的方法,并提供了具体的代码实现。通过获取图片的宽高,计算目标宽度和高度,并创建新图实现等比例缩放。 ... [详细]
  • 后台获取视图对应的字符串
    1.帮助类后台获取视图对应的字符串publicclassViewHelper{将View输出为字符串(注:不会执行对应的ac ... [详细]
  • 拥抱Android Design Support Library新变化(导航视图、悬浮ActionBar)
    转载请注明明桑AndroidAndroid5.0Loollipop作为Android最重要的版本之一,为我们带来了全新的界面风格和设计语言。看起来很受欢迎࿰ ... [详细]
  • 自动轮播,反转播放的ViewPagerAdapter的使用方法和效果展示
    本文介绍了如何使用自动轮播、反转播放的ViewPagerAdapter,并展示了其效果。该ViewPagerAdapter支持无限循环、触摸暂停、切换缩放等功能。同时提供了使用GIF.gif的示例和github地址。通过LoopFragmentPagerAdapter类的getActualCount、getActualItem和getActualPagerTitle方法可以实现自定义的循环效果和标题展示。 ... [详细]
  • Go GUIlxn/walk 学习3.菜单栏和工具栏的具体实现
    本文介绍了使用Go语言的GUI库lxn/walk实现菜单栏和工具栏的具体方法,包括消息窗口的产生、文件放置动作响应和提示框的应用。部分代码来自上一篇博客和lxn/walk官方示例。文章提供了学习GUI开发的实际案例和代码示例。 ... [详细]
  • 欢乐的票圈重构之旅——RecyclerView的头尾布局增加
    项目重构的Git地址:https:github.comrazerdpFriendCircletreemain-dev项目同步更新的文集:http:www.jianshu.comno ... [详细]
  • 本文介绍了在MFC下利用C++和MFC的特性动态创建窗口的方法,包括继承现有的MFC类并加以改造、插入工具栏和状态栏对象的声明等。同时还提到了窗口销毁的处理方法。本文详细介绍了实现方法并给出了相关注意事项。 ... [详细]
  • 如何在HTML中获取鼠标的当前位置
    本文介绍了在HTML中获取鼠标当前位置的三种方法,分别是相对于屏幕的位置、相对于窗口的位置以及考虑了页面滚动因素的位置。通过这些方法可以准确获取鼠标的坐标信息。 ... [详细]
  • android 触屏处理流程,android触摸事件处理流程 ? FOOKWOOD「建议收藏」
    android触屏处理流程,android触摸事件处理流程?FOOKWOOD「建议收藏」最近在工作中,经常需要处理触摸事件,但是有时候会出现一些奇怪的bug,比如有时候会检测不到A ... [详细]
author-avatar
小熊维尼-b晴
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有