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

前言

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

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

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

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

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

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

悬浮窗

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

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

最简单的方法就是OverlayEntry了。

一、封装一个OverlayEntry

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

scss 复制代码
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广播。

ini 复制代码
​
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;
  }
}
​

三、使用方法

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

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

最后来张效果图

相关推荐
Lanren的编程日记1 小时前
Flutter 鸿蒙应用AR功能集成实战:多平台AR框架+模拟模式,打造增强现实体验
flutter·ar·harmonyos
zhangjikuan892 小时前
Flutter备忘
flutter
Lanren的编程日记3 小时前
Flutter 鸿蒙应用权限管理功能实战:标准化权限申请与状态管控,提升用户信任度
flutter·华为·harmonyos
Lanren的编程日记4 小时前
Flutter 鸿蒙应用语音识别功能集成实战:多平台框架+模拟模式,实现便捷语音输入
flutter·语音识别·harmonyos
拉拉尼亚4 小时前
Flutter Widget 完全指南
flutter
Lanren的编程日记4 小时前
Flutter 鸿蒙应用数据验证功能实战:完善表单验证体系,全方位提升数据质量
flutter·华为·harmonyos
jump_jump14 小时前
GetX — Flutter 的瑞士军刀,还是过度封装的陷阱?
flutter·设计模式·前端框架
MonkeyKing1 天前
三棵树彻底拆解(Widget / Element / RenderObject)
flutter·dart
Lanren的编程日记1 天前
Flutter 鸿蒙应用错误处理优化实战:完善全局异常捕获,全方位提升应用稳定性
flutter·华为·harmonyos