Flutter黑馬頭條項目開發(二.底部切換導航和新聞頁面開發)

底部四個切換導航

它分爲首頁,問答,視頻和我的四大模塊
創建lib/home/home.dart首頁文件,使用的是bottomNavigationBar組件,官網也有介紹
在這裏插入圖片描述
它有一個onTap函數,這個函數會有一個index下標參數,同時準備四個模塊頁面news.dart,question.dart,video.dart和user.dart;裏面的切換過程:定義上面四個頁面數組,在body屬性中將動態下標傳遞過去,在事件裏更改這個下標即可,同時加上currentIndex動態屬性點擊效果,注意的是要用StatefulWidget組件.

新聞列表頁

頂部搜索

在news/news.dart文件中
頂部的搜索是寫入的樣式,實際點擊是進入另一個頁面的,具體實現步驟:在AppBar組件的title中給一個自定義SearchBox組件,在SearchBox.dart組件中寫我們的搜索內容:
在這裏插入圖片描述

頭部tabBar

使用的是DefaultTabController組件進行重新包裝,裏面放我們的Scaffold組件,具體如下:

import 'package:flutter/material.dart';

class TabBarBtn extends StatelessWidget {
  final List channel;
  TabBarBtn(this.channel);

  @override
  Widget build(BuildContext context) {
    return Container(
      color: Colors.white,
      child: TabBar(
        labelColor: Colors.black,
        unselectedLabelColor: Colors.black45,
        labelStyle: TextStyle(
          fontSize: 14.0
        ),
        indicatorColor: Colors.blueAccent,
        indicatorWeight: 3.0,
        indicatorSize: TabBarIndicatorSize.label,
        labelPadding: EdgeInsets.symmetric(horizontal: 20.0),
        isScrollable: true,
        tabs: channel.map((value){
          return Tab(text: value['name'],);
        }).toList()
      ),
    );
  }
}

主頁新聞列表news,tabContent

news.dart

class News extends StatefulWidget {
  @override
  _NewsState createState() => _NewsState();
}

class _NewsState extends State<News> {

  List channels = [];

  _getChannels () async{
    print('關閉之後刷新復機');
    var data = await PubMoudle.httpRequest('get', '/getchannels');
    // print(data.data['data']['channels']);
    setState(() {
      channels = data.data['data']['channels'];
    });
  }

  @override
  void initState() {
    super.initState();
    _getChannels();
  }

  @override
  Widget build(BuildContext context) {
    return channels.length == 0 ?SizedBox():DefaultTabController(
      length: channels.length,
      child: Scaffold(
        appBar: AppBar(
          title: SearchBox(),
          elevation: 0.0,
          bottom: PreferredSize(
            preferredSize: Size.fromHeight(50.0),
            child: TabBarBtn(channels)
          )
        ),
        body: TabBarView(
          children: channels.map((value){
            return TabBarContent(value['id']);
          }).toList()
        ),
        drawer: DrawerList(_getChannels),
      ),
    );
  }
}

tabContent.dart
使用listView組件,橫線使用SizeBox,圖片是使用Row結合網絡圖片,AspectRadio組件可以讓子組件等比縮放,第三個Row左文字右圖片方式佈局,左文字使用靈活佈局Expanded佈局,圖片使用SizeBox限制固定大小.

class TabBarContent extends StatefulWidget {
  final int id;
  TabBarContent(this.id);

  @override
  _TabBarContentState createState() => _TabBarContentState();
}

class _TabBarContentState extends State<TabBarContent> {

  List<Article> _list = [];
  int page = 1;
  ScrollController _controller = ScrollController();

  _getData([type]) async{
    var data = await PubMoudle.httpRequest('post', '/getarticles', {'id': widget.id, 'page': page});
    print(data.data['data']['results']);
    List jsonlist = data.data['data']['results'];
    List<Article> listData = jsonlist.map((value) => Article.fromJson(value)).toList();
    if(type == 1){
      setState(() {
        _list.addAll(listData);   
      });
    }else{
      setState(() {
        _list = listData;
      });
    }
  }

   Future _refresh() async{
    //走接口
    _getData();
    // setState(() {
        
    // });
  }

  @override
  void initState() {
    super.initState();
    _getData();

    _controller.addListener((){
      var maxScroll = _controller.position.maxScrollExtent;
      var pixels = _controller.position.pixels;
      if(maxScroll == pixels){
        //s刷新了
        _getData(1);
      }
    });
  }


  @override
  Widget build(BuildContext context) {
    return RefreshIndicator(
      onRefresh: _refresh,
      child: Padding(
        padding: EdgeInsets.all(15.0),
        child: ListView.builder(
          itemCount: _list.length,
          itemBuilder: (context, index){
            return GestureDetector(
              onTap: (){
                Navigator.push(context, MaterialPageRoute(
                  builder: (context) => DetailPage(_list[index].artId)
                ));
              },
              child: NewsItem(_list[index]),
            );
          },
          controller: _controller,
        ),
      ),
    );
  }
}

class NewsItem extends StatelessWidget {
  final Article article;
  NewsItem(this.article);
  
  @override
  Widget build(BuildContext context) {
    return Column(
      crossAxisAlignment: CrossAxisAlignment.start,
      children: <Widget>[
        article.imgType == 1?Row(
          crossAxisAlignment: CrossAxisAlignment.start,
          children: <Widget>[
            Expanded(
              child: Text(
                article.title,
                style: TextStyle(
                  color: Colors.black,
                  fontSize: 18.0
                ),
              ),
            ),
            //圖片
            SizedBox(
              width: 100.0,
              height: 100.0,
              child: Image.network(
                article.images[0],
                fit: BoxFit.cover,
              ),
            )
          ],
        ): Text(
          article.title,
          style: TextStyle(
            color: Colors.black,
            fontSize: 18.0
          ),
        ),
        SizedBox(height: 6.0,),
        article.imgType == 3 ? Row(
          children: article.images.map((value){
            return Expanded(
              child: AspectRatio(
                aspectRatio: 4/3,
                child: Image.network(
                  value,
                  fit: BoxFit.cover,
                ),
              ),
            );
          }).toList()
        ): SizedBox(height: 6.0,),
        RichText(
          text: TextSpan(
            text: '${article.isTop==1?"置頂  ":""}',
            style: TextStyle(
              color: Colors.red,
            ),
            children: [
              TextSpan(
                text: '${article.autName}  ',
                style: TextStyle(
                  color: Colors.grey,
                ),
              ),
              TextSpan(
                text: '${article.commCount}評論  ',
                style: TextStyle(
                  color: Colors.grey,
                ),
              ),
              TextSpan(
                text: timeago.format(DateTime.parse(article.pubdate)),
                style: TextStyle(
                  color: Colors.grey,
                ),
              )
            ]
          ),
        ),
        Divider(height: 30.0,),
      ],
    );
  }
}

渲染tab數據

在news.dart裏寫一個獲取數據的方_getChannels法,在初始化聲明週期裏調用這個方法,將數據放在TabBarBtn和TabBarContent裏,因爲第一次調用是空數據,所以做了三元判斷加載DefaultTabController,在map數組循環裏用toList()轉換

渲染新聞內容

根據上面tab個數渲染TabBarContent,將id傳遞過來,因爲涉及到數據所以需要改用StatefulWidget,在生命週期裏調用新聞數據_getData方法,同一頁面獲取別的組件id參數獲取方法用widget.id,在響應體裏判斷是否字符串和數字的方法,使用序列化,創建article.dart文件,裏面用類似構造函數的方法,在dart中跟類寫法很像,裏面用formJson方法用json進行處理,在響應體裏調用,用List listData保留起來,最後將數據放到頁面中,在數據定義的時候使用list

進行聲明,用setData進行賦值,這樣在頁面中就可以使用比較簡單的ListView.builder來進行構造,返回NewsItem(),定義NewsItem組件,裏面用數據填充,數字判斷置頂用三元判斷,圖片和左右佈局都有用到三元判斷.

下拉刷新

使用RefreshIndicator組件,把要刷新的組件寫在裏面,同時給一個方法,這個方法要用Future聲明,同時要用異步寫法,其實很簡單.

上拉加載更多

定義一個controller方法,用ScrollController定義,在初始化生命週期裏添加監聽,監聽最大上拉值和pixels值,相等代表刷新,根據每次page++在_getData裏使用addAll()方法添加數據.

時間格式化(裏面的多少時間前)

使用的是timego2.0插件,可以將正常時間轉爲多少時間前.
在這裏插入圖片描述
最終效果:
在這裏插入圖片描述

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章