阅读 160

女朋友想要听歌,我反手用Flutter做了2个音乐播放器,给她拿捏了

前言:又是为大家贡献自己头发的一天,这几天在网上搜了搜,发现Flutter的音乐播放器比较少(也还是有几款比较好的,文章最后推荐给大家????),要么就是代码不全无法运行,要么就是UI没法看,于是我就站了出来,给大家带来了这些播放器,所以,兄弟们给个赞吧,头发没几根了????

源码在文章最后,是兄弟就来运行看看

话不多说,先上效果图(有图有真相啊):

有和我一样喜欢Jay的嘛

效果图1.gif 效果图2.gif

阅读本文的注意点:

  • 使用的插件:

    assets_audio_player: ^2.0.14 //用于音乐的播放 flutter_swiper: any //用于第一张效果图的左右切换效果,如果只要实现第二张的话就不需要啦~ 复制代码

  • 文章只展示重要代码,完整源码在文章的最后

正文:

1.带有波浪式动画的播放/暂停音乐按钮

按钮效果图.gif

当点击播放时,将多个不同颜色的椭圆形堆叠在一起,然后进行旋转。

  • 所以根据效果需求我们实现需要3个步骤:

1.创建一个无状态的背景椭圆形

2.使用将多个背景圆形存放于Stack中,以及点击处理

3.添加缩放和旋转动画AnimationController来控制背景圆形

  • 定义需要传入的参数

    double _rotation = 0; double _scale = 0.85; 复制代码

  • 创建一个无状态的Blob,用于接收传进来的color和旋转以及缩放值

    封装处理一下:

    //无状态的 class Blob extends StatelessWidget {   final Color color; //传入的颜色   final double rotation; //传入旋转的角度   final double scale; //传入缩放的比例   const Blob({this.color, this.rotation = 0, this.scale = 1});   @override   Widget build(BuildContext context) {     return Transform.scale(       scale: scale,       child: Transform.rotate(         angle: rotation,         child: Container(             ///背景的椭圆形卡片           decoration: BoxDecoration(             color: color,             borderRadius: BorderRadius.only(               topLeft: Radius.circular(150),               topRight: Radius.circular(240),               bottomLeft: Radius.circular(220),               bottomRight: Radius.circular(180),             ),           ),         ),       ),     );   } } 复制代码

  • 使用将多个背景椭圆形存放于Stack中

    首先,我们定义状态,这是一个:一个bool isPlaying按钮,播放/暂停,第二部分是rotationscale值适用于我们的Blob秒。稍后,我们将对这些旋转和缩放值进行动画处理。

    bool isPlaying; @override   void initState() {     isPlaying = widget.initialIsPlaying;     super.initState();   } return Container( //控制播放按钮大小   width: widget.iconSize + 30,   height: widget.iconSize + 30,   child: Stack(     alignment: Alignment.center,     children: [       if (_showWaves) ...[       //创建3个椭圆形进行叠加         Blob(color: Color(0xff0092ff), scale: _scale, rotation: _rotation),         Blob(             color: Color(0xff4ac7b7),             scale: _scale,             rotation: _rotation * 2 - 30),         Blob(             color: Color(0xffa4a6f6),             scale: _scale,             rotation: _rotation * 3 - 45),       ],       Container(             constraints: BoxConstraints.expand(),             child: IconButton(               icon: isPlaying ? widget.pauseIcon : widget.playIcon,               onPressed: _onToggle,             ),             decoration: BoxDecoration(               shape: BoxShape.circle,               color: Colors.white,             ),           ),     ],   ), ); 复制代码

  • 动画状态控制处理

    在 flutter 中添加动画常用的情况是创建一个AnimationController,需要SingleTickerProviderStateMixinmixin(或者TickerProviderStateMixin用于使用多个控制器)的对象,addListenersetState在有状态的小部件上执行导致重建。

    首先我们先给小部件添加两个AnimationController,然后添加两个常量,它们是两个持续时间,每个控制器一个。我们会将它们标记static const因为它们永远不会改变。

    static const _kToggleDuration = Duration(milliseconds: 300); static const _kRotationDuration = Duration(seconds: 5); AnimationController _rotationController; AnimationController _scaleController; 复制代码

    我们需要在initState方法中初始化控制器。因为是要一直旋转下去,所以我们将使用我们的repeat方法,_rotationController它也将立即启动我们的动画。该_scaleController是相似的,但只能在按钮按下运行。

    @override void initState() {   isPlaying = widget.initialIsPlaying;   _rotationController =       AnimationController(vsync: this, duration: _kRotationDuration)         ..addListener(() => setState(_updateRotation))         ..repeat();   _scaleController =       AnimationController(vsync: this, duration: _kToggleDuration)         ..addListener(() => setState(_updateScale));   super.initState(); } 复制代码

    为了动画更加的流畅,我还定义了两个辅助方法来更新旋转和缩放。

    void _updateRotation() => _rotation = _rotationController.value * 2 * pi; void _updateScale() => _scale = (_scaleController.value * 0.2) + 0.85; 复制代码

  • 触发动画,按钮点击时启动动画

    void _onToggle() {     setState(() {       isPlaying = !isPlaying;       _scale = _scale == 1 ? .85 : 1;     });     widget.onPressed();  } 复制代码

这样按钮效果就完成啦[灵光一现]

2.轮播图类型的播放

  • 分析:

    1.格式化时间处理

    2.需要实现音乐播放功能

    3.左右切换歌曲功能

    4.进度条定位处理

  • 格式化时间处理

    我们会获取到两个参数,一个是歌曲当前播放的时间,一个是歌曲的总时间

时间格式化处理.png

我们需要对其进行格式化

该方法类似于求整数和求其余数

minuteString获取到当前的分钟

secondString获取到当前的秒数

String transformString(int seconds) {   String minuteString =       '${(seconds / 60).floor() < 10 ? 0 : ''}${(seconds / 60).floor()}';   String secondString = '${seconds % 60 < 10 ? 0 : ''}${seconds % 60}';   return '$minuteString:$secondString'; } 复制代码

使用:

当前播放时间:

Text(   transformString(realtimePlayingInfos.currentPosition.inSeconds),   style: TextStyle(       color: Colors.white, fontWeight: FontWeight.bold, fontSize: 17), ), 复制代码

歌曲总时间也一样的写法,只是参数不同,详细看源代码哈~

  • 音乐播放功能实现

    这个是一个播放器最重要的功能

    本文使用assets_audio_player,大家有其他需求的话也可以使用其他的插件,原理都差不多

    我们先定义静态资源:

    //音乐名和图片名不能为中文!!中文是无法识别的 List<Audio> audioList = [   Audio('assets/daphneOdera.mp3',       metas: Metas(           title: '七里香',           artist: 'Jay',           image: MetasImage.asset('assets/daphneOdera.jpg'))),  ...其他的歌曲 ]; 复制代码

    对于播放器我们需要一个控制器

    final AssetsAudioPlayer audioPlayer = AssetsAudioPlayer(); 复制代码

    第一步:我们需要对其进行初始化:

    @override   void initState() {     super.initState();     setupPlaylist();   }    void setupPlaylist() async { //初始化播放器,对静态资源也进行初始化   audioPlayer.open(Playlist(audios: audioList),        autoStart: false, loopMode: LoopMode.playlist); } 复制代码

    第二步:进行播放以及暂停控制

    在此次我们需要用到插件的RealtimePlayingInfos 参数,用于获取静态资源

    Widget playBar(RealtimePlayingInfos realtimePlayingInfos) {   return PlayButton(       //PlayButton就是前面封装的播放按钮                 onPressed: () => audioPlayer.playOrPause(), //播放或暂停                 initialIsPlaying:false,                 iconColor: highlightColor,                 iconSize:screenHeight * 0.06,               ); } 复制代码

    这样简单的播放功能就完成了,这个插件封装的还是很简单的

  • 左右切换歌曲功能

    这个也是这个播放器最难的点,因为要在处理切换的时候,还要切歌,判断当前是哪一首歌

    轮播图实现,这里使用了插件,如果有特殊需求的话,大家也可以使用PageView自己写

    //传入RealtimePlayingInfos参数 Widget swiper(RealtimePlayingInfos realtimePlayingInfos) {     //定义轮播图   return Container(     width: screenWidth,     height: screenHeight * 0.45,     child: Swiper(       controller: swiperController,       itemCount: audioList.length,       itemBuilder: (context, index) {         return ClipRRect(           borderRadius: BorderRadius.circular(50.0),           child: Image.asset(               //获取歌曲封面             audioList[index].metas.image.path,             fit: BoxFit.cover,           ),         );       },       onIndexChanged: (newIndex) async {           //左右切换时,传入新的参数,用于定位当前播放歌曲         audioPlayer.playlistPlayAtIndex(newIndex);       },       viewportFraction: 0.75,       scale: 0.8,     ),   ); } 复制代码

  • 进度条定位处理

进度条.png

通过当前的时间与总时长进行计算,具体的参数大家可以参考SliderTheme,在这里就是传入了两个参数。

audioPlayer.seek()方法用于定位到拖动位置

Widget slider(RealtimePlayingInfos realtimePlayingInfos) {   return SliderTheme(       data: SliderThemeData(           thumbColor: highlightColor,           thumbShape: RoundSliderThumbShape(enabledThumbRadius: 1),           activeTrackColor: highlightColor,           inactiveTrackColor: Colors.grey[800],           overlayColor: Colors.transparent),       child: Slider.adaptive(           value: realtimePlayingInfos.currentPosition.inSeconds.toDouble(),           max: realtimePlayingInfos.duration.inSeconds.toDouble(),           min: 0,           onChanged: (value) {             audioPlayer.seek(Duration(seconds: value.toInt()));           })); } 复制代码

  • 左右按钮切换歌曲处理

    swiperController.previous() 上一首歌曲

    swiperController.next() 下一首歌曲

    IconButton(   icon: Icon(Icons.skip_previous_rounded),   onPressed: () => swiperController.previous(),   iconSize: screenHeight * 0.06,   color: Colors.white,   splashColor: Colors.transparent,   highlightColor: Colors.transparent, ), 复制代码

这样就把轮播图效果的播放器完成啦

收到.png

第二张效果主要是布局上的一些差别,功能是没有什么区别的,主要是进度条的样式有些不同,这里简单分析一下:

主要是在拖动时有个小点,代码区别不大。

Widget slider(RealtimePlayingInfos realtimePlayingInfos) {   return SliderTheme(     data: SliderThemeData(         trackShape: CustomTrackShape(),         thumbShape: RoundSliderThumbShape(enabledThumbRadius: 8)),     child: Slider.adaptive(         value: realtimePlayingInfos.currentPosition.inSeconds.toDouble(),         max: realtimePlayingInfos.duration.inSeconds.toDouble(),         activeColor: Color(0xffe3eb6b),         inactiveColor: Colors.grey[850],         onChanged: (value) {           audioPlayer.seek(Duration(seconds: value.toInt()));         }),   ); }


作者:阿Tya
链接:https://juejin.cn/post/7017717932704694286


文章分类
后端
版权声明:本站是系统测试站点,无实际运营。本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 XXXXXXo@163.com 举报,一经查实,本站将立刻删除。
相关推荐