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 );
相关推荐
lqj_本人1 小时前
鸿蒙next选择 Flutter 开发跨平台应用的原因
flutter·华为·harmonyos
lqj_本人5 小时前
Flutter&鸿蒙next 状态管理框架对比分析
flutter·华为·harmonyos
起司锅仔9 小时前
Flutter启动流程(2)
flutter
hello world smile12 小时前
最全的Flutter中pubspec.yaml及其yaml 语法的使用说明
android·前端·javascript·flutter·dart·yaml·pubspec.yaml
lqj_本人12 小时前
Flutter 的 Widget 概述与常用 Widgets 与鸿蒙 Next 的对比
flutter·harmonyos
iFlyCai12 小时前
极简实现酷炫动效:Flutter隐式动画指南第二篇之一些酷炫的隐式动画效果
flutter
lqj_本人12 小时前
Flutter&鸿蒙next 中使用 MobX 进行状态管理
flutter·华为·harmonyos
lqj_本人13 小时前
Flutter&鸿蒙next 中的 setState 使用场景与最佳实践
flutter·华为·harmonyos
hello world smile14 小时前
Flutter常用命令整理
android·flutter·移动开发·android studio·安卓
lqj_本人17 小时前
Flutter&鸿蒙next 中的 Expanded 和 Flexible 使用技巧详解
flutter·harmonyos