likes
comments
collection
share

Flutter仿网易云音乐(三)全局音乐播放栏

作者站长头像
站长
· 阅读数 4

前言

网易云音乐ios端和android端底部的音乐栏几乎都覆盖了所有页面,但是还是有些许不同:

Flutter仿网易云音乐(三)全局音乐播放栏 Flutter仿网易云音乐(三)全局音乐播放栏

从上图可以看出ios端的音乐栏在页面切换时总是固定的,而android端则有一个较为明显的路由切换动画。

在编程开始之前,先来了解一下在Android原生中是如何实现的,知乎文章:www.zhihu.com/question/36…

从最高赞回答中可以知晓,在Android原生中实现固定音乐栏的方法,就是在每个activity都写一个音乐栏...

呃...这种方法不说繁琐,简直就是麻烦。没有批评网易大佬的意思,也许他们有更多的考量。

那么如果在flutter实现固定音乐栏的话,有没有更简单的方法呢。

Flutter仿网易云音乐(三)全局音乐播放栏

悬浮窗

的确,如果创建一个固定位置的悬浮窗的话,实现出来的效果应该会和ios端更贴近,即页面跳转时无路由动画。

那么应该怎么实现悬浮窗呢?

最简单的方法就是OverlayEntry了。

一、封装一个OverlayEntry

音乐栏全局有且只有一个,因此使用工厂模式创建单例类:

class MusicBar {
  static final MusicBar _instance = MusicBar._();
  factory MusicBar() => _instance;
  MusicBar._();
  
  ///显示音乐栏
  OverlayEntry? overlayEntry;
  show(BuildContext context,Widget child,{double? top,double? left}) {
    if (overlayEntry == null) {
      overlayEntry = OverlayEntry(builder: (BuildContext context) {
        return MusicBarWidget(
          top: top??100,
          left: left??100,
          child: child,
        );
      });
      Overlay.of(context)?.insert(overlayEntry!);
    }
  }
  ///隐藏音乐栏
  void hide() {
    overlayEntry?.remove();
    overlayEntry = null;
  }
}

二、封装悬浮窗Widget

网易云音乐首页音乐栏下面还有个导航栏,当路由至其他界面时导航栏消失,这时音乐栏的下方就会空出一片地方。参考ios的处理方法,我添加了一段下沉动画,而回到首页时再上浮回来,同样用eventbus广播。


class _MusicBarWidgetState extends State<MusicBarWidget>
    with TickerProviderStateMixin {
  AnimationController? _controller;
  double left = 0;
  double top = 0;
  double maxX = 0;
  double maxY = 0;
  var parentKey = GlobalKey();
  var childKey = GlobalKey();
  var parentSize = const Size(0, 0);
  var childSize = const Size(0, 0);
  late StreamSubscription _subscription;

  @override
  void initState() {
    _subscription = eventBus.on().listen((event) {
      EventObj eventObj = event;
      if (eventObj.code == EventCode.toBottom) {
        toBottom();
      }
      if (eventObj.code == EventCode.riseUp) {
        riseUp();
      }
    });
    left = widget.left;
    top = widget.top;
    WidgetsBinding.instance.addPostFrameCallback((d) {
      parentSize = getWidgetSize(parentKey);
      childSize = getWidgetSize(childKey);
      maxX = parentSize.width - childSize.width;
      maxY = parentSize.height - childSize.height;
    });
    super.initState();
  }

  @override
  void dispose() {
    _controller?.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Stack(
      key: parentKey,
      fit: StackFit.expand,
      children: [
        Positioned(
          key: childKey,
          left: left,
          top: top,
          child: widget.child,
        )
      ],
    );
  }

  //底部导航栏消失时沉底
  void toBottom() {
    _controller = AnimationController(vsync: this)..duration = widget.duration;
    var animation = Tween<double>(begin: top, end: maxY).animate(_controller!);
    animation.addListener(() {
      top = animation.value;
      setState(() {});
    });
    _controller!.forward();
  }

  //底部导航栏出现时抬高
  void riseUp() {
    _controller = AnimationController(vsync: this)..duration = widget.duration;
    var animation =
        Tween<double>(begin: top, end: maxY - 56).animate(_controller!);
    animation.addListener(() {
      top = animation.value;
      setState(() {});
    });
    _controller!.forward();
  }

  Size getWidgetSize(GlobalKey key) {
    final RenderBox renderBox =
        key.currentContext?.findRenderObject() as RenderBox;
    return renderBox.size;
  }
}

三、使用方法

MusicBar smallWindowManager = MusicBar();
smallWindowManager.show(
    context,
    _musicBar(size,smallWindowManager,context),top: size.height-ScreenUtil().setWidth(70)-56,left: 0);
//_musicBar()为Widget,样式自己编写

虽然这样子实现的悬浮窗不受路由影响,但是在某些页面需要隐藏的情况下也需要注意显示隐藏的逻辑,在特定的生命周期进行显示隐藏。

最后来张效果图

Flutter仿网易云音乐(三)全局音乐播放栏

转载自:https://juejin.cn/post/7342335589741576243
评论
请登录