女朋友想要听歌,我反手用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()));
        }),
  );
}

本文结束了,看到这里的兄弟们给点点赞吧,又是掉头发的一天😭

卡通风格的音乐播放器

炫酷波浪风格的音乐播放器

源代码在这里哦

  • 8
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 4
    评论
### 回答1: 好的,以下是一个简单的商品列表页的 Flutter 代码: ``` import 'package:flutter/material.dart'; class ProductListPage extends StatefulWidget { @override _ProductListPageState createState() => _ProductListPageState(); } class _ProductListPageState extends State<ProductListPage> { List<String> _products = [ 'Product 1', 'Product 2', 'Product 3', 'Product 4', 'Product 5', ]; @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text('Product List'), ), body: ListView.builder( itemCount: _products.length, itemBuilder: (BuildContext context, int index) { return ListTile( leading: Icon(Icons.shopping_cart), title: Text(_products[index]), trailing: Icon(Icons.arrow_forward_ios), onTap: () { // Navigate to product detail page }, ); }, ), ); } } ``` 这个代码创建了一个 `ProductListPage` 类,继承自 `StatefulWidget`。在 `_ProductListPageState` 类中,我们创建了一个 `_products` 列表,其中包含了一些虚拟的产品名称。在 `build` 方法中,我们使用 `ListView.builder` 创建了一个滚动列表,并使用 `ListTile` 来渲染每个产品的名称和箭头图标。当用户点击任何一个产品时,我们可以通过导航到另一个页面来显示其详细信息。 ### 回答2: 当然可以!使用Flutter编写一个商品列表页非常简单。 首先,你需要在项目中导入flutter的依赖库。在项目的`pubspec.yaml`文件中添加如下代码: ```yaml dependencies: flutter: sdk: flutter cupertino_icons: ^1.0.2 ``` 接下来,你需要创建一个新的Flutter页面,可以命名为`product_list.dart`。在这个页面中,你可以定义一个`StatefulWidget`,用于展示商品列表。代码如下所示: ```dart import 'package:flutter/material.dart'; class ProductListPage extends StatefulWidget { @override _ProductListPageState createState() => _ProductListPageState(); } class _ProductListPageState extends State<ProductListPage> { final List<String> productList = [ '商品1', '商品2', '商品3', '商品4', ]; @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text('商品列表'), ), body: ListView.builder( itemCount: productList.length, itemBuilder: (context, index) { return ListTile( title: Text(productList[index]), ); }, ), ); } } ``` 在这个例子中,我们通过`productList`列表存储了一些商品的名称。然后,我们使用`ListView.builder`构建了一个商品列表视图,列表的每一项是一个`ListTile`,显示了对应商品的名称。 最后,你可以在项目的入口文件(一般是`main.dart`)中导航到这个商品列表页。例如: ```dart import 'package:flutter/material.dart'; import 'package:your_app_name/product_list.dart'; void main() { runApp(MyApp()); } class MyApp extends StatelessWidget { @override Widget build(BuildContext context) { return MaterialApp( title: 'Your App Name', theme: ThemeData( primarySwatch: Colors.blue, ), home: ProductListPage(), // 导航到商品列表页 ); } } ``` 这样,当你的应用程序启动时,就会显示一个简单的商品列表页。这只是一个基础的例子,你可以根据自己的需求进行进一步的修改和扩展。 希望这个回答对你有帮助!如果有任何问题,欢迎随时问我。 ### 回答3: 当然可以!Flutter是一个跨平台的移动应用开发框架,可以用Dart语言编写,非常适合构建漂亮的用户界面。以下是我用Flutter写的一个简单的商品列表页。 ```dart import 'package:flutter/material.dart'; class ProductListPage extends StatelessWidget { final List<String> products = [ '商品1', '商品2', '商品3', '商品4', '商品5', '商品6', ]; @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text('商品列表'), ), body: ListView.builder( itemCount: products.length, itemBuilder: (BuildContext context, int index) { return ListTile( leading: Icon(Icons.shopping_cart), title: Text(products[index]), onTap: () { // 处理商品点击事件 showDialog( context: context, builder: (context) { return AlertDialog( title: Text('提示'), content: Text('您点击了${products[index]}'), actions: [ FlatButton( child: Text('关闭'), onPressed: () { Navigator.of(context).pop(); }, ), ], ); }, ); }, ); }, ), ); } } ``` 这个页面使用了Scaffold作为页面的基本布局,AppBar作为页面的标题栏,在body中利用ListView.builder来展示商品列表。每个商品都显示了一个购物车的图标和商品名称,并且可以通过点击弹出一个提示框显示商品的详细信息。 这只是一个简单的示例,你可以根据需要进一步扩展和美化这个商品列表页,比如添加商品图片、价格等信息,并实现更多的交互功能。希望对你有帮助!

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

编程的平行世界

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值