Flutter仿寫單讀App介紹與總結

前言:

  • Flutter在2018年裏從Beta一直走到1.0版本,雖然暴露出1.0版本仍然有不少Bug、生態不完善、庫少、某些功能實現很差等問題,可是關注它的人們依然熱情很高,天天都有關於Flutter的新的東西產出,說明不少人仍是很看好它的。
  • 國內阿里騰訊等也在積極佈道,作了不少高質量的分享。也許大火須要時間,商用也須要膽量,可是如今學習瞭解一下,仍是頗有必要的。

關於項目:

此次選擇仿寫一個很輕量的項目單讀App,主要經過項目來練習一下經常使用的組件佈局網絡請求json解析等。javascript

項目準備:

本項目是經過Charles抓包工具來獲取項目Api,經過Apple Configurator 2工具來獲取仿寫App的圖片資源,關於兩個工具的介紹和使用能夠參考下之前的文章Swift高仿喜馬拉雅APP之一Charles抓包、圖片資源獲取等php

項目源碼:

Flutter仿寫單讀App 完整源碼css

效果圖:

主要效果圖

項目總體主要點介紹:

以項目進來看到的主頁面爲例介紹佈局:html

項目主頁採用的是PageView,當前屏幕所能看到的即時PageView的單一頁面,該頁面的佈局主要是使用Column裏面children: <Widget>[]包裹各類Widget,頂部圖片是使用 new Image.network()來獲取顯示網絡圖片,中間部分爲包裹的Container、Text,各個組件之間間距可使用Padding或者Expanded來零活控制,最底部選擇使用Row包裹一下喜歡喝評論按鈕等。前端

主頁面

主頁面的主要代碼示例:html5

返回PageView:java

return Center(
      child: PageView.builder(
        itemCount: dataList.length,
        scrollDirection: Axis.vertical,
        itemBuilder: (context, index) {
          return calendarList(dataList[index],index,context);
        },
      ),
    );
複製代碼

網絡請求和數據解析:android

須要在工程的pubspec.yaml文件中dependencies: 中引入 dio: ^1.0.13 該庫爲網絡請求庫^x.x.x爲版本號。 在dependencies:中引入json_annotation:: ^2.0.0,在dev_dependencies:中引入 json_serializable: ^2.0.1 build_runner: ^1.1.2,這三個爲json解析庫,配合使用就能夠進行網絡請求和json解析數據賦值了,引入完成後記得右上角的Packages get一下。ios

使用舉例以下:git

說明一下: 因爲是抓包接口,抓包中含有加密的sign字段並無破解,致使接口有很短的超時時間,因此該項目中使用了不少的json本地接口數據,也意味着沒有分頁,可是文字模塊的接口是獲取的網絡接口,含有分頁和下拉接在更多,因此該項目能夠看到本地json數據和網絡數據兩種處理方式。

對於本地json文件的引入,能夠直接拖動一個json到工程本身新建的文件夾,而後在pubspec.yaml文件中flutter:下面寫上assets:表明引入資源文件,而後把具體路徑寫下就好了例如- json/HomeData.json不過須要注意寫法和空格,一樣項目中須要的圖片資源也是拖進新的個文件夾內,而後在這裏面作下引入Packages get一下,具體的能夠查看本項目源碼。

本地json數據load和解析示例:

void loadData() async{
    String cssStr = await DefaultAssetBundle.of(context).loadString('json/HomeData.json');
    CommonModel model = CommonModel.fromJson(json.decode(cssStr));
    setState(() {
      List<dynamic>data = [];
      data.addAll(model.datas);
      dataList.addAll(data);//給數據源賦值
    });
  }
複製代碼

網絡接口請求和解析示例: 說明一下請求http接口的時候iOS須要添加信任Http認證,具體爲iOS文件夾-->Runner-->info.plist添加代碼

/// 這裏是請求接口數據,當滑動到最底端的時候會判斷是否加載更多更改二個參數,post_id,page+1,而後把新請求的數據追加到之前的數據中
  void loadData(String post_id, int curPage,bool isMore) async{
    Dio dio = new Dio();
    Response response = await dio.get("http://203.195.230.211/index.php?m=Home&c=Api2&a=getTagList&tag=$tag&p=$curPage&client=iOS&show_sdv=1&page_id=$post_id&create_time=0&sign=24a904d18a786327741ca5613180ceda&time=1547092560&device_id=A39632E7-F689-405B-A3A3-0319D6095B54&version=1.6.2&client=iOS");
    CommonModel result = CommonModel.fromJson(response.data);
    setState(() {
      if(!isMore) {
        List<dynamic>data = [];
        data.addAll(result.datas);
        new_page_id = result.datas[data.length-1].id;
        dataList.addAll(data);//給數據源賦值
      }else{
        List<dynamic>data = [];
        data.addAll(result.datas);
        new_page_id = result.datas[data.length-1].id;
        dataList.addAll(data);
      }
    });
  }
}
複製代碼

對於數據模型的建立:

這裏利用一個工具來自動生成modeljson2dart, 如圖所示:

具體用法是先在工程中建立一個例如HomeDataModel.dart文件,而後利用該工具生成json格式,拷貝到該文件下面,注意上圖中的entity應該改成HomeDataModel,這個時候終端cd到該項目的根目錄,便是ls能夠看到pubspec.yaml的目錄,運行flutter packages pub run build_runner build,過一會會自動生成HomeDataModel.g.dart的文件。

到這裏首頁部分的佈局數據模型接口請求數據解析都好了,就能夠賦值查看真是數據的頁面了,這裏以主頁底部Row,即點贊喜歡部分賦值代碼示例,具體的請看源碼。

class _LikeWidgetState extends State<LikeWidget> {
  bool _isLike = true;
  void _togoLike() {
    setState(() {
      /// 若是是喜歡置爲未喜歡
      if (_isLike) {
        _isLike = false;
        /// 若是是未喜歡置爲喜歡
      } else {
        _isLike = true;
      }
    });
  }
  @override
  Widget build(BuildContext context) {
    return Row(
      children: <Widget>[
        new Expanded(
            child:new Row(
              children: <Widget>[
                new IconButton(
                    icon: Image.asset('assets/comment@2x.png'),
                    onPressed: (){
                      Navigator.of(context).push(new MaterialPageRoute(builder: (ctx) {
                        return new CommentPageStateful(post_id: widget.data.id);
                      }));
                    }
                ),
                new Text(
                  widget.data.comment,
                  style: TextStyle(
                    color: Colors.black,
                    fontSize: 16,
                  ),
                ),
                new IconButton(
                    icon: (_isLike ? new Image.asset("assets/like@2x.png") : new Image.asset("assets/liked@2x.png")),
                    onPressed: (){
                      _togoLike();
                    }
                ),
                new Text(
                  /// 這裏是先經過int.parse(widget.data.good) string轉int ,而後toString()再轉成string
                  _isLike ? int.parse(widget.data.good).toString() : (int.parse(widget.data.good)+1).toString(),
                  style: TextStyle(
                    color: Colors.black,
                    fontSize: 16,
                  ),
                ),
              ],
            )
        ),
        new Container(
          padding: EdgeInsets.only(right: 15),
          child: new Text(
            '閱讀數 '+widget.data.view,
            style: TextStyle(
              color: Colors.black,
              fontSize: 16,
            ),
          ),
        )
      ],
    );
  }
}

複製代碼

自定義Drawer:

經過主頁面能夠看到,頂部AppBar左右兩邊按鈕能夠彈出左右Drawer,因爲系統的Drawer是不能遮罩全屏的,因此這裏選擇自定義Widget實現全屏遮罩的Drawer

效果圖:

左邊導航頁
右邊導航頁

自定義Drawer示例代碼:

/// 自定義的左邊欄抽屜
class LeftDrawer extends StatefulWidget {
  final double elevation;
  final Widget child;
  final String semanticLabel;
  final double widthPercent;
  ///add start
  final DrawerCallback callback;
  ///add end
  const LeftDrawer({
    Key key,
    this.elevation = 16.0,
    this.child,
    this.semanticLabel,
    this.widthPercent,
    ///add start
    this.callback,
    ///add end
  })  : assert(widthPercent < 1.0 && widthPercent > 0.0),
        super(key: key);
  @override
  _LeftDrawerState createState() => _LeftDrawerState();
}

class _LeftDrawerState extends State<LeftDrawer> {

  @override
  Widget build(BuildContext context) {
    assert(debugCheckHasMaterialLocalizations(context));
    String label = widget.semanticLabel;
    final double _width = MediaQuery.of(context).size.width * widget.widthPercent;
    return Semantics(
      scopesRoute: true,
      namesRoute: true,
      explicitChildNodes: true,
      label: label,
      child: ConstrainedBox(
        constraints: BoxConstraints.expand(width: _width),
        child: Material(
          color: Colors.black87,
          elevation: widget.elevation,
          child: Container(
          ),
        ),
      ),
    );
  }
複製代碼

對於左右Drawer中的文字按鈕有個放大顯示的動畫,該字體動畫使用的是 animated_text_kit: ^1.2.0,有好幾種動畫,具體能夠參考源碼,具體使用爲導入import 'package:animated_text_kit/animated_text_kit.dart';

Widget _AnimatedText(String str){
    return SizedBox(
      child: ScaleAnimatedTextKit(
        duration: Duration(milliseconds: 4000),
        isRepeatingAnimation: false,
        text:[str],
        textStyle: TextStyle(
            fontSize: 35,
            color: Colors.white
        ),
      ),
    );
  }
複製代碼

關於加載WebView:

Flutter中使用WebView,能夠選擇使用flutter_webview_plugin: ^0.3.0+2插件,使用也很簡單引入頭文件import 'package:flutter_webview_plugin/flutter_webview_plugin.dart';

Widget _WebView(String url) {
  return WebviewScaffold(url: url);
}
複製代碼

使用該插件能夠顯示通常WebView頁面,可是我工程中的WebView頁面頂部AppBar,上面仍是有按鈕的,以下圖所示,點擊按鈕要跳轉到評論界面,可是使用該插件由於返回的WebviewScaffold,因此當在WebView頁面跳轉其餘頁面的時候,該頁面會一直在其餘頁面的最上層,雖然可使用widget.flutterWebviewPlugin.close();來進行關閉該頁面,可是意味着返回來的時候就看不到WebView頁面內容了,我本想去做者github尋求答案,看到有人已經提了issue可是做者給的標籤是待解決。有人知道這個問題怎麼解決請賜教。

WebView頁面
而若是隻是單純的加載 WebView頁面,再也不進行二次跳轉的狀況下,還可使用 url_launcher: ^4.0.1插件,該插件爲系統插件不只能夠顯示 WebView頁面具體使用請自行查閱資料,使用後的頁面樣式是固定的,具體使用參考以下引 入import 'package:url_launcher/url_launcher.dart';

void _launchURL(String url, BuildContext context) async {
    if (await canLaunch(url)) {
      await launch(url);
    } else {
      throw 'Could not launch $url';
    }
  }
  
  onTap: (){
    _launchURL(data.html5, context);
  },
複製代碼

url_launcher顯示的WebView頁面樣式:

url_launcher顯示的WebView頁面樣式

說明一下:

本項目中兩種加載WebView頁面的方式均有使用,文字模塊的二級頁面的Detail頁面使用的是flutter_webview_plugin,其他詳情頁面使用的均是url_launcher顯示的WebView頁面。

***追加說明:

通過羣友提示發現了更好的一種webview加載方式,使用 webview_flutter: ^0.2.0插件,目前該項目中WebView頁面已經所有換成該方法,使用該插件能夠自定義AppBar,而且跳轉頁面也不會有問題,以上兩種webview加載方法能夠自行選擇。
具體使用爲在pubspec.yaml中引入webview_flutter: ^0.2.0而且Packages get一下,在須要用到的頁面import 'package:webview_flutter/webview_flutter.dart'; import 'dart:async';

須要特別注意的是iOS端須要在info.plist中添加設置

plist設置
使用源碼示例:

class WebViewExample extends StatelessWidget {
  final Completer<WebViewController> _controller =
  Completer<WebViewController>();

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Flutter WebView example'),
      ),
      body: WebView(
        initialUrl: 'https://www.baidu.com',
        javascriptMode: JavascriptMode.unrestricted,
        onWebViewCreated: (WebViewController webViewController) {
          _controller.complete(webViewController);
        },
      ),
    );
  }
}
複製代碼

評論頁面:

對於剛剛的使用flutter_webview_pluginWebView頁面右上角點擊評論按鈕,便可跳轉評論頁面,評論頁面由於須要顯示HeaderView,因此選擇了sticky_headers: ^0.1.7庫,使用起來效果很好也很方便,對於佈局和詳細代碼請查看源碼,sticky_headers主要代碼示例以下:

child: ListView.builder(
          itemCount: newsList.length+hotsList.length+1,
          itemBuilder: (context, index) {
            if (index == 0) {
              return StickyHeader(header: CommentHeader('喜歡', diggList.length, index),
              content: DiggList(diggList, diggList.length, context),
              );
            }else if (hotsList.length >0 && index < hotsList.length+1) {
              return StickyHeader(header: CommentHeader('最熱評論', hotsList.length, index-1),
                content: HotsList(hotsList[index-1], index, context),
              );
            }else {
              return StickyHeader(header: CommentHeader('最新評論', newsList.length, index-hotsList.length-1),
                content: NewsList(newsList[index-hotsList.length-1], index, context),
              );
            }
          },
        ),
複製代碼

評論頁面效果圖:

評論頁面

談論頁面:

談論頁面頂部含有一個TabBar,左右滑動能夠切換單讀問頁面和讀者論頁面。

代碼示例以下:

class TalkStateful  extends StatefulWidget {
  @override
  _TalkPageState createState() => new _TalkPageState();
}

class _TalkPageState extends State<TalkStateful> with SingleTickerProviderStateMixin {
  TabController _tabController;
  @override
  void initState() {
    // TODO: implement initState
    super.initState();
    _tabController = new TabController(vsync: this, initialIndex: 0, length: 2);
  }

  @override
  Widget build(BuildContext context) {
    // TODO: implement build
    return Scaffold(
      appBar: AppBar(
        backgroundColor: Colors.black87,
        title: new Text('談論', style: TextStyle(color: Colors.white,fontSize: 24),),
        bottom: new TabBar(
            isScrollable: false,//是否可滑動
            unselectedLabelColor: Colors.white,//未選中按鈕顏色
            labelColor: Colors.white,//選中按鈕顏色
            labelStyle: TextStyle(fontSize: 16),//文字樣式
            indicatorSize: TabBarIndicatorSize.label,//滑動的寬度是根據內容來適應,仍是與整塊那麼大(label表示根據內容來適應)
            indicatorWeight: 2.0,//滑塊高度
            indicatorColor: Colors.black87,//滑動顏色
            indicatorPadding: EdgeInsets.only(bottom: 1),//與底部距離爲1
            controller: _tabController,
            tabs: <Widget> [
              new Tab(
                text: '單讀問',
              ),
              new Tab(
                text: '讀者論',
              )
            ]
        ),
      ),
      body: new TabBarView(
          controller: _tabController,
          children: <Widget> [
            new DanDuWenPage(),
            new DuZheLunPage(),
          ]
      ),
    );
  }
}
複製代碼

談論頁面效果圖以下:

單讀問頁面

讀者論頁面

關於本項目就介紹到這裏吧,想了解更多請看源碼部分,若是有什麼不懂得或者疑問能夠相互交流。

應用Icon和啓動頁LaunchImage設置:

iOS設置工程主文件夾例:flutter_dandu--> ios-->Runner-->Assets.xcassets-->AppIcon And LaunchImage

ios啓動頁和icon設置

Android設置工程主文件夾例:flutter_dandu-->android-->app-->src--main-->res

Android啓動頁和icon設置

總結:

剛接觸Flutter使用Dart語言,確定是會彆扭的,嵌套過多閱讀困難,可是瞭解以後發現也還行,整體來講如今談Flutter的未來還有點早,可是前景仍是很不錯的,有時間的或者客戶端前端從業者能夠考慮入門學習一下。熟練以後寫東西確實很快,跨平臺是大勢所趨,就算不是Flutter也會有其餘的出現。最重要的是FlutterGoogle手機設備新系統FuchsiaUI框架和過渡性產品,應該仍是挺看好的。

學習資源:

對於大佬確定是啃源碼和官方文檔了,可是對於小菜雞來講仍是找找譯文和其餘大佬教程吧。這裏整理了一些不錯的學習資源連接:
系列基礎知識教程:
一、Flutter中文網:https://flutterchina.club
二、技術胖:Flutter基礎視頻免費教程
大佬技術文章:
1.juejin.im/user/59be05…
2.juejin.im/post/5c1f35…
3.juejin.im/user/5ad016…
4.juejin.im/post/5bc450…
5.www.jianshu.com/u/5c5872cc9…
Flutter圈子和標籤及知乎:
Flutter圈子:https://www.jianshu.com/c/ebc9d2e84214
Flutter標籤:https://juejin.im/tag/Flutter
Flutter知乎:https://zhuanlan.zhihu.com/flutter
Flutter佈道者:
閒魚技術團隊
Flutter開源示例:
1.github.com/2d-inc/Hist…
2.flutterawesome.com
3.github.com/Solido/awes…
Flutter優秀開源項目:
一、Github客戶端App
二、阿里拍賣團隊Flutter開發者幫助App
三、開源中國App
四、仿網易雲音樂App
五、仿書旗小說App
六、仿好奇心日報App
七、仿單讀App

相關文章
相關標籤/搜索