最近主要在学习Flutter,一直想学习开发APP,虽然会Vue,但是用Uniapp开发一直不是自己理想的,而我对flutter很感兴趣,很大一部分原因是因为我用过很多第三方客户端都是用flutter开发的,对于material design的风格我也很喜欢.

这篇博文拖了两星期才写完,很多细节可能有错.

这里先放出该项目的地址xiaoyaocz/dart_simple_live: 简简单单的看直播 (github.com)

第一个星期

最初一个星期一直在阅读网上的教程,从CSDN上的各种文章到《Flutter实战·第二版》,但是对于没有app开发经验的我,读来读去真的让我一头雾水,电子书上来就把各种组件告诉你,而没有实例的方式让我读起来比较困难,即使我跟随B站视频学习写了一个成品App,但是对于电子书的组件介绍依旧是一头雾水

有些眉目

我一直在用一个直播软件simple_live,是一个聚合了B站,抖音,斗鱼,虎牙直播的软件.

恰巧看到作者发布了Windows版本,我也第一时间下载体验,结果Windows上的操控适配并没有做完,于是我开始查看软件的代码,尝试自己来做桌面端的操控.

视频播放器操控

我提交的第一份代码就是针对桌面端视频播放器的操控的.

起初我看到作者写了一个鼠标侧键退出全屏的功能,但是我更习惯ESC退出,所以照葫芦画瓢写了一下按ESC退出全屏的功能

RawKeyboardListener(
                 focusNode: FocusNode(),
                 onKey: (RawKeyEvent event) async {
                   if (event.logicalKey == LogicalKeyboardKey.escape){
//业务逻辑
                  }

作者还没有对桌面端的鼠标手势做监听,逻辑都还是手机上的,所以我开始照葫芦画瓢,一边搜索怎么写鼠标监听,一边给软件添加代码,作者的文件分类很清晰,并且呼出/隐藏播放控制器的方法作者都已经写好,我只需要写鼠标监听即可,所以写起来还是很顺利的.成功实现了鼠标滑入播放器区域显示控制器,全屏模式滑到上/下方25%区域显示播放器(仿照B站的策略)

MouseRegion(
   onHover
   onEnter
   onExit
)

音量调节,小窗

音量调节我最初是想实现网页上的效果,指针悬浮显示滑动条,点击实现开启/关闭静音.最初选择使用overlay悬浮图层来做滑动条,再加上GetX的状态存储,搭配Timer计时器做消失计时,做一个也不难.毛病就是overlay是在最顶层的,导致在返回主页的时候音量滑动条还在显示,并且因为全屏和非全屏两个状态的音量条坐标位置需要单独计算,又写了一个工具类来计算坐标,十分的麻烦.

初版可以说是相当糟糕了,还是提交了PR后作者帮我重写了一版,才有了如今的样子

image-20231109163150905

使用的是flutter的名为[SimpleDialog]的库,这个库如其名可以很方便写出Dialog组件,并且自定义程度十分高,并且有一个很关键的属性是和其他组件做关联来判断位置,这样只需要和音量ICON做关联就可以了.

小窗这个功能的实现出奇的简单,原因是在我初次使用桌面端时作者就已经写了按侧键退出全屏的功能,但该功能其实不太完善(或者作者有意为之?),退出全屏时候是只有播放器没有其他信息的状态,这便给我写小窗功能做了启发,于是在查看代码后发现作者用了一个状态变量来显示/隐藏除播放器外的控件,这下就很简单了,思路就是点击按钮->修改状态变量->隐藏标题栏,修改窗口大小

作者用的是window_manager来控制桌面端大小,这个库十分方便,我也使用了该库来修改窗口大小,并且这个库提供了一个DragToMoveArea控件,可以实现拖拽窗口的功能,只需要在播放器组件上套一层DragToMoveArea就可以了.

image-20231109164228487

更多尝试

在给桌面端增加一些功能后,查看了一下Issues里有一些很简单的小建议作者打上了标签还没有实现,于是我开始实现这些小建议.

添加定时关闭延时弹窗

依旧是查看了代码,先学习作者是如何实现定时关闭的功能的,作者的思路是创建两个状态变量,一个用于判断是否开启定时关闭,一个用于定时关闭计时,再配合Timer计时器,在进入直播间后开启计时器,设置一个变量用于剩余时间计时,当该变量<=0时候,执行关闭代码.

所以我的思路就很简单了,在time<=0时候弹窗并同时开启一个计时器用于弹窗倒计时,并根据弹窗的选择来决定重新设置计时器还是关闭APP.

autoExitTimer = Timer.periodic(const Duration(seconds: 1), (timer) async {
     countdown.value -= 1;
     if (countdown.value <= 0) {
       timer = Timer(const Duration(seconds: 10), () async {
         await WakelockPlus.disable();
         exit(0);
      });
       autoExitTimer?.cancel();
       var delay = await Utils.showAlertDialog(
           "定时关闭已到时,是否延迟关闭?",
           title: "延迟关闭",
           confirm: "延迟",
           cancel: "关闭",
           selectable: true
      );
       if (delay) {
         timer.cancel();
         delayAutoExit.value = true;
         showAutoExitSheet();
         setAutoExit();
      } else {
         delayAutoExit.value = false;
         await WakelockPlus.disable();
         exit(0);
      }
    }
  });
}

主题变换/动态取色

flutter上动态取色是一个看起来很高级的功能,我个人也很喜欢这种功能.

在flutter中动态取色都是使用dynamic_color库,这个库的具体使用方法我不再写了,网上的教程很详细了.

主题变换主要使用了Get的设置主题功能,但一开始使用Get.changeTheme时候,发现在黑夜模式变更主体切换日间模式会有BUG,所以最后改用了在Controller.setStyleColor后直接Get.forceAppUpdate()强制rebuild.

image-20231109165938125
class ColorBox extends GetView<AppSettingsController> {
 final Color color;
 final String name;

 const ColorBox({super.key, required this.color, required this.name});

 @override
 Widget build(BuildContext context) {
   return GestureDetector(
     onTap: () {
       controller.setStyleColor(color.value);
       Get.forceAppUpdate();
    },
     child: Column(
       children: [
         Obx(
          () => Container(
             width: 70.0,
             height: 40.0,
             margin: const EdgeInsets.only(
                 left: 7.0, right: 7.0, top: 7.0, bottom: 2.0),
             decoration: BoxDecoration(
                 color: color, borderRadius: BorderRadius.circular(4.0)),
             child: AnimatedOpacity(
               opacity: controller.styleColor.value == color.value ? 1 : 0,
               duration: 0.4.seconds,
               child: Container(
                 decoration: BoxDecoration(
                   borderRadius: BorderRadius.circular(4.0),
                   border: Border.all(color: Colors.black, width: 1),
                ),
                 child: const Icon(
                   Icons.check,
                   color: Colors.white,
                ),
              ),
            ),
          ),
        ),
         Text(name)
      ],
    ),
  );
}
}

总结

还是直接上手一个项目学得快,光看flutter教程真是看得我头大,文字太多了我只能说.不是没耐心看下去,而是只看不写实在是效率低下.这次PR的经历让我快速的学习了很多,包括GET以及hive的使用,也很感谢作者的耐心修改.


Angel,请你不要放开我的手