作为一个常年和Android“斗智斗勇”的老码农,我见过太多播放器:有的点开就静音到让人怀疑人生,有的突然外放吓得同事摔了键盘……今天咱们就撸个带智商+情商的音乐播放器,重点解决两大灵魂拷问:“声儿呢?!” 和**“太吵了闭嘴!”**。
一、MediaPlayer:它可不是个“傻白甜”
别看MediaPlayer只是个类,人家内心戏多着呢!新手常把它当MP3点击器,结果分分钟表演“闪退三连”:
val mediaPlayer = MediaPlayer().apply {
setDataSource("你的音频路径") // 路径一错,立刻扑街!
prepare() // 纯真版:在主线程调?系统反手一个ANR警告!
start() // 最怕此时手机响起微信电话…音乐秒变BGM伴奏
}
高级玩法:给它穿上“防弹衣”!
- 用
prepareAsync()异步准备——相当于让播放器自己蹲角落加载,不堵UI线程; - 状态监听连环套:
mediaPlayer.setOnPreparedListener {
// 此时才能安全点击播放,别像急着拆快递把盒子撕烂了
btn_play.isEnabled = true
}
mediaPlayer.setOnCompletionListener {
// 循环播放?在这偷偷调mediaPlayer.seekTo(0)就行
}
二、音量控制:SeekBar的“丝滑魔术”
为什么你的音量拖拽像卡了痰?因为缺了进度反馈闭环!记住这个公式:
// ① 初始化音量滑杆(0f-1f范围)
val audioManager = getSystemService(AUDIO_SERVICE) as AudioManager
val maxVolume = audioManager.getStreamMaxVolume(AudioManager.STREAM_MUSIC)
val currentVolume = audioManager.getStreamVolume(AudioManager.STREAM_MUSIC)
seekBar_volume.max = 100
seekBar_volume.progress = (currentVolume.toFloat() / maxVolume * 100).toInt()
// ② 拖拽时实时调节(这才是灵魂!)
seekBar_volume.setOnSeekBarChangeListener(object : OnSeekBarChangeListener {
override fun onProgressChanged(seekBar: SeekBar?, progress: Int, fromUser: Boolean) {
if (fromUser) {
val volume = progress / 100.0f
audioManager.setStreamVolume(AudioManager.STREAM_MUSIC, (volume * maxVolume).toInt(), 0)
// 第三个flag:0表示安静调整,AudioManager.FLAG_PLAY_SOUND会作死地播放调节音效…
}
}
})
三、音频焦点争夺战:比抢车位还刺激!
你的App放音乐时突然来了电话?没处理音频焦点的话,就会变成**“音乐+铃声”双重奏**!学学高情商播放器:
val audioFocusRequest = AudioFocusRequest.Builder(AudioManager.AUDIOFOCUS_GAIN).run {
setOnAudioFocusChangeListener { focusChange ->
when (focusChange) {
AudioManager.AUDIOFOCUS_LOSS -> mediaPlayer.pause() // 别人霸占车位,咱先熄火
AudioManager.AUDIOFOCUS_LOSS_TRANSIENT -> mediaPlayer.pause() // 临时被抢,比如导航说话
AudioManager.AUDIOFOCUS_GAIN -> mediaPlayer.start() // 重新抢回方向盘!
}
}
build()
}
// 申请焦点!没这步就像没驾照开车
audioManager.requestAudioFocus(audioFocusRequest)
四、完整代码:会“蹦迪”的播放器长这样!
(Activity完整版,粘贴即用)
class MainActivity : AppCompatActivity() {
private lateinit var mediaPlayer: MediaPlayer
private lateinit var audioManager: AudioManager
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
// 初始化管理器
audioManager = getSystemService(AUDIO_SERVICE) as AudioManager
setupMediaPlayer()
setupVolumeSeekBar()
}
private fun setupMediaPlayer() {
mediaPlayer = MediaPlayer.create(this, R.raw.your_music) // 资源文件扔进res/raw/
btn_play.setOnClickListener {
if (!mediaPlayer.isPlaying) {
mediaPlayer.start()
btn_play.text = "暂停"
} else {
mediaPlayer.pause()
btn_play.text = "播放"
}
}
}
private fun setupVolumeSeekBar() {
val maxVolume = audioManager.getStreamMaxVolume(AudioManager.STREAM_MUSIC)
val currentVolume = audioManager.getStreamVolume(AudioManager.STREAM_MUSIC)
seekBar_volume.max = 100
seekBar_volume.progress = (currentVolume.toFloat() / maxVolume * 100).toInt()
seekBar_volume.setOnSeekBarChangeListener(object : OnSeekBarChangeListener {
override fun onProgressChanged(seekBar: SeekBar?, progress: Int, fromUser: Boolean) {
if (fromUser) {
audioManager.setStreamVolume(
AudioManager.STREAM_MUSIC,
(progress / 100.0f * maxVolume).toInt(), 0
)
}
}
// 必须重写但用不到的方法…(假装这里有空实现)
})
}
override fun onDestroy() {
mediaPlayer.release() // 别忘了!否则下次打开播放器会变“哑巴”
super.onDestroy()
}
}
五、踩坑预警:血泪总结3条保命技巧
- 资源释放是爸爸:MediaPlayer用完必须
release(),否则内存泄漏多来几次,App直接表演“原地去世”; - 生命周期要偷窥:App退到后台记得暂停,监听到
onPause()时就该让播放器“眯一会儿”; - 异常处理不能浪:
IllegalStateException是MediaPlayer的祖传大招,所有操作都得包在try-catch里。
现在运行你的播放器——拖动滑杆时,是不是终于有了“掌控全局”的爽感?这不仅是技术实现,更是和Android系统的一场深度对话。当你理解音频焦点、系统服务协作时,你的代码就真正“活”过来了。
彩蛋:想让播放器更智能?试试在音频焦点丢失时自动淡出音量,恢复时再淡入,用户体验直接封神!(提示:用ObjectAnimator操作音量值+Handler延时)
总结:从“哑巴应用”到“智能DJ”,只需搞定MediaPlayer的状态机管理、SeekBar的实时反馈机制、音频焦点的场景化协作。你的代码不止能跑,还能带着节奏跑!
打造带音量控制的Android音乐播放器

被折叠的 条评论
为什么被折叠?



