Flutter 如何在颤振中使水平菜单与垂直产品列表自动滚动

Flutter 如何在颤振中使水平菜单与垂直产品列表自动滚动,flutter,flutter-layout,flutter-dependencies,flutter-animation,Flutter,Flutter Layout,Flutter Dependencies,Flutter Animation,我在页面顶部有一个水平分类菜单和一个长长的垂直滚动产品列表。目前我正在寻找的是自动化的水平菜单上的页面顶部,只要我去到各自的产品 因此,基本上我计划在顶部水平菜单中包含项目类别,所有包含这些类别的产品作为其结构的一部分,放在一个长列表中。因此,一旦用户(垂直)浏览这些产品,水平菜单中的类别就会突出显示,但一旦用户通过向下浏览项目列表转到产品的另一部分,则顶部水平菜单上的类别就会改变 Categ 1 Categ 2 Categ 3 Categ 4 -------------> Produc

我在页面顶部有一个水平分类菜单和一个长长的垂直滚动产品列表。目前我正在寻找的是自动化的水平菜单上的页面顶部,只要我去到各自的产品

因此,基本上我计划在顶部水平菜单中包含项目类别,所有包含这些类别的产品作为其结构的一部分,放在一个长列表中。因此,一旦用户(垂直)浏览这些产品,水平菜单中的类别就会突出显示,但一旦用户通过向下浏览项目列表转到产品的另一部分,则顶部水平菜单上的类别就会改变

Categ 1 Categ 2 Categ 3 Categ 4 ------------->

Product 1
Product 2
Product 3
Product 4
我需要帮助才能轻而易举地做到这一点

知道选项卡栏和嵌套滚动视图,但这不是我想要的,我需要它们位于同一页面上,并且我不想根据选项卡的不同来区分产品。我希望在一个大列表中的同一页上的所有产品,只希望自动突出显示类别(取决于您是哪种产品)。我还需要帮助滚动产品到各自的位置,只要我点击该类别


感谢您的帮助

您可以设置类别和产品列表的
项目范围
,并使用它计算滚动偏移量以检测当前产品列表项目

样本

import 'dart:math';

import 'package:flutter/material.dart';

Future<void> main() async {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      debugShowCheckedModeBanner: false,
      home: HomePage(),
    );
  }
}

class HomePage extends StatefulWidget {
  @override
  _HomePageState createState() => _HomePageState();
}

class _HomePageState extends State<HomePage> {
  final Map<String, List<String>> _categories = <String, List<String>>{};
  final List<int> _categoryOffsets = <int>[];

  final ScrollController _categoriesController = ScrollController();
  final ScrollController _productsController = ScrollController();

  final int _categoryItemExtent = 150;
  final int _productItemExtent = 100;

  int _categoryIdx = 0;

  @override
  void initState() {
    super.initState();
    _productsController.addListener(_onProductsScroll);

    for (int i = 1; i <= 10; i++) {
      int productCnt = Random().nextInt(10);
      productCnt = productCnt == 0 ? 1 : productCnt;

      _categories['Category $i'] =
          List.generate(productCnt, (int j) => 'Product ${i * 10 + j}');

      final int prevOffset = i == 1 ? 0 : _categoryOffsets[i - 2];
      _categoryOffsets.add(prevOffset + (_productItemExtent * productCnt));
    }
  }

  @override
  Widget build(BuildContext context) {
    final List<String> allProducts = _categories.entries.fold<List<String>>(
      <String>[],
      (previousValue, element) {
        previousValue.addAll(element.value);
        return previousValue;
      },
    );

    return Scaffold(
      body: Column(
        children: <Widget>[
          Expanded(
            flex: 1,
            child: ListView.builder(
              controller: _categoriesController,
              scrollDirection: Axis.horizontal,
              itemExtent: _categoryItemExtent.toDouble(),
              itemCount: _categories.length,
              itemBuilder: (_, int i) => Card(
                color: _categoryIdx == i ? Colors.green : null,
                child: Text(_categories.keys.elementAt(i)),
              ),
            ),
          ),
          Expanded(
            flex: 3,
            child: ListView.builder(
              controller: _productsController,
              itemExtent: _productItemExtent.toDouble(),
              itemCount: allProducts.length,
              itemBuilder: (_, int i) {
                return Card(
                  child: Text(allProducts[i]),
                );
              },
            ),
          ),
        ],
      ),
    );
  }

  void _onProductsScroll() {
    final double offset = _productsController.offset;

    for (int i = 0; i < _categoryOffsets.length; i++) {
      if (offset <= _categoryOffsets[i]) {
        if (_categoryIdx != i) {
          print('Scroll to category $i');
          _categoriesController.animateTo(
            (i * _categoryItemExtent).toDouble(),
            duration: const Duration(milliseconds: 500),
            curve: Curves.easeInOut,
          );

          setState(() => _categoryIdx = i);
        }
        break;
      }
    }
  }

  @override
  void dispose() {
    _categoriesController.dispose();
    _productsController.removeListener(_onProductsScroll);
    _productsController.dispose();
    super.dispose();
  }
}
import'dart:math';
进口“包装:颤振/材料.省道”;
Future main()异步{
runApp(MyApp());
}
类MyApp扩展了无状态小部件{
@凌驾
小部件构建(构建上下文){
返回材料PP(
debugShowCheckedModeBanner:false,
主页:主页(),
);
}
}
类主页扩展了StatefulWidget{
@凌驾
_HomePageState createState()=>\u HomePageState();
}
类_HomePageState扩展状态{
最终地图_分类={};
最终列表_categoryoffset=[];
最终ScrollController_categoriesController=ScrollController();
最终ScrollController_productsController=ScrollController();
最终int_分类项目范围=150;
最终整数_productItemExtent=100;
int _categoryIdx=0;
@凌驾
void initState(){
super.initState();
_productsController.addListener(\u onProductsScroll);
对于(inti=1;i'乘积${i*10+j}');
最终int prevOffset=i==1?0:_categoryOffset[i-2];
_添加(prevOffset+(_productItemExtent*productCnt));
}
}
@凌驾
小部件构建(构建上下文){
最终列表所有产品=_categories.entries.fold(
[],
(以前的值,元素){
previousValue.addAll(element.value);
返回以前的值;
},
);
返回脚手架(
正文:专栏(
儿童:[
扩大(
弹性:1,
子项:ListView.builder(
控制器:_分类控制器,
滚动方向:轴水平,
itemExtent:\u categoryItemExtent.toDouble(),
itemCount:_categories.length,
itemBuilder:(_,int i)=>卡(
颜色:_categoryIdx==i?颜色。绿色:null,
子:文本(_categories.keys.elementAt(i)),
),
),
),
扩大(
弹性:3,
子项:ListView.builder(
控制器:\产品控制器,
itemExtent:\u productItemExtent.toDouble(),
itemCount:allProducts.length,
itemBuilder:(u,int i){
回程卡(
子项:文本(所有产品[i]),
);
},
),
),
],
),
);
}
void _onProductsScroll(){
最终双偏移=_productsController.offset;
对于(int i=0;i<_categoryOffsets.length;i++){
if(偏移量_categoryIdx=i);
}
打破
}
}
}
@凌驾
无效处置(){
_categoriesController.dispose();
_productsController.removeListener(_onProductsScroll);
_productsController.dispose();
super.dispose();
}
}

哇,这正是我想要的。非常感谢!祝你好运