• 第五篇 抖音的强大对手来了,用Flutter手撸一个抖音国际版,看看有多炫


    前言

    由于中间几个月项目天天加班,导致没没时间更新,最近一段时间对前端进行了重构,加了很多页面,如登录、注册、关注、个人中心等,目前写这个纯属业余个人爱好,所以断断续续的继续在做......

    前端地址:https://www.pgyer.com/dtok
    后端服务器地址:http://47.95.209.198:8181/

    注释:由于本人的apple id无法打包ios、所以暂时只打包的android版本,ios版本正在解决账号问题

    效果如下:

    架构更新

    之前技术采用flutter做的前端,后端api则对接的是抖音官方api,由于抖音的官方api更新频繁,导致经常播放不了,所以索性自己来写服务器后端api,那么后端api采用了那些技术咧

    • springcloud 主要是后台控制面板 演示地址:http://47.95.209.198:8181/login
    • elasticsearch 主要对视频数据离线查询
    • ipfs 用于分布式节点存储短视频
    • ethereum 用户激励用户存储短视频、毕竟买服务器存花费够大的

    界面更新

    • 支持国家化,多语言切换
    • ipfs上传、下载文件
    • 登录页面
    • 注册页面
    • 上下轮播时优化播放效果
    • 点赞功能

    其他功能还在继续完善,各位喜欢的话欢迎点个star 前端项目地址:https://github.com/telsacoin/telsavideo
    后端需要的话请留下邮箱

    本期最大的优化就是国际化,flutter国家化按以下步骤

    在pubspec.yaml文件加上

      flutter:
        sdk: flutter
      flutter_localizations:
        sdk: flutter
      intl: ^0.17.0 # Add this line
      ffi: ^1.1.2
    

    在底部的flutter设置里添加

    # The following section is specific to Flutter.
    flutter:
      # The following line ensures that the Material Icons font is
      # included with your application, so that you can use the icons in
      # the material Icons class.
      uses-material-design: true
      generate: true # Add this line
    

    新建多语言包

    在lib目录新建子目录l10n
    里面添加app_zh.arb文件
    内容如下:

    {
        "home_top_foryou":"推荐",
        "home_top_following":"关注",
        "home_share":"分享",
        "home_buttom_title":"首页",
        "home_buttom_discover":"发现",
        "home_buttom_notification":"通知",
        "home_buttom_persion":"我"
    }
    

    在main文件引用

    import 'package:flutter_gen/gen_l10n/app_localizations.dart';
    

    在build里加入多语言检测及支持的代码

    return MaterialApp(
        debugShowCheckedModeBanner: false,
        onGenerateTitle: (context) =>
            AppLocalizations.of(context)!.home_buttom_title,
        home: SplashScreen(),
        localeResolutionCallback: (
          Locale? locale,
          Iterable<Locale> supportedLocales,
        ) {
          return locale;
        },
        localizationsDelegates: AppLocalizations.localizationsDelegates,
        supportedLocales: AppLocalizations.supportedLocales,
        theme: ThemeData(
          textSelectionTheme: TextSelectionThemeData(
            cursorColor: Colors.white,
          ),
          splashColor: Colors.transparent,
          highlightColor: Colors.transparent,
          primarySwatch: Colors.red,
          primaryColor: Colors.black,
          indicatorColor: Colors.white,
          tabBarTheme: TabBarTheme(),
        ),
        /* initialRoute: '/',
       onGenerateRoute: RouteGenerator.generateRoute, */
        builder: (context, child) {
          return ScrollConfiguration(
            behavior: MyBehavior(),
            child: child!,
          );
        },
      );
    

    然后在需要引用的位置加入

    import 'package:flutter_gen/gen_l10n/app_localizations.dart';
    

    调用的位置

    AppLocalizations.of(context)!.home_top_foryou
    

    至此,国际化就完成了

    另外本地针对播放模块进行了优化,将代码拆分到videoplayer.dart文件.一来是方便代码阅读,而来可以作为子组件使用,其他的代码写得太冗余也在继续拆开,独立出来,各位感兴趣的可以关注项目的进展。

    采用FutureBuilder对界面请求数据异步处理,当加载完成后才播放,效果更佳

    代码如下:

    return FutureBuilder<DTok>(
        future: videos,
        builder: (context, snapshot) {
          print(snapshot.connectionState);
          if (snapshot.connectionState == ConnectionState.waiting) {
            return loading;
            // return Column(
            //   crossAxisAlignment: CrossAxisAlignment.center,
            //   mainAxisAlignment: MainAxisAlignment.center,
            //   children: [
            //     loading,
            //     Visibility(
            //       visible: snapshot.hasData,
            //       child: PageView.builder(
            //           controller: foryouController,
            //           onPageChanged: (index) {
            //             //when the video is changing, release the previous video instance.
            //             //disposeVideo();
            //             setState(() {});
            //           },
            //           scrollDirection: Axis.vertical,
            //           itemCount: snapshot.data!.itemList!.length,
            //           itemBuilder: (context, index) {
            //             var item = snapshot.data!.itemList![index];
            //             return Videoplayer(
            //               item: item,
            //                MediaQuery.of(context).size.width,
            //               heigth: MediaQuery.of(context).size.height,
            //             );
            //           }),
            //     )
            //   ],
            // );
          } else if (snapshot.connectionState == ConnectionState.done) {
            if (snapshot.hasError) {
              return Column(
                crossAxisAlignment: CrossAxisAlignment.center,
                mainAxisAlignment: MainAxisAlignment.center,
                children: [
                  const Text('Error, Please restart your app agagin')
                ],
              );
            } else if (snapshot.hasData) {
              try {
                return PageView.builder(
                    controller: foryouController,
                    onPageChanged: (index) {
                      //when the video is changing, release the previous video instance.
                      //disposeVideo();
                      //setState(() {});
                    },
                    scrollDirection: Axis.vertical,
                    itemCount: snapshot.data!.itemList!.length,
                    itemBuilder: (context, index) {
                      var item = snapshot.data!.itemList![index];
                      return Videoplayer(
                        item: item,
                         MediaQuery.of(context).size.width,
                        heigth: MediaQuery.of(context).size.height,
                      );
                    });
              } catch (e) {
                return Container(
                   MediaQuery.of(context).size.width,
                  height: MediaQuery.of(context).size.height,
                  color: Colors.black,
                  child: Center(
                      child: Text(
                    'Error, Please restart your app again.',
                    style: TextStyle(color: Colors.white),
                  )),
                );
              }
            } else {
              // empty data
              return loading;
            }
          } else {
            return Text('State: ${snapshot.connectionState}');
          }
        });
    

    这里可以看到当snapshot.connectionState == ConnectionState.waiting的时候请求的数据正在加载中,则显示加载的图标loading

    当snapshot.connectionState == ConnectionState.done 时,此时数据已经加载完毕,但是加载完毕有可能也没有数据,所以需要判断不同的情况

    当加载出现异常情况则显示异常的widget

    if (snapshot.hasError) {
          return Column(
            crossAxisAlignment: CrossAxisAlignment.center,
            mainAxisAlignment: MainAxisAlignment.center,
            children: [
              const Text('Error, Please restart your app agagin')
            ],
          );
        } 
    

    当if (snapshot.hasData)则说明有返回值,但是这个返回值不一定就是我们需要的数据,所以还需要try catch一下,保证呈现给用户的界面是正常的

    try {
          return PageView.builder(
              controller: foryouController,
              onPageChanged: (index) {
                //when the video is changing, release the previous video instance.
                //disposeVideo();
                //setState(() {});
              },
              scrollDirection: Axis.vertical,
              itemCount: snapshot.data!.itemList!.length,
              itemBuilder: (context, index) {
                var item = snapshot.data!.itemList![index];
                return Videoplayer(
                  item: item,
                   MediaQuery.of(context).size.width,
                  heigth: MediaQuery.of(context).size.height,
                );
              });
        } catch (e) {
          return Container(
             MediaQuery.of(context).size.width,
            height: MediaQuery.of(context).size.height,
            color: Colors.black,
            child: Center(
                child: Text(
              'Error, Please restart your app again.',
              style: TextStyle(color: Colors.white),
            )),
          );
        }
      } 
    

    其他情况则返回加载状态,因为没有数据返回

    另外加载videoplay的时候把width、heigth传入到下一个控件,这样好计算界面呈现的宽度与高度

    return Videoplayer(
            item: item,
             MediaQuery.of(context).size.width,
            heigth: MediaQuery.of(context).size.height,
          );
    

    结语

    请继续关注本博客,其他页面持续更新完成,源码地址:telsavideo, 欢迎fork和star,谢谢!!!

    再次奉上演示地址:
    前端地址:https://www.pgyer.com/dtok
    后端服务器地址:http://47.95.209.198:8181/

  • 相关阅读:
    Log4Net记录到MySql
    创建快照
    grep的用法(CentOS7)及有关正则表达式的使用
    samba
    mkdir
    raid0和raid5的 实验过程
    route
    source和sh执行脚本时的差异
    echo命令的简单用法和实例
    smbpasswd和pdbedit
  • 原文地址:https://www.cnblogs.com/fengqingyangNo1/p/15988863.html
Copyright © 2020-2023  润新知