• Flutter仿网易云音乐:播放界面


    写在前头

    本来是要做一个仿网易云音乐的flutter项目,但是因为最近事情比较多,项目周期跨度会比较长,因此分几个步骤来完成。这是仿网易云音乐项目系列文章的第一篇。没有完全照搬网易云音乐的UI,借鉴了其中的黑胶唱机动画。

    先贴上项目地址 github.com/KinsomyJS/f…

    初步效果图

    思路

    这个界面实现起来其实是比较简单的,大致分为如下几个部分:

    • 1.背景的高斯模糊效果
    • 2.黑胶唱头的旋转动画
    • 3.黑胶唱片的旋转动画
    • 4.下部控制器和进度条部分

    我们一个个来说实现过程。

    实践

    整个界面是一个堆叠视图,最下面是一个背景图片,上面覆盖一层高斯模糊半透明遮罩,再上层是title,黑胶唱机和控制器。

    1. 背景高斯模糊

    首先使用stack组件用来包裹堆叠视图,在里面有两个container,第一个是背景网络图片,第二个就是一个BackdropFilter

    Stack(
          children: <Widget>[
            new Container(
              decoration: new BoxDecoration(
                image: new DecorationImage(
                  image: new NetworkImage(coverArt),
                  fit: BoxFit.cover,
                  colorFilter: new ColorFilter.mode(
                    Colors.black54,
                    BlendMode.overlay,
                  ),
                ),
              ),
            ),
            new Container(
                child: new BackdropFilter(
              filter: ImageFilter.blur(sigmaX: 10.0, sigmaY: 10.0),
              child: Opacity(
                opacity: 0.6,
                child: new Container(
                  decoration: new BoxDecoration(
                    color: Colors.grey.shade900,
                  ),
                ),
              ),
            )),
            
            ...
        ]
    复制代码

    这里的高斯模糊sigmaX和sigmaY的值选择了10,然后透明度为0.6,颜色为grey.shade900。

    2.黑胶唱头的旋转动画

    关于动画的知识这里就不做详细介绍了,可以参考官方文档传送门

    自定义动画组件在needle_anim.dart文件里。 这里将动画和组件解耦,分别定义了动画过程类PivotTransition,顾名思义围绕一个支点旋转,继承自AnimatedWidget

    支点定在child组件的topcenter位置。 注意turns不能为空,需要根据turns的值计算旋转绕过的周长,围绕Z轴旋转。

    class PivotTransition extends AnimatedWidget {
      /// 创建旋转变换
      /// turns不能为空.
      PivotTransition({
        Key key,
        this.alignment: FractionalOffset.topCenter,
        @required Animation<double> turns,
        this.child,
      }) : super(key: key, listenable: turns);
    
      /// The animation that controls the rotation of the child.
      /// If the current value of the turns animation is v, the child will be
      /// rotated v * 2 * pi radians before being painted.
      Animation<double> get turns => listenable;
    
      /// The pivot point to rotate around.
      final FractionalOffset alignment;
    
      /// The widget below this widget in the tree.
      final Widget child;
    
      @override
      Widget build(BuildContext context) {
        final double turnsValue = turns.value;
        final Matrix4 transform = new Matrix4.rotationZ(turnsValue * pi * 2.0);
        return new Transform(
          transform: transform,
          alignment: alignment,
          child: child,
        );
      }
    }
    复制代码

    接下来就是自定义黑胶唱头组件。

    final _rotateTween = new Tween<double>(begin: -0.15, end: 0.0);
    new Container(
      child: new PivotTransition(
        turns: _rotateTween.animate(controller_needle),
        alignment: FractionalOffset.topLeft,
        child: new Container(
           100.0,
          child: new Image.asset("images/play_needle.png"),
        ),
      ),
    ),
    
    复制代码

    将png图片包裹在container内作为child参数传递给PivotTransition

    外部使用的时候传入一个Tween,起始位置为-0.15 ~ 0.0。

    3.黑胶唱片的旋转动画

    这部分代码在record_anim.dart文件内。使用了package:flutter/animation.dart提供的RotationTransition做旋转,很简单。

    class RotateRecord extends AnimatedWidget {
      RotateRecord({Key key, Animation<double> animation})
          : super(key: key, listenable: animation);
    
      Widget build(BuildContext context) {
        final Animation<double> animation = listenable;
        return new Container(
          margin: new EdgeInsets.symmetric(vertical: 10.0),
          height: 250.0,
           250.0,
          child: new RotationTransition(
              turns: animation,
              child: new Container(
                decoration: BoxDecoration(
                  shape: BoxShape.circle,
                  image: DecorationImage(
                    image: NetworkImage(
                        "https://images-na.ssl-images-amazon.com/images/I/51inO4DBH0L._SS500.jpg"),
                  ),
                ),
              )),
        );
      }
    }
    复制代码

    接着自定义旋转动画的控制逻辑。旋转一圈用时十五秒钟,速度为线性匀速,同时会重复旋转动画。

    controller_record = new AnimationController(
            duration: const Duration(milliseconds: 15000), vsync: this);
    animation_record =
            new CurvedAnimation(parent: controller_record, curve: Curves.linear);
    animation_record.addStatusListener((status) {
      if (status == AnimationStatus.completed) {
        controller_record.repeat();
      } else if (status == AnimationStatus.dismissed) {
        controller_record.forward();
      }
    });
    复制代码

    4.下部控制器和进度条部分

    播放流媒体音频使用了三方组件audioplayers,具体代码在player_page.dart文件内,封装了一个player组件,接受了一系列参数包括音频路径,播放操作回调等。该组件支持本地资源和网络资源,这里用网络音频资源做demo。

    const Player(
          {@required this.audioUrl,
          @required this.onCompleted,
          @required this.onError,
          @required this.onNext,
          @required this.onPrevious,
          this.key,
          this.volume: 1.0,
          this.onPlaying,
          this.color: Colors.white,
          this.isLocal: false});
    复制代码

    在initState方法里初始化AudioPlayer对象。".."是dart的级联操作符。

     audioPlayer = new AudioPlayer();
        audioPlayer
          ..completionHandler = widget.onCompleted
          ..errorHandler = widget.onError
          ..durationHandler = ((duration) {
            setState(() {
              this.duration = duration;
    
              if (position != null) {
                this.sliderValue = (position.inSeconds / duration.inSeconds);
              }
            });
          })
          ..positionHandler = ((position) {
            setState(() {
              this.position = position;
    
              if (duration != null) {
                this.sliderValue = (position.inSeconds / duration.inSeconds);
              }
            });
          });
    复制代码

    开始播放代码

    audioPlayer.play(
        widget.audioUrl,
        isLocal: widget.isLocal,
        volume: widget.volume,
      );
    复制代码

    开始播放后,durationHandler会回调音频总时长,positionHandler会回调播放进度,两个回调都返回一个Duration对象。根据这两个duration对象可以计算机播放进度的百分比,这里使用Slider组件做进度条。

    new Slider(
        onChanged: (newValue) {
          if (duration != null) {
            int seconds = (duration.inSeconds * newValue).round();
            print("audioPlayer.seek: $seconds");
            audioPlayer.seek(new Duration(seconds: seconds));
          }
        },
        value: sliderValue ?? 0.0,
        activeColor: widget.color,
      ),
    复制代码

    总结

    整体实现是非常简单的,只要对flutter的组件有所了解就能很快写出来,后面还会加入歌词滚动功能来丰富界面。

    具体项目可以到 github.com/KinsomyJS/f… 查看,也欢迎star持续关注。

    参考资料

    1. 官方文档
    2. pub: audioplayers
  • 相关阅读:
    shell脚本编程练习
    linux中()、[]、{}、(())、[[]]等各种括号的使用
    Linux Shell 变量自加
    while read line [linux] shell 学习
    踢出某正在访问的用户||永久禁止某IP访问
    linux设置 自定义脚本开机启动
    syntax error: unexpected end of file完美解决方案
    Linux利用nc命令脚本批量检测服务器指定端口是否开放
    41-贪心算法
    38-动态规划
  • 原文地址:https://www.cnblogs.com/twodog/p/12135290.html
Copyright © 2020-2023  润新知