Flutter黑马头条项目开发(二.底部切换导航和新闻页面开发)

底部四个切换导航

它分为首页,问答,视频和我的四大模块
创建lib/home/home.dart首页文件,使用的是bottomNavigationBar组件,官网也有介绍
Flutter黑马头条项目开发(二.底部切换导航和新闻页面开发)_第1张图片
它有一个onTap函数,这个函数会有一个index下标参数,同时准备四个模块页面news.dart,question.dart,video.dart和user.dart;里面的切换过程:定义上面四个页面数组,在body属性中将动态下标传递过去,在事件里更改这个下标即可,同时加上currentIndex动态属性点击效果,注意的是要用StatefulWidget组件.

新闻列表页

顶部搜索

在news/news.dart文件中
顶部的搜索是写入的样式,实际点击是进入另一个页面的,具体实现步骤:在AppBar组件的title中给一个自定义SearchBox组件,在SearchBox.dart组件中写我们的搜索内容:
Flutter黑马头条项目开发(二.底部切换导航和新闻页面开发)_第2张图片

头部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插件,可以将正常时间转为多少时间前.
Flutter黑马头条项目开发(二.底部切换导航和新闻页面开发)_第3张图片
最终效果:
Flutter黑马头条项目开发(二.底部切换导航和新闻页面开发)_第4张图片

你可能感兴趣的:(十八.Flutter)