flutter 想要一款简洁美观的多端本地音乐播放器该怎么办?

flutter 想要一款简洁美观的多端本地音乐播放器该怎么办?

本地音乐播放器在现在来说好像没什么人在用了吧,大家都直接使用某易,某Q来播放音乐,这些软件虽然很强大,但是不能播放本地保存的会员音乐,即使你本地有周杰伦的mp3音乐,在某Q播放器里面好像也不能播放,并且这些软件里面,丑陋的广告满天飞,让人头大。于是,我决定自己开发一款本地音乐播放器,界面要简洁、大气、美观,具备基本的本地音乐播放功能,一套代码实现跨平台多端运行。

渐变背景颜色实现

首先实现一个渐变色背景,使用Container组件的decoration属性,设置BoxDecoration来实现渐变色效果,在colors里面放上你喜欢的颜色。我实现的是比较粉嫩的背景颜色。

dart 复制代码
Container(
    alignment: Alignment.center,
    decoration: const BoxDecoration(
      gradient: LinearGradient(
        begin: Alignment.topLeft,
        colors: [
          Color.fromARGB(255, 252, 248, 228),
          Color.fromARGB(255, 255, 221, 233),
     ],
   ),
 ),
),

底部导航按钮、切换页面

我们给播放器实现两个界面,第一个界面,也就是首页用来选择本地音乐存储路径,第二个界面展示播放界面。通过bottomNavigationBar组件实现底部导航功能栏,点击按钮切换界面。使用sizebox组件来控制显示区域大小,配合Row横向布局组件包裹IconButton按钮,点击按钮更改index页面索引,触发页面切换效果。

dart 复制代码
  bottomNavigationBar: SizedBox(
      height: 50,
      child: Row(
        mainAxisAlignment: MainAxisAlignment.center,
        children: [
          Expanded(
            child: IconButton(
                onPressed: () {
                  index.value = 0;
                },
                icon: const Icon(Icons.home)),
          ),
          Expanded(
            child: IconButton(
                onPressed: () {
                  index.value = 1;
                },
                icon: const Icon(Icons.audiotrack)),
          ),
        ],
      ),
    ),

使用ValueListenableBuilder组件监听index页面索引,在点击按钮时index更改,触发pages显示页面重绘,实现点击切换页面效果。

dart 复制代码
var pages = [
  const MyApp1(),
  const MyApp2(),
];

var index = ValueNotifier<int>(0);

ValueListenableBuilder(
    builder: (BuildContext context, value, Widget? child) {
        return pages[index.value];
      },
    valueListenable: index,
),

首页布局、本地音乐路径选取

主页放置一个按钮用来指定本地音乐文件路径,音乐文件路径选择我们使用file_picker插件,这个插件可以实现跨平台的文件选择功能,支持单个、多个文件选择,也可以指定路径,这个插件在前边的文章介绍过,大家有兴趣可以翻翻看看。

通过file_picker插件将指定的本地音乐文件路径存入musiclist中,当中播放列表,后边播放的时候要用到。只放置一个文件选择按钮有点单调,所以再放一段文字填充一下吧。

dart 复制代码
List<String> musiclist = [""];

class MyApp1 extends StatelessWidget {
  const MyApp1({super.key});

  @override
  Widget build(BuildContext context) {
    return SafeArea(
      child: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            const Text(
              "Music player",
              style: TextStyle(fontSize: 50),
            ),
            const Padding(padding: EdgeInsets.all(12)),
            TextButton(
              onPressed: () async {
                FilePickerResult? result =
                    await FilePicker.platform.pickFiles(allowMultiple: true);
                if (result != null) {
                  for (var i = 0; i < result.files.length; i++) {
                    debugPrint(result.files[i].path);
                    musiclist.add(result.files[i].path.toString());
                  }
                } else {
                  // User canceled the picker
                }
              },
              child: const Text("Choose Music Files"),
            ),
          ],
        ),
      ),
    );
  }
}

audioplayers插件

audioplayers插件可同时播放多个音频文件,适用于Android,iOS,Linux,macOS,Windows和Web,使用audioplayers插件可以实现音乐播放、暂停、停止、下一曲、上一曲等基本操作,也可以获取当前播放进度。播放的音乐文件可以是网络上资源、也可以是本地资源,很是方便。我们还可以监控播放进度和播放状态。

audioplayers插件初始化:AudioPlayer audioPlayer = AudioPlayer();  音乐暂停:audioPlayer.pause();  音乐停止:audioPlayer.stop();  音乐播放:audioPlayer.play();

我们使用musiclist存储要播放的音乐路径,使用DeviceFileSource可以设置播放本地音乐文件,只需要指定音乐路径即可。

dart 复制代码
AudioPlayer audioPlayer = AudioPlayer();

Future<void> pause() async {
  audioPlayer.pause();
}

Future<void> stop() async {
  audioPlayer.stop();
}

Future<void> playlocalfile() async {
  audioPlayer.play(
    DeviceFileSource(
      musiclist[musicindex],
    ),
  );
}

音乐播放界面布局

接着来实现播放页面。播放界面由上到下依次放置一个小汽车动画、音浪动画,这两个动画在音乐播放时会动起来,音乐暂停会停止播放。进度条也是必须具备的,随着音乐的播放指示进度。接着就是音乐曲目展示区域,显示正在播放的歌曲名。最后就是播放按键的实现了,具备基本的上一曲、下一曲、播放、暂停功能。

小汽车动画和波浪动画

动画部分我们依旧使用lottie来实现,前边的文章也有介绍过lottie动画,大家有兴趣可以翻翻我的主页。先去lottie主页找几个免费的、适合的动画下载下来,我找了一个小汽车动画、音浪动画、进度条动画,看起来还不错。

因为要控制动画的播放和暂停,所以会更改组件的状态,所以我们要用StatefulWidget有状态组件。在StatefulWidget有状态组件初始化时设置动画控制器topanimaController,后边都通过topanimaController来控制动画的播放。

dart 复制代码
class TopAnimLottie extends StatefulWidget {
  const TopAnimLottie({super.key});

  @override
  State<TopAnimLottie> createState() => _TopAnimLottieState();
}

class _TopAnimLottieState extends State<TopAnimLottie>
    with TickerProviderStateMixin {
  @override
  void initState() {
    super.initState();
    topanimaController = AnimationController(vsync: this);
  }

  @override
  void dispose() {
    topanimaController.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Container(
      child: Lottie.asset(
        'assets/animation_lmdkfr6e.json',
        width: 150,
        height: 120,
        controller: topanimaController,
        onLoaded: (composition) {
          setState(() {
            topanimaController.duration = composition.duration;
          });
        },
      ),
    );
  }
}

进度条

因为进度条动画的播放要随着音乐进度更改,同时要能进能退,所以进度条动画与上边的小汽车动画在实现上略有不同,但依旧都是lottie动画。  在有状态组件中放置一个Timer.periodic定时器,每一秒计算一下播放进度,根据播放进度控制进度条动画的位置。使用audioPlayer.getCurrentPosition可以获得当前播放进度,使用audioPlayer.getDuration()可以获得全部播放时长。计算出播放进度后,利用proresscontroller.animateTo(left);控制进度条播放。

dart 复制代码
import 'dart:async';

import 'package:flutter/material.dart';
import 'package:lottie/lottie.dart';

import 'audioplay.dart';

late AnimationController proresscontroller;

class Progressbar extends StatefulWidget {
  const Progressbar({super.key});

  @override
  State<Progressbar> createState() => _ProgressbarState();
}

double left = 0;

class _ProgressbarState extends State<Progressbar>
    with TickerProviderStateMixin {
  @override
  void initState() {
    super.initState();
    proresscontroller = AnimationController(vsync: this);
    var timer = Timer.periodic(const Duration(seconds: 1), (timer) async {
      var cu = await audioPlayer.getCurrentPosition();
      var all = await audioPlayer.getDuration();
      var culist = cu.toString().split(":");
      var alllist = all.toString().split(":");
      var cud = double.parse(culist[1]) * 60 + double.parse(culist[2]);
      var alld = double.parse(alllist[1]) * 60 + double.parse(alllist[2]);
      left = cud / alld;
      proresscontroller.animateTo(left);
    });
  }

  @override
  void dispose() {
    proresscontroller.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Center(
      child: Lottie.asset(
        'assets/91938-green-progress-bar-without-popover.json',
        width: 200,
        controller: proresscontroller,
        onLoaded: (composition) {
          setState(() {
            proresscontroller.duration = composition.duration;
          });
        },
      ),
    );
  }
}

播放按键

播放按钮使用IconButton组件,在相应的上一曲、下一曲、播放、暂停按钮中实现对应的操作逻辑。点击上一曲或者下一曲按钮时,在前边定义的musiclist列表中切换要播放的音乐文件路径,实现歌曲切换。同时控制小汽车、音浪、进度条动画的播放状态。

播放或者暂停按钮使用flag来进行一个判断,根据播放状态来控制要展示的按钮图标,同时控制动画的播放和暂停。

dart 复制代码
var flag = true;

class MyApp2 extends StatefulWidget {
  const MyApp2({super.key});

  @override
  State<MyApp2> createState() => _MyApp2State();
}

class _MyApp2State extends State<MyApp2> {
  @override
  Widget build(BuildContext context) {
    return Column(
      mainAxisAlignment: MainAxisAlignment.center,
      children: [
        const Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            TopAnimLottie(),
            DownAnimLottie(),
            Padding(
              padding: const EdgeInsets.fromLTRB(0, 12, 0, 8),
              child: Progressbar(),
            ),
          ],
        ),
        Padding(
          padding: const EdgeInsets.all(10.0),
          child: Text(
            musiclist[musicindex]
                .split("\\")[musiclist[musicindex].split("\\").length - 1],
            style: const TextStyle(fontSize: 18),
          ),
        ),
        Row(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            IconButton(
              onPressed: () async {
                if (musicindex > 0) {
                  musicindex = musicindex - 1;
                }
                flag = false;
                await stop();
                proresscontroller.reset();
                playlocalfile();
                downanimaController.reset();
                downanimaController.repeat();
                topanimaController.reset();
                topanimaController.repeat();
                setState(() {});
              },
              icon: const Icon(Icons.skip_previous),
            ),
            Container(
              child: flag
                  ? IconButton(
                      onPressed: () {
                        flag = false;
                        setState(() {});
                        playlocalfile();
                        downanimaController.reset();
                        downanimaController.repeat();
                        topanimaController.reset();
                        topanimaController.repeat();
                      },
                      icon: const Icon(Icons.play_arrow))
                  : IconButton(
                      onPressed: () {
                        flag = true;
                        pause();
                        setState(() {});
                        topanimaController.stop();
                        downanimaController.stop();
                      },
                      icon: const Icon(Icons.pause)),
            ),
            IconButton(
              onPressed: () async {
                if (musicindex + 1 < musiclist.length) {
                  musicindex = musicindex + 1;
                }
                flag = false;
                proresscontroller.reset();
                await stop();
                playlocalfile();
                downanimaController.reset();
                downanimaController.repeat();
                topanimaController.reset();
                topanimaController.repeat();
                setState(() {});
              },
              icon: const Icon(Icons.skip_next),
            )
          ],
        ),
      ],
    );
  }
}

总结

以上就是我自己开发的音乐播放app啦,只是实现了本地的音乐播放功能,简单的加了一些动画,能满足最基本的播放音乐的需求,当然,还有很多功能没有实现,距离商业音乐播放器还有超级大的差距,后边如果有时间,再继续完善吧。大家可以多多提提建议。后边有时间把代码公开一下,有兴趣继续开发的小伙伴,我们可以一起合作开发哦。

相关推荐
拾光拾趣录3 分钟前
CSS 深入解析:提升网页样式技巧与常见问题解决方案
前端·css
莫空00004 分钟前
深入理解JavaScript属性描述符:从数据属性到存取器属性
前端·面试
guojl4 分钟前
深度剖析Kafka读写机制
前端
FogLetter5 分钟前
图片懒加载:让网页飞起来的魔法技巧 ✨
前端·javascript·css
Mxuan6 分钟前
vscode webview 插件开发(精装篇)
前端
Mxuan7 分钟前
vscode webview 插件开发(交付篇)
前端
Mxuan8 分钟前
vscode 插件与 electron 应用跳转网页进行登录的实践
前端
拾光拾趣录8 分钟前
JavaScript 加载对浏览器渲染的影响
前端·javascript·浏览器
Codebee8 分钟前
OneCode图表配置速查手册
大数据·前端·数据可视化
然我9 分钟前
React 开发通关指南:用 HTML 的思维写 JS🚀🚀
前端·react.js·html