Flutter入門——山寨掘金(二)

MeFelixWang發表於2018-07-25

寫在前面

上篇文章我們實現了首頁和文章詳情頁,今天我們繼續。

正式開始

一. 實現發現頁

開啟 discovery.dart ,可以刪掉之前寫的程式碼,或者在原來的基礎上改造也可以,看大家喜歡,首先在頂部引入需要用的包和其他檔案:

import 'package:flutter/material.dart';
import 'package:flutter/cupertino.dart';
import 'dart:async';
import 'dart:convert';
import 'package:http/http.dart' as http;
import '../utils/countTime.dart';
import '../config/httpHeaders.dart';
複製程式碼

在這裡我引入了一個 countTime.dart 檔案,這個是我們用來計算文章釋出時間與當前的差值的,我們先把這個小工具實現一下。在 lib 資料夾下新建 utils 資料夾,並在其中新建 countTime.dart 檔案,寫入以下程式碼:

//計算髮布時間間隔
String countTime(String timestamp) {
  var now = new DateTime.now();
  var publicTime = DateTime.parse(timestamp);
  var diff = now.difference(publicTime);
  if (diff.inDays > 0) {
    return '${diff.inDays}天前';
  } else if (diff.inHours > 0) {
    return '${diff.inHours}小時前';
  } else if (diff.inMinutes > 0) {
    return '${diff.inMinutes}分鐘前';
  } else if (diff.inSeconds > 0) {
    return '${diff.inSeconds}秒前';
  }
  return timestamp.substring(0, timestamp.indexOf('T'));
}

複製程式碼

上面的程式碼通過傳入的時間戳來計算差值,並返回不同的文字,比較簡單,只要小夥伴們熟悉一下語法就會了。

回到 discovery.dart 繼續我們的程式碼,將上一篇文章中網路請求的寫法改一下:

/*接著寫*/
class DiscoveryPage extends StatefulWidget {
  @override
  DiscoveryPageState createState() => new DiscoveryPageState();
}

class DiscoveryPageState extends State<DiscoveryPage> {
  List hotArticles;

  Future getHotArticles() {
    return http.get(Uri.encodeFull(
        'https://timeline-merger-ms.juejin.im/v1/get_entry_by_rank?src=${httpHeaders['X-Juejin-Src']}&uid=${httpHeaders['X-Juejin-Uid']}&device_id=${httpHeaders['X-Juejin-Client']}&token=${httpHeaders['X-Juejin-Token']}&limit=20&category=all&recomment=1'));
  }

  @override
  void initState() {
    super.initState();
    this.getHotArticles().then((response) {
      setState(() {
        hotArticles = json.decode(response.body)['d']['entrylist'];
      });
    }, onError: (e) {
      throw Exception('Failed to load data');
    });
  }
}
複製程式碼

initState 用來做初始化,寫過 react 的同志應該很熟悉了。接著是 then ,是不是和 Promise 很像?

上一篇文章中我們構建頁面用的主要是 ListView ,既然是入門教程,我們今天就用新的元件,多熟悉一些東西。接著寫:

class DiscoveryPageState extends State<DiscoveryPage> {
  /*接著寫*/
    @override
    Widget build(BuildContext context) {
      // TODO: implement build
      return CustomScrollView(
        slivers: <Widget>[
          new SliverAppBar(
            pinned: true,
            title: new Card(
                color: new Color.fromRGBO(250, 250, 250, 0.6),
                child: new FlatButton(
                  onPressed: () {
                    Navigator.pushNamed(context, '/search');
                  },
                  child: new Row(
                    mainAxisAlignment: MainAxisAlignment.center,
                    crossAxisAlignment: CrossAxisAlignment.center,
                    children: <Widget>[
                      new Icon(
                        Icons.search,
                        color: Colors.black,
                      ),
                      new Padding(padding: new EdgeInsets.only(right: 5.0)),
                      new Text('搜尋')
                    ],
                  ),
                )),
            titleSpacing: 5.0,
            backgroundColor: new Color.fromRGBO(244, 245, 245, 1.0),
          ),
          new SliverList(
              delegate: new SliverChildBuilderDelegate((context, index) {
            return new Container(
              color: Colors.white,
              padding: new EdgeInsets.only(top: 15.0,bottom: 15.0),
              margin: new EdgeInsets.only(bottom: 20.0),
              child: new Row(
                mainAxisAlignment: MainAxisAlignment.spaceEvenly,
                crossAxisAlignment: CrossAxisAlignment.center,
                children: <Widget>[
                  new FlatButton(
                      onPressed: null,
                      child: new Column(
                        children: <Widget>[
                          new Icon(
                            Icons.whatshot,
                            color: Colors.red,
                            size: 30.0,
                          ),
                          new Text('本週最熱')
                        ],
                      )),
                  new FlatButton(
                      onPressed: null,
                      child: new Column(
                        children: <Widget>[
                          new Icon(
                            Icons.collections,
                            color: Colors.green,
                            size: 30.0,
                          ),
                          new Text('收藏集')
                        ],
                      )),
                  new FlatButton(
                      onPressed: () {
                        Navigator.pushNamed(context, '/activities');
                      },
                      child: new Column(
                        children: <Widget>[
                          new Icon(
                            Icons.toys,
                            color: Colors.yellow,
                            size: 30.0,
                          ),
                          new Text('活動')
                        ],
                      )),
                ],
              ),
            );
          }, childCount: 1)),
          new SliverList(
              delegate: new SliverChildBuilderDelegate((context, index) {
            return new Container(
              padding: new EdgeInsets.all(10.0),
              decoration: new BoxDecoration(
                  border: new Border(
                      bottom: new BorderSide(width: 0.2, color: Colors.grey)),
                  color: Colors.white),
              child: new Row(
                mainAxisAlignment: MainAxisAlignment.spaceBetween,
                crossAxisAlignment: CrossAxisAlignment.center,
                children: <Widget>[
                  new Row(
                    mainAxisAlignment: MainAxisAlignment.start,
                    crossAxisAlignment: CrossAxisAlignment.center,
                    children: <Widget>[
                      new Icon(
                        Icons.whatshot,
                        color: Colors.red,
                      ),
                      new Padding(padding: new EdgeInsets.only(right: 5.0)),
                      new Text(
                        '熱門文章',
                        style: new TextStyle(fontSize: 14.0),
                      )
                    ],
                  ),
                  new Row(
                    children: <Widget>[
                      new Icon(
                        Icons.settings,
                        color: Colors.grey,
                      ),
                      new Padding(padding: new EdgeInsets.only(right: 5.0)),
                      new Text(
                        '定製熱門',
                        style: new TextStyle(fontSize: 14.0, color: Colors.grey),
                      )
                    ],
                  )
                ],
              ),
            );
          }, childCount: 1)),
          new SliverFixedExtentList(
              itemExtent: 100.0,
              delegate: new SliverChildBuilderDelegate((context, index) {
                var itemInfo = hotArticles[index];
                return createItem(itemInfo);
              }, childCount: hotArticles == null ? 0 : hotArticles.length)),
        ],
      );
    }
}
複製程式碼

這裡我們用的 CustomScrollViewSliver,語法啥的小夥伴們自己看文件了哈,就不解釋了。對於搜尋按鈕和活動按鈕,我這裡已經寫了跳轉路由,不急,我們一會兒就去實現。我們把單個文章的構建程式碼提出來,讓整體簡潔一點。

class DiscoveryPageState extends State<DiscoveryPage> {
  /*接著寫*/
  //單個熱門文章
    Widget createItem(itemInfo) {
      var publicTime = countTime(itemInfo['createdAt']);
      return new Container(
        padding: new EdgeInsets.only(top: 10.0, bottom: 10.0),
        decoration: new BoxDecoration(
            color: Colors.white,
            border: new Border(
                bottom: new BorderSide(width: 0.2, color: Colors.grey))),
        child: new FlatButton(
            onPressed: null,
            child: new Row(
              mainAxisAlignment: MainAxisAlignment.spaceBetween,
              crossAxisAlignment: CrossAxisAlignment.center,
              children: <Widget>[
                new Expanded(
                  child: new Column(
                    crossAxisAlignment: CrossAxisAlignment.start,
                    children: <Widget>[
                      new Text(
                        itemInfo['title'],
                        textAlign: TextAlign.left,
                        style: new TextStyle(
                          color: Colors.black,
                        ),
                        maxLines: 2,
                        overflow: TextOverflow.ellipsis,
                      ),
                      new Text(
                        '${itemInfo['collectionCount']}人喜歡 · ${itemInfo['user']['username']} · $publicTime',
                        textAlign: TextAlign.left,
                        style: new TextStyle(color: Colors.grey, fontSize: 12.0),
                        softWrap: true,
                      )
                    ],
                  ),
                ),
                itemInfo['screenshot'] != null
                    ? new Image.network(
                        itemInfo['screenshot'],
                        width: 100.0,
                      )
                    : new Container(
                        width: 0.0,
                        height: 0.0,
                      )
              ],
            )),
      );
    }
}
複製程式碼

這裡的單個文章有可能沒有截圖,所以寫個判斷。現在執行一下,如果你看到的介面長這樣,就OK了:

Flutter入門——山寨掘金(二)

二. 實現搜尋頁

我們先實現搜尋頁,在 pages 下新建 search.dart ,寫入下列程式碼:

import 'package:flutter/material.dart';
import 'package:flutter/cupertino.dart';
import 'package:http/http.dart' as http;
import 'dart:convert';
import 'dart:async';
import '../utils/countTime.dart';

class SearchPage extends StatefulWidget {
  @override
  SearchPageState createState() => new SearchPageState();
}

class SearchPageState extends State<SearchPage> {
  String searchContent;
  List searchResult;

  Future search(String query) {
    return http.get(
        'https://search-merger-ms.juejin.im/v1/search?query=$query&page=0&raw_result=false&src=web');
  }

  final TextEditingController controller = new TextEditingController();
}

複製程式碼

這裡我們申明兩個變數 searchContentsearchResult ,前者是搜尋內容,後者是結果列表,再申明一個 controller 用於控制輸入框。

看文件啊,同志們!

接著構建頁面:

class SearchPageState extends State<SearchPage> {
/*接著寫*/
  @override
  Widget build(BuildContext context) {
    // TODO: implement build
    return new CustomScrollView(
      slivers: <Widget>[
        new SliverAppBar(
            pinned: true,
            leading: new IconButton(
                icon: new Icon(Icons.chevron_left),
                onPressed: () {
                  Navigator.pop(context);
                }),
            title: new Text(
              '搜尋',
              style: new TextStyle(fontWeight: FontWeight.normal),
            ),
            centerTitle: true,
            iconTheme: new IconThemeData(color: Colors.blue),
            backgroundColor: new Color.fromRGBO(244, 245, 245, 1.0),
            bottom: new PreferredSize(
                child: new Container(
                  color: Colors.white,
                  padding: new EdgeInsets.all(5.0),
                  child: new Card(
                      color: new Color.fromRGBO(252, 252, 252, 0.6),
                      child: new Padding(
                        padding: new EdgeInsets.all(5.0),
                        child: new Row(
                          crossAxisAlignment: CrossAxisAlignment.center,
                          mainAxisAlignment: MainAxisAlignment.spaceBetween,
                          children: <Widget>[
                            new Expanded(
                              child: new TextField(
                                autofocus: true,
                                style: new TextStyle(
                                    fontSize: 14.0, color: Colors.black),
                                decoration: new InputDecoration(
                                  contentPadding: new EdgeInsets.all(0.0),
                                  border: InputBorder.none,
                                  hintText: '搜尋',
                                  prefixIcon: new Icon(
                                    Icons.search,
                                    size: 16.0,
                                    color: Colors.grey,
                                  ),
                                ),
                                onChanged: (String content) {
                                  setState(() {
                                    searchContent = content;
                                  });
                                },
                                onSubmitted: (String content) {
                                  search(content).then((response) {
                                    setState(() {
                                      searchResult =
                                          json.decode(response.body)['d'];
                                    });
                                  }, onError: (e) {
                                    throw Exception('Failed to load data');
                                  });
                                },
                                controller: controller,
                              ),
                            ),
                            searchContent == ''
                                ? new Container(
                                    height: 0.0,
                                    width: 0.0,
                                  )
                                : new InkResponse(
                                    child: new Icon(
                                      Icons.close,
                                    ),
                                    onTap: () {
                                      setState(() {
                                        searchContent = '';
                                        controller.text = '';
                                      });
                                    })
                          ],
                        ),
                      )),
                ),
                preferredSize: new Size.fromHeight(40.0))),
        searchResult == null
            ? new SliverFillRemaining(
                child: new Container(
                  color: Colors.white,
                ),
              )
            : new SliverList(
                delegate: new SliverChildBuilderDelegate((context, index) {
                var resultInfo = searchResult[index];
                return showResult(resultInfo);
              }, childCount: searchResult.length))
      ],
    );
  }
}

複製程式碼

這裡沒什麼特別的,小夥伴們看看程式碼就懂了,我們還是把搜尋結果單獨提出來:

class SearchPageState extends State<SearchPage> {
/*接著寫*/
//顯示搜尋結果
  Widget showResult(resultInfo) {
    var publicTime = countTime(resultInfo['createdAt']);
    return new Container(
      alignment: Alignment.centerLeft,
      padding: new EdgeInsets.all(10.0),
      decoration: new BoxDecoration(
          color: Colors.white,
          border: new Border(
              bottom: new BorderSide(width: 0.2, color: Colors.grey))),
      child: new FlatButton(
          onPressed: null,
          child: new Column(
            crossAxisAlignment: CrossAxisAlignment.start,
            mainAxisAlignment: MainAxisAlignment.start,
            children: <Widget>[
              new Text(
                resultInfo['title'],
                style: new TextStyle(color: Colors.black),
              ),
              new Text(
                '${resultInfo['collectionCount']}人喜歡 · ${resultInfo['user']['username']} · $publicTime',
                textAlign: TextAlign.left,
                style: new TextStyle(color: Colors.grey, fontSize: 12.0),
                softWrap: true,
              )
            ],
          )),
    );
  }
}

複製程式碼

至此,搜尋頁面寫完了,別忙執行啊,還沒寫路由呢。開啟 main.dart,引入 search.dart ,然後配置一下路由:

import 'package:flutter/material.dart';
import 'pages/index.dart';
import 'pages/search.dart';

void main() => runApp(new MyApp());

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return new MaterialApp(
      home: new IndexPage(),
      theme: new ThemeData(
          highlightColor: Colors.transparent,
          //將點選高亮色設為透明
          splashColor: Colors.transparent,
          //將噴濺顏色設為透明
          bottomAppBarColor: new Color.fromRGBO(244, 245, 245, 1.0),
          //設定底部導航的背景色
          scaffoldBackgroundColor: new Color.fromRGBO(244, 245, 245, 1.0),
          //設定頁面背景顏色
          primaryIconTheme: new IconThemeData(color: Colors.blue),
          //主要icon樣式,如頭部返回icon按鈕
          indicatorColor: Colors.blue,
          //設定tab指示器顏色
          iconTheme: new IconThemeData(size: 18.0),
          //設定icon樣式
          primaryTextTheme: new TextTheme(
              //設定文字樣式
              title: new TextStyle(color: Colors.black, fontSize: 16.0))),
      routes: <String, WidgetBuilder>{
        '/search': (BuildContext context) => SearchPage()
      },
    );
  }
}
複製程式碼

現在可以執行了,效果如下:點選進入搜尋詳情頁我就不做了,這些都留給小夥伴們練手吧:

Flutter入門——山寨掘金(二)

三. 實現活動頁

活動頁的實現和首頁一模一樣,程式碼我就不貼了,在 main.dart 配置一下就行:

import 'package:flutter/material.dart';
import 'pages/index.dart';
import 'pages/search.dart';
import 'pages/activities.dart';
void main() => runApp(new MyApp());

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return new MaterialApp(
      home: new IndexPage(),
      theme: new ThemeData(
          highlightColor: Colors.transparent,
          //將點選高亮色設為透明
          splashColor: Colors.transparent,
          //將噴濺顏色設為透明
          bottomAppBarColor: new Color.fromRGBO(244, 245, 245, 1.0),
          //設定底部導航的背景色
          scaffoldBackgroundColor: new Color.fromRGBO(244, 245, 245, 1.0),
          //設定頁面背景顏色
          primaryIconTheme: new IconThemeData(color: Colors.blue),
          //主要icon樣式,如頭部返回icon按鈕
          indicatorColor: Colors.blue,
          //設定tab指示器顏色
          iconTheme: new IconThemeData(size: 18.0),
          //設定icon樣式
          primaryTextTheme: new TextTheme(
              //設定文字樣式
              title: new TextStyle(color: Colors.black, fontSize: 16.0))),
      routes: <String, WidgetBuilder>{
        '/search': (BuildContext context) => SearchPage(),
        '/activities': (BuildContext context) => ActivitiesPage(),
      },
    );
  }
}

複製程式碼

效果如下:

Flutter入門——山寨掘金(二)

結尾叨叨

今天的內容不多,主要使用了新的元件和新的請求寫法,小夥伴們想實現什麼功能就動手吧,多多練習,今天就到這裡了。原始碼點這裡

相關文章