学习Flutter仿制网易云音乐APP

2021-06-30  本文已影响0人  qiufeng1ye

代码取自于不倦APP(简洁的三方网易播放器),作者sixbugs。


我使用的是vscode环境,环境搭建可参考此文最后一段。环境搭建好后新建一个项目,打开pubspec.yaml文件设置要引用的包。

dependencies:
  get: ^3.26.0
  path_provider: ^2.0.1
  color_thief_flutter: ^1.0.2
  cached_network_image: ^2.5.1
  flutter_placeholder_textlines: ^1.0.4
  starry:
    path: starry
  shared_preferences: ^2.0.4
  cookie_jar: ^2.0.0
  encrypt: ^3.0.0
  we_slide: ^2.1.0
  pull_to_refresh: ^1.6.5
  sleek_circular_slider: ^2.0.0
  dots_indicator: ^2.0.0
  progress_state_button: ^1.0.2
  direct_select_flutter: ^1.1.0
  on_audio_query: ^1.0.6
  flutter_screenutil: ^5.0.0
  dio: ^4.0.0
  url_launcher: ^6.0.3
  package_info: ^2.0.0
  flutter:
    sdk: flutter

设置后点击右上角的“Get Packages”下载依赖,再打开lib下的main.dart文件。


代码结构
import 'api/answer.dart';
import 'api/netease_cloud_music.dart';
import 'global/global_binding.dart';
import 'global/global_config.dart';
import 'global/global_theme.dart';

main.dart中引用的包主要是数据接口类和全局类。数据接口类调用的是网页云音乐开放的api,API文档地址参考为这里

全局类

使用DEBUG模式进入APP,将断点搭载runAPP一行,F11单点调试观察程序的运行情况。


调试状态

随着进入程序,可以发现首先在GlobalBinding中初始化了GlobalController、FindController、UserController三个实例。

GlobalController
GlobalController
GlobalController主要用于初始化播放器。
GlobalController下的addSliderListener方法如下,其用于监听点击底部播放栏状态的变化情况,使用了we_slide插件
  void addSliderListener(weSlideController,PreloadPageController pageController) {
    this.weSlideController = weSlideController;
    this.pageController = pageController;
    weSlideController.addListener(() {
      if (weSlideController.isOpened) {
        HomeController.to.resumeStream();
      } else {
        pageController?.jumpToPage(0);
        HomeController.to.pauseStream();
      }
    });
  }

在音乐播放的部分使用了作者自己封装的StarrySky插件

  ///播放或暂停
  playOrPause() async {
    // if (playState.value == PlayState.ERROR || playState.value == PlayState.STOP)
    //   return;
    if (song.value.musicId == '-99') return;
    if (playState.value == PlayState.PLAYING) {
      await Starry.pauseMusic();
    } else {
      await Starry.restoreMusic();
    }
  }

  ///设置播放模式
  changePlayMode() {
    if (playListMode.value == PlayListMode.SONG) {
      //1->2->3
      var value = 1;
      switch (playMode.value) {
        case 1:
          value = 2;
          break;
        case 2:
          value = 3;
          break;
        case 3:
          value = 1;
          break;
      }
      Starry.setPlayMode(value);
    }
  }
FindController
FindController

FindController主要用于初始化发现页。
FindController下的loadTodaySheet方法如下,将获取的数据刷新至发现页上。

  loadTodaySheet({forcedRefresh = false}) {
    NetUtils()
        .getRecommendResource(forcedRefresh: forcedRefresh)
        .then((personalEntity) {
      if (personalEntity != null && personalEntity.code == 200) {
        var sheets = personalEntity.result;
        allSheet
          ..clear()
          ..addAll(sheets);
        sheet..clear();
        if (sheets.length == 6) {
          var i = sheets.length ~/ 3;
          for (int j = 0; j < i; j++) {
            sheet.add(sheets.sublist(j * 3, (j + 1) * 3));
          }
        }
      } else {
        loadState.value = LoadState.FAIL;
      }
    });
    loadNewSong();
  }
UserController
UserController

UserController主要用于初始化用户页。

页面类

回到Main.Dart中继续跟踪程序的运行,进入runApp下的initialRoute,在app_outes.dart中对整个程序的路由绑定了对应的页面。

class AppPages {
  static const INITIAL = '/home';

  static final routes = [
    GetPage(name: '/home', page: () => HomeView(), binding: HomeBinding()),
    GetPage(name: '/today', page: () => TodayView(), binding: TodayBinding()),
    GetPage(
        name: '/sheet',
        page: () => SheetInfoView(),
        binding: SheetInfoBinding()),
    GetPage(
        name: '/profile', page: () => ProfileView(), binding: ProfileBinding()),
    GetPage(
        name: '/setting', page: () => SettingView(), binding: SettingBinding()),
    GetPage(name: '/login', page: () => LoginView(), binding: LoginBinding()),
    GetPage(name: '/cloud', page: () => CloudView(), binding: CloudBinding()),
    GetPage(
        name: '/sheet_classify',
        page: () => SheetClassifyView(),
        binding: SheetClassifyBinding()),
    GetPage(
        name: '/music_talk',
        page: () => MusicTalkView(),
        binding: MusicTalkBinding()),
    GetPage(
        name: '/all_song',
        page: () => AllSongView(),
        binding: AllSongBinding()),
    GetPage(
        name: '/search_details',
        page: () => SearchDetailsView(),
        binding: SearchDetailBinding()),
    GetPage(name: '/local', page: () => MusicView(), binding: MusicBinding()),
    GetPage(name: '/top', page: () => TopView(), binding: TopBinding()),
    GetPage(
        name: '/local_album',
        page: () => LocalAlbumView(),
        binding: LocalAlbumBinding()),
    GetPage(
        name: '/donate', page: () => DonateView(), binding: DonateBinding()),
    GetPage(name: '/about', page: () => AboutView()),
    GetPage(name: '/album', page: () => AlbumView(), binding: AlbumBinding()),
    GetPage(
        name: '/history', page: () => HistoryView(), binding: HistoryBinding()),
    GetPage(name: '/radio', page: () => RadioView(), binding: RadioBinding()),
    GetPage(
        name: '/radio_detail',
        page: () => RadioDetailView(),
        binding: RadioDetailBinding()),
    GetPage(
        name: '/play_list_manager',
        page: () => PlayListManagerView(),
        binding: PlayListManagerBinding()),
  ];
}

本项目的路由管理用到了GetX包,GetX 是 Flutter 上的一个轻量且强大的解决方案:高性能的状态管理、智能的依赖注入和便捷的路由管理。

响应式数据
controller定义初始数据源 ,需要继承 GetxController ;binding用于将控制器推入getx中,可以理解为要注册控制器;view中使用 GetView<T> 语法将 controller 和 binding 进行关联 ,内部可以直接使用 controller的实例调用数据和方法。
使用 Obx(()=> widget) 语法,返回你要变更的组件, 让数据在视图上更新,并且是局部刷新的。 home_view的目录结构
home_controller的目录结构

以home页面为例,home_view主要由各类widget组成,home_controller里用于实现业务逻辑。

上一篇下一篇

猜你喜欢

热点阅读