用flutter写个音乐app
特别鸣谢
学习flutter 我看的最多的两个系列教程,分享给大家,希望对小伙伴有所帮助
简介
用业余时间学习flutter也有大半年了,感觉相当不错,不但开发方便,速度快,还出身名门,有大厂背书,之前uniapp 出来不久的时候我也用业余时间学了学,用本地的音乐数据接口写了个简单的音乐app,在这里要感谢 网易云音乐api 这个超级棒的开源项目,但是后来一直没有在真实商业项目中使用这个技术,时间一长也就忘了,后来flutter横空出世,光芒四射,爱学习的我怎么能放过她,写这篇作文的目的是想对自己这段时间学习的知识点进行梳理,归纳总结,增强记忆,
项目构成
后端数据
前面提到的 网易云音乐api 下载到本地,运行起来后作为音乐服务器给app提供数据
前端
就是这次的主角 flutter app啦 我也把代码上传到码云啦 代码地址
目前的页面部分页面截图
播放页和播放状态下首页显示播放进度
全局只有一个音乐播放器,在首页初始化,之所以放在首页是因为想实现音乐播放全局生效,不受某个路由限制,使用provider全局共享播放器状态,待播放列表,使用event_bus做事件广播,全局控制播放器播放或者暂停,上一首,下一首
目前用到的插件
dependencies:
flutter:
sdk: flutter
# The following adds the Cupertino Icons font to your application.
# Use with the CupertinoIcons class for iOS style icons.
cupertino_icons: ^1.0.2
video_player: ^2.1.14 // 视频播放器
chewie: ^1.2.2 //视频播放器
flutter_swiper_null_safety: ^1.0.2 //轮播图
dio: ^4.0.0 // 数据请求
flutter_easyloading: ^3.0.3 // loading 动画插件
provider: ^5.0.0 // 状态管理
event_bus: ^2.0.0 // 事件广播
audioplayers: ^0.20.1 // 音频插件
audio_video_progress_bar: ^0.9.0 // 播放进度条插件
intl: ^0.17.0
flutter_screenutil: ^5.0.0+2 // 屏幕尺寸适配插件
percent_indicator: ^3.0.1 // 圆形进度条插件
flutter_local_notifications: ^8.2.0 // 系统通知栏消息通知插件
wakelock: ^0.5.6 // 保持屏幕常亮的插件
首页,短视频,我的(目前是空的,还没有想好写什么)这三个页面是三个组件,为了实现每次进入改页面时候不会刷新,保存页面状态,使用的indexStack组件,这个组件使用也很简单,和原来的写法区别并不大。
// body: tabs[_currentIndex],
body: IndexedStack(
index: _currentIndex,
children: tabs,
),
移动端项目首先要解决的就是不同尺寸设备下尺寸适配问题,这里我走了不少弯路,开始都是自己写的方法,在网上搜的,代码量不多,用起来还挺好的,但是,这种方法打包了正式版就无法识别,下面是原来的代码
import 'package:flutter/material.dart';
import 'dart:ui';
class Adapt {
static MediaQueryData mediaQuery = MediaQueryData.fromWindow(window);
static double _width = mediaQuery.size.width;
static double _height = mediaQuery.size.height;
static double _topbarH = mediaQuery.padding.top;
static double _botbarH = mediaQuery.padding.bottom;
static double _pixelRatio = mediaQuery.devicePixelRatio;
static var _ratio;
static init(int number) {
int uiwidth = number is int ? number : 750;
_ratio = _width / uiwidth;
}
static px(number) {
if (!(_ratio is double || _ratio is int)) {
Adapt.init(750);
}
return number * _ratio;
}
static onepx() {
return 1 / _pixelRatio;
}
static screenW() {
return _width;
}
static screenH() {
return _height;
}
static padTopH() {
return _topbarH;
}
static padBotH() {
return _botbarH;
}
}
原来的用法
import '../utils/adapt.dart';
...
...
...
leading: Image.network(
_list[index]['al']['picUrl'],
width: Adapt.px(100),
height: Adapt.px(100),
),
开发版挺好的,但是打包正式版,所有用了这种方法的元素都不见了,识别不了这种尺寸,但并不是完全无法识别,十次有一两次是正常显示的,我就很郁闷,各种试错,也没有解决,要是有哪位小哥哥在正式版用过这类方法可以教教我,最后我还是老老实实地用了 flutter_screenutil 使用起来挺方便,网上教程一堆,值得注意的是这个插件经过比较大的改版,新版和老版的使用方法完全不一样,注意分辨。 奇怪的是,我原来的页面并没有改,还是原来的方法,只是新页面用这个方法,打包正式版之后,原来的页面也显示正常了,我也无法解释。
仿抖音短视频滑动切换效果(这个页面已经重构,不再使用第三方的chewie 这个播放器,下面有重构详细原因)
短视频播放页我想做的类似抖音那种感觉,上下滑动切换,正好官方有PageView组件,这个组件用起来挺方便的,分分钟写个轮播图功能,但是实际用起来真实伤透了脑筋,这个组件正常的快速滑动没有问题,但是我要是慢慢的滑动,滑到大概屏幕30%时候触发组件的onchange事件,我要是不松手再滑回来,就又触发一次,连续来回滑多次,就触发多次,本来是要在onchange事件里重新加载视频资源的,这样一弄,播放器立马就报错卡死了,后来就在组件外边包一个Listener组件,更精准的识别用户操作,
body: Listener(
onPointerDown: (event) {
this.setState(() {
_isChange = false; // 每次手指按下的时候还原_isChange的值为false
});
},
onPointerUp: (upPointEvent) {
var _duration = Duration(milliseconds: 200);
Timer(_duration, () { //用个定时器延迟200毫秒判断是否重新加载新视频
if (_isChange) {
// 变了
//if (_oldIndex > currentIndex) {//还能判断用户是上滑还是下滑
// print("上滑");
//} else {
// print("下滑");
//}
_getVideoUrl(videoArr[currentIndex]);//请求新的视频资源
} else {
print("没有变化 ----");
}
});
},
child: PageView.builder(
// 使用PageView.builder动态加载页面,提高性能,可以无限滑动
scrollDirection: Axis.vertical,
itemCount: videoArr.length,
controller: _pageContronller,
onPageChanged: (index) {
this.setState(() {
this.currentIndex = index;
_isChange = index == _oldIndex ? false : true;
// 这里的_oldIndex在切换视频加载完后把当前的index 赋值给_oldIndex
});
},
itemBuilder: (context, index) {
return _currentNum == index
? Container(
color: Colors.black,
child: Center(
child: _canPlay
? Chewie(controller: _chewieController)
: Icon(Icons.play_circle_filled, color: Colors.white),
),
)
: Center(
child: Container(
height: 200,
width: 600,
color: Colors.grey,
child: Center(
child: Icon(Icons.play_circle_filled, size: 80),
),
),
);
},
),
),
最后实现了我的想法,视频随意滑动切换,
安卓手机点返回时回到桌面而不退出程序
之前一直有个需求就是在主页点返回时不退出程序,而是返回桌面,因为我这是个音乐app,肯定要实现音乐可以在后台播放,之前没有做处理的时候,点返回确实能回到桌面,音乐也没有暂停,但是再次点击进入app点音乐播放时候就会出现两首歌同时播放的现象,其实这是因为,你点返回的时候退出了程序,但是播放器并没有销毁,还在继续播放,当你在此进入程序点击播放时候就出现了新旧播放器同时存在的现象,这肯定不是我们想要的,所以今天搜搜实现方法,但是网上方法不多,大多数还是比较老的方法,说的不明不白的,对我造成不小的困扰,所以我要仔细说说这个问题,希望对小伙伴们有所帮助,
第一步
在这个目录下的这个文件(目前是2021年9月,我不敢保证我这个方法一直有有效)
在这个文件里写下面的代码(不要完全复制粘贴)
package com.example.videodemo2 //videodemo2是我的项目名字,大家不要困扰
import io.flutter.embedding.android.FlutterActivity
import androidx.annotation.NonNull
import io.flutter.embedding.engine.FlutterEngine
import io.flutter.plugin.common.MethodChannel
import io.flutter.plugins.GeneratedPluginRegistrant.*
class MainActivity: FlutterActivity() {
private val CHANNEL = "android/back/desktop"
override fun configureFlutterEngine(@NonNull flutterEngine: FlutterEngine) {
registerWith(flutterEngine);
MethodChannel(flutterEngine.dartExecutor.binaryMessenger, CHANNEL).setMethodCallHandler { methodCall, result ->
if (methodCall.method == "backDesktop") {
result.success(true)
moveTaskToBack(false)
}
}
}
}
第二步,
在lib目录新建android_back_desktop.dart
文件,并写上如下代码
import 'package:flutter/services.dart';
class AndroidBackDesktop {
//通讯名称,回到手机桌面
static const String CHANNEL = "android/back/desktop";
//设置回退到手机桌面事件
static const String eventBackDesktop = "backDesktop";
//设置回退到手机桌面方法
static Future<bool> backToDesktop() async {
final platform = MethodChannel(CHANNEL);
//通知安卓返回到手机桌面
try {
var res = await platform.invokeMethod(eventBackDesktop);
} on PlatformException catch (e) {
print("通信失败,设置回退到安卓手机桌面失败");
print(e.toString());
}
return Future.value(false);
}
}
第三步
在main.dart 入口文件 引入第二部的文件,在 MaterialApp
里使用
child: MaterialApp(
title: "我的音乐",
home: WillPopScope(
onWillPop: () async {
AndroidBackDesktop.backToDesktop(); // 这一行是关键代码
return false;
},
child: Tabs(),
),
initialRoute: '/',
onGenerateRoute: onGenerateRoute,
builder: EasyLoading.init(),
),
至此,在home页点击返回时候就直接返回系统桌面了,不会退出程序,音乐正常播放。
播放页实现歌词随播放进度滚动
之前的播放页大致实现了一下,只是简单的把歌词显示出来,并没有做任何效果,感觉太丑,正好要学学 ListWheelScrollView 的用法,
如果不做联动效果,和普通listview用法区别也不大,我的想法是要随播放进度,实时滚动,在网上搜了一下,页没有找到别人分享的成功案例,那就自己实现一遍吧,
第一步,要用事件控制列表的滚动,先给滚动列表加歌控制器
获取歌词的时候把数据处理一下生成时间数组和对应的歌词数组,
this.setState(() {
_lyrics = arrLyric; // 歌词数组
timeArr = _timeArr; // 对应的时间数组
});
ScrollController _controller = new ScrollController();
child: ListWheelScrollView(
controller: _controller, // 控制器
itemExtent: 30,// 每条列表项的高度
useMagnifier: true, // 选中项带放大镜效果
overAndUnderCenterOpacity: 0.2, // 未选中项透明度为0.2
magnification: 1.5, // 放大倍数
children: List.generate(
_lyrics.length, // 歌词列表length
(index) {
return Container(
child: Center(
child: Text(
"${_lyrics[index]}",
maxLines: 1,
)),
);
},
),
),
第二步,要在音乐播放器播放时间改变的时候,用事件广播,告诉控制器是否要滚动列表
- 实现事件广播,先定义一个播放进度改变的广播事件
class progressChange {
int currentTime = 0;
progressChange(cur) {
this.currentTime = cur;
}
}
- 播放进度改变的时候发广播(播放器在index.dart 页面),之前用provider实现全局共享播放时间实时变化
_audioPlayer.onAudioPositionChanged.listen((Duration p) {
// 数据里的时间格式 00:00:10.503000
var _t = 0;
var _cur = "$p".substring(2, 7);
var _m = int.parse("$_cur".substring(0, 2));
var _s = int.parse("$_cur".substring(3));
var _total = _m * 60 + _s;
audioProgress.setCurrentTime(_total * 1000); // provider 设置当前播放时间进度
eventbus.fire(new progressChange(_total)); // 播放进度改变发广播,告诉歌词滚动
});
第三步, 在歌词页监听广播,收到广播后执行自定义的 _timeChange 函数
@override
void initState() {
// TODO: implement initState
super.initState();
eventbus.on<progressChange>().listen((event) {
_timeChange(event.currentTime);
});
}
ScrollController _controller = new ScrollController(); // 第一步定义的控制器
void _timeChange(currentTime) {
int _index = this.timeArr.indexOf(currentTime);
if (_index != -1) {
// _controller 控制器执行动画 让歌词列表滚动
_controller.animateTo(_index * 30,
duration: Duration(milliseconds: 200), curve: Curves.linear);
}
}
至此,歌词跟随歌曲播放实时滚动功能就实现了
修改app图标
按要求把想用的图标做成5种不同的尺寸,放在原来的位置,就行了
这几个位置里的图片都要换
重构短视频/mv 可滑动切换的播放页
之前的视频播放用的是 chewie 这个视频播放插件
video_player: ^2.2.5
chewie: ^1.2.2
它依赖于 video_player 这个flutter官方的视频播放插件,由于官方的插件 只有功能,UI需要自己写,所以网上好多基于官方插件的封装,比如增加 播放暂停按钮,音量调节控件,播放进度条。。等等功能,如果你只是个视频播放功能,用这个插件挺方便的,但我的需求是类似抖音快手等短视频app那样可以随意滑动切换,还有很多自定义的功能,比如监听播放进度还剩5秒钟时候提示5秒后跳转下一个视频,播放完毕后自动跳转下一个,用这个插件就有点问题了,以前用的 chewie 播放器滑动切换之后,播放器无法销毁干净,回到桌面再回来,莫名其妙的在后台播放着刚才播放的视频,所以使用官方的播放器重构了一下,完善了一下布局,给以后增加其他功能预留位置,增加几个按钮(并没有加真实功能,先做个样子)
给滑动切换的短视频 加播放进度条和拖动快进功能
目前的效果
过程倒也挺顺利,官方的视频播放插件很给力,flutter源码也很给力,源码既是文档,给flutter官方一个大大的赞,
增加视频播放时屏幕常亮功能
播放视频的时候我们要保持手机屏幕常亮,使用到了 wakelock 插件,使用方法页挺方便,在播放页 initState 生命周期里开启屏幕常亮
import 'package:wakelock/wakelock.dart';
@override
void initState() {
super.initState();
...
...
...
Wakelock.enable();
}
在离开页面的时候关闭手机常亮
@override
void dispose() {
super.dispose();
...
...
...
Wakelock.disable();
}
转载自:https://juejin.cn/post/7012096689594974244