第五篇- 抖音的強大對手來了,用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,
        //               width: 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,
                    width: MediaQuery.of(context).size.width,
                    heigth: MediaQuery.of(context).size.height,
                  );
                });
          } catch (e) {
            return Container(
              width: 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,
              width: MediaQuery.of(context).size.width,
              heigth: MediaQuery.of(context).size.height,
            );
          });
    } catch (e) {
      return Container(
        width: 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,
        width: 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/


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM