flutter 仿淘宝推荐二级分类效果

先看效果

一开始 用的PageView 做的, 然后重写PageScrollPhysics一顿魔改, 最后发现还是有一些小bug。 后面又想到pageview 能做,listview肯定也能做,最后用ListView加GridView 把功能实现了。

listview 实现pageview 的分页滑动效果只需要给ListView设置 physics: const PageScrollPhysics()。

做一个功能最重要的是拆分。

1:第一页最多展示5个半,(其实那半个是第二页的一部分)第二页后最多展示 15个

2:高度随着滑动动态变化

高度变化很简单只要最外层的widget 高度是动态的就行

复制代码
Container(
                    height: state.height, // 动态变化
                    decoration: const BoxDecoration(
                      color: Colors.white,
                      // borderRadius:
                      //     BorderRadius.only(bottomLeft: Radius.circular(8), bottomRight: Radius.circular(8)),
                    ),
                    child: _buildSubcategory(context),
                  )

_buildSubcategory 这里主要有一些数学计算,因为数据多少是不确定的,第一页只显示五个,默认第二页最多显示15个,后面第三页等等都是默认最多显示15个,当然子分类可能没那么多,基本就两页,但是公式可以通用,想分几页就几页,能支持就支持呗。

复制代码
 /// 构建子分类
  Widget _buildSubcategory(BuildContext context) => Stack(
        children: <Widget>[
          SizedBox(
            width: ScreenUtil.width,
            child:  ListView.builder(
              key: ValueKey("ListView$id"),
              addAutomaticKeepAlives: true,
              padding: EdgeInsets.zero,
              scrollDirection: Axis.horizontal,
              physics:  const PageScrollPhysics(),
              // physics: const NoInertiaScrollPhysics(),
              itemCount: state.listSubcategoryEntity.length < 6
                  ? 1
                  : ((state.listSubcategoryEntity.length - 5) / 15).ceil() + 1,
              controller: controller.scrollController,
              itemBuilder: (context, index) {
                List<SubcategoryEntity> list = [];
                if (index == 0) {
                  if(state.listSubcategoryEntity.length>5){
                    list.addAll(state.listSubcategoryEntity.sublist(0, 5));
                  }else{
                    list.addAll(state.listSubcategoryEntity);
                  }
                } else {
                  list.addAll(state.listSubcategoryEntity.sublist(
                      5 + (index - 1) * 15,
                      state.listSubcategoryEntity.length > (5 + index * 15)
                          ? 5 + index * 15
                          : state.listSubcategoryEntity.length));
                }
                return _buildSubcategoryWidget(context, index, list);
              },),
          ),
          Align(
            alignment: Alignment.bottomCenter,
            child: widgetBuilder(
              id: DemandListPageWidgetId.subcategoryIndicator,
              builder: () => _buildSubcategoryIndicatorWidget(
                  state.listSubcategoryEntity.length < 6
                      ? 1
                      : ((state.listSubcategoryEntity.length - 5) / 15).ceil() +
                          1,
                  state.index),
            ),
          )
        ],
      );

至于第一页怎么漏出第二页的数据,很简单 把第一页的width宽度设置小一点就能漏出第二页。

为什么是ScreenUtil.width-40?你想减多少都可以。。。我只是觉得减40漏出的部分更好看一些。

复制代码
 _buildSubcategoryWidget(
      BuildContext context, int indexs, List<SubcategoryEntity> list) {
    return SizedBox(
      width:  state.listSubcategoryEntity.length >5&&indexs==0?ScreenUtil.width-40:ScreenUtil.width,
      child: GridView.builder(
        key: ValueKey("GridView$id"),
        physics: const NeverScrollableScrollPhysics(),
        shrinkWrap: true,
        padding:
        state.listSubcategoryEntity.length >5&&indexs==0?const EdgeInsets.only(left: 10 , top: 8):
        const EdgeInsets.only(left: 0, top: 8),
        gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
          crossAxisCount: 5,
          // crossAxisSpacing: 5,
          childAspectRatio:1.1,
          // mainAxisSpacing: 2
        ),
        itemBuilder: (BuildContext context, int index) {
          return Ripple(
              onTap: () {
                controller.push(url: list[index].jumpUrl ?? "");
              },
              child: Column(
                mainAxisSize: MainAxisSize.min,
                children: [
                  ImageBuild.network(list[index].image?.small ?? '',
                      fit: BoxFit.fill,
                      size: const Size(35, 35),
                      placeholder: AppImage.commonPlaceholderIconSmall),
                  const SizedBox(
                    height: 2.0,
                  ),
                  TextBuild.text(
                    list[index].title?.content ?? "",
                    style: AppTextStyleData( 
                      fontWeight: AppTextStyle.regular,
                      fontSize: 14,
                      color: AppColorData.hex(
                          list[index].title?.fontColor ?? "#1E1E1E"),
                    ),
                    textAlign: TextAlign.center,
                  ),
                ],
              ));
        },
        itemCount: indexs == 0
            ? (state.listSubcategoryEntity.length < 5
            ? state.listSubcategoryEntity.length
            : 5)
            : (state.listSubcategoryEntity.length > 5 + indexs * 15
            ? 15
            : (state.listSubcategoryEntity.length - (5 + (indexs - 1) * 15))),
      ),
    );
  }

主要是监听滑动事件,然后动态改变高度

复制代码
NotificationListener<ScrollNotification>(
        onNotification: (ScrollNotification sn) {
     if (sn.metrics.axis == Axis.horizontal) {
            if (sn is ScrollEndNotification) {
               // var currentPage = (math.max(0.0, clampDouble(sn.metrics.pixels, sn.metrics.minScrollExtent, sn.metrics.maxScrollExtent)) /
               //     math.max(1.0, ScreenUtil.width)).round();
               // state.index = currentPage;
               // controller.widgetRebuild(DemandListPageWidgetId.subcategoryIndicator);
            } else {
              double progress = sn.metrics.pixels;
              if (0 < progress && progress <= ScreenUtil.width) {
                var multiple = state.listSubcategoryEntity.length>15 ? 1:0;
                if(state.listSubcategoryEntity.length<11){
                  state.height = 80;
                }else{
                  state.height =  progress/ScreenUtil.width * ((multiple+1)*78)+80;
                }
              } else if (progress == 0) {
                state.height = 80;
              }
              var currentPage = (math.max(0.0, clampDouble(sn.metrics.pixels, sn.metrics.minScrollExtent, sn.metrics.maxScrollExtent)) /
                  math.max(1.0, ScreenUtil.width)).round();
              state.index = currentPage; // 记录当前页数
              controller.widgetRebuild(DemandListPageWidgetId.subcategory); //刷新页面,自行替换
            }
          }
          return false;
        },
        child:    child );
相关推荐
Geek_Vison6 小时前
保险APP已经用了Flutter,还能接入小程序吗?
flutter·小程序·uni-app·mpaas·小程序容器
FungLeo6 小时前
成为全栈·Flutter App 篇·go_router 与四 Tab App Shell:保留导航状态、深链和登录回跳
flutter·移动开发·成为全栈·go_router·路由设计·深链接
m0_738185827 小时前
Flutter 鸿蒙化实战:screen_protector 适配 OpenHarmony,防截屏/防泄露
flutter·华为·harmonyos·鸿蒙
梦想的颜色12 小时前
【编程实战】AI 时代 APP 开发全栈硬核指南:技术选型 + AI 架构 + 模型落地全维度决策
python·flutter·react native·react.js·ai·桌面应用·milvus
yuanlaile1 天前
2026年全新Flutter 跨平台开发全栈学习资料整理
flutter·getx·鸿蒙 next·flutter开发鸿蒙·flutter入门实战·flutter全栈
恋猫de小郭2 天前
Genkit Dart 1.0 发布,Flutter 原生的 AI Agent 终于完整了
android·前端·flutter
第10086个小白2 天前
flutter桌面端打包方式(Inno Setup Compiler)版本
前端·flutter
陆断枫2 天前
Flutter 3.47 动画引擎盘点:Impeller 全平台,和那些"新特性"
flutter
程序员老刘2 天前
Flet 1.0 思路很好,可惜来晚了
flutter·ai编程·客户端
m0_738185823 天前
Flutter 鸿蒙化实战:qr_code_scanner_plus 适配 OpenHarmony,二维码扫描
flutter·华为·harmonyos·鸿蒙