Flutter实现带导航栏的PageView页面

1,727 阅读3分钟

一.效果图

新闻资讯页面

二.页面分析

这里我们只用关注资讯页面就行,资讯页面大概可以分为两个部分:

1.顶部导航栏

顶部导航栏有3个固定的tab,选中的时候字体变大,并且改变颜色,如果直接使用系统的TabBar控件的话就不能改变字体大小了,所以这里自定义导航栏,可以自己来实现想要的效果。如果Tab是动态的话可以使用横向的ListView,这里由于只有固定的3个所以直接使用Row嵌套3个Text来实现这个导航栏。

2.页面body

页面主题可以直接使用ListView控件来实现,这里主要item布局样式:

1.推荐页面可以看出,分两种情况,一种是无图,一种是有图片的展示,这里最多展示3张图片,根据接口返回的图片集合来判断是否有图片,然后分别加载不同的控件。所以推荐页面item样式大概就是Colum+Text+Image+Row+Text。

2.城市页面和导购页面item样式是一致的,但是和推荐页面的样式还是有区别,推荐页面图片是在中间,但是这两个页面的图片是在右边,所以整体是在右布局。

三.码代码

1.构建导航栏

margin:设置距离顶部的间距为状态栏的高度。 height:设置导航栏高度。 tabText():Text公共属性

Container:是常用的容器控件之一,只包含一个子控件,用来定位和修饰子控件,比如形状和背景颜色等。

SizeBox: 比较常用的控件,只包含一个子控件,用来限制子控件的大小。

Expanded:包含一个子控件,默认不带其他参数的情况下,用来充满页面剩余位置,类似于android里面的weight,不过要注意的是使用Expanded的时候,父组件的尺寸应该是可计算的或者固定值,不能是无限制,不然程序没法知道,就会包错。

PageView通过 currentIndex来关联更新 顶部text和主题页面之间的切换,滑动切换页面的时候,同步更新顶部tab导航栏。

class TabBarInfoPageState extends State<TabBarInfoPage> {
  int currentIndex = 0;

  @override
  Widget build(BuildContext context) {
    return Container(
        color: Colors.white,
        child: Column(children: [
          Container(
              margin: EdgeInsets.only(top: ScreenUtil().statusBarHeight),
              height: 80.h,
              child: Row(
                children: [
                  SizedBox(width: 20.w),
                  tabText("推荐", 0),
                  SizedBox(width: 50.w),
                  tabText("武汉", 1),
                  SizedBox(width: 50.w),
                  tabText("导购", 2)
                ],
              )
          ),
          Expanded(
              child: PageView.builder(
                  onPageChanged: ((int index) => setState(() {
                        currentIndex = index;
                      })),
                  itemCount: 3,
                  itemBuilder: (context, index) {
                    return InfoListPage(index + 1);
                  }))
        ]));
  }

  /// 导航栏文本样式定义
  Text tabText(String text, int index) {
    return Text(text,
        style: TextStyle(
            color: currentIndex == index ? Colors.black : Colors.grey,
            fontSize: currentIndex == index ? 40.sp : 30.sp));
  }
2.构建资讯列表

资讯列表分为两种样式,一种是多张图片,并且图片在中间,另外一种是单张图片,并且图片在右边,

  /// 推荐tabUI样式
  Widget recommendWidget(InfoMessageDataActivityData info) {
    return Column(crossAxisAlignment: CrossAxisAlignment.start, children: [
      Text(info.msgTitle.trim(),
          textAlign: TextAlign.left, style: TextStyle(fontSize: 30.sp)),
      SizedBox(height: 20.h),
      Row(
        children: images(info.msgTitleImgList),
        mainAxisAlignment: MainAxisAlignment.spaceBetween,
      ),
      SizedBox(
        height: 20.h,
      ),
      Row(children: [
        Text(
          info.sourceInfo,
          style: TextStyle(color: Colors.grey),
        ),
        Text("${info.clickNum}阅读", style: TextStyle(color: Colors.grey))
      ], mainAxisAlignment: MainAxisAlignment.spaceBetween),
      SizedBox(height: 20.h),
      Divider(
        height: 1.h,
        indent: 0,
        color: Colors.grey,
      )
    ]);
  }
Widget shoppingWidget(InfoMessageDataActivityData info) {
    List<String> images = info.msgTitleImgList;
    return Column(crossAxisAlignment: CrossAxisAlignment.start, children: [
      Row(crossAxisAlignment: CrossAxisAlignment.start, children: [
        Expanded(
            child: SizedBox(
                height: 150.h,
                child: Column(
                    mainAxisAlignment: MainAxisAlignment.spaceBetween,
                    crossAxisAlignment: CrossAxisAlignment.start,
                    children: [
                      Text(info.msgTitle,
                          textAlign: TextAlign.left,
                          style: TextStyle(fontSize: 30.sp)),
                      Row(
                          mainAxisAlignment: MainAxisAlignment.spaceBetween,
                          children: [
                            Text(
                              info.sourceInfo,
                              style: TextStyle(color: Colors.grey),
                            ),
                            Text("${info.clickNum}阅读",
                                style: TextStyle(color: Colors.grey))
                          ]),
                    ]))),
        images == null
            ? Container()
            : Padding(
            padding: EdgeInsets.only(left: 15.w),
            child: ClipRRect(
              child: Image.network(info.msgTitleImgList[0],
                  width: 220.w, height: 150.h, fit: BoxFit.cover),
              borderRadius: BorderRadius.circular(6),
            ))
      ]),
      SizedBox(height: 20.h),
      Divider(
        height: 1.h,
        indent: 0,
        color: Colors.grey,
      )
    ]);
  }