Flutter双栏滚动插件flutter_twin_scroller的使用

Flutter双栏滚动插件flutter_twin_scroller的使用

flutter_twin_scroller 是一个用于在 Flutter 中同步多个 ScrollController 滚动位置的插件。本文将介绍如何使用该插件实现双栏滚动效果。

iOS Demo

Android Demo

特性

  • 同步不同小部件的 ScrollController
  • 在页面之间同步 ScrollController
  • 支持 BouncingScrollPhysics(iOS)和 ClampingScrollPhysics(Android)滚动效果

要求

所有绑定到相同 TwinScrollController 的小部件必须具有相同的滚动区域(如果水平滚动,则宽度相同;如果垂直滚动,则高度相同)。

使用方法

步骤1:创建 TwinScrollController

final twinScrollController = TwinScrollController();

步骤2:使用 TwinScroller 小部件绑定 ScrollControllerTwinScrollController

TwinScroller(
    controller: twinScrollController,
    childScrollController: scrollController,
    child: ListView.builder(
        controller: scrollController,
        itemCount: 20,
        itemBuilder: (context, index) {
            return Padding(
                padding: const EdgeInsets.all(8.0),
                child: Row(
                    children: [
                        Text(
                            'Item: $index',
                            style: const TextStyle(color: Colors.black),
                        )
                    ],
                ),
            );
        },
    ),
),

所有绑定到相同 TwinScrollControllerScrollController 将具有相同的滚动位置。

完整示例

以下是一个完整的示例,展示了如何使用 flutter_twin_scroller 实现双栏滚动效果。

import 'package:flutter/material.dart';
import 'package:flutter_twin_scroller/flutter_twin_scroller.dart';
import 'dart:math' as math;

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({Key? key}) : super(key: key);

  [@override](/user/override)
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: const MyHomePage(title: 'TwinScroller Demo'),
    );
  }
}

class MyHomePage extends StatefulWidget {
  const MyHomePage({Key? key, required this.title}) : super(key: key);

  final String title;

  [@override](/user/override)
  State<MyHomePage> createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  final verticalTwinScrollController = TwinScrollController();
  final verticalScrollController1 = ScrollController();
  final verticalScrollController2 = ScrollController();

  final horizontalTwinScrollController = TwinScrollController();
  final horizontalScrollController1 = ScrollController();
  final horizontalScrollController2 = ScrollController();
  final horizontalScrollController3 = ScrollController();

  int stackIndex = 0;
  int _counter = 20;

  void _incrementCounter() {
    setState(() {
      _counter++;
    });
  }

  [@override](/user/override)
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(widget.title),
      ),
      body: IndexedStack(
        index: stackIndex,
        children: [
          Column(
            children: [
              TwinScroller(
                controller: horizontalTwinScrollController,
                childScrollController: horizontalScrollController1,
                child: Container(
                  height: 60,
                  color: Color((math.Random().nextDouble() * 0xFFFFFF).toInt()).withOpacity(0.3),
                  child: ListView.builder(
                    controller: horizontalScrollController1,
                    itemCount: _counter,
                    scrollDirection: Axis.horizontal,
                    itemBuilder: (context, index) {
                      return Container(
                        color: Color((math.Random().nextDouble() * 0xFFFFFF).toInt()).withOpacity(0.3),
                        child: Padding(
                          padding: const EdgeInsets.all(8.0),
                          child: Row(
                            children: [
                              Text(
                                'Item: $index',
                                style: const TextStyle(color: Colors.black),
                              )
                            ],
                          ),
                        ),
                      );
                    },
                  ),
                ),
              ),
              Flexible(
                child: Row(
                  children: [
                    Flexible(
                      child: TwinScroller(
                        controller: verticalTwinScrollController,
                        childScrollController: verticalScrollController1,
                        child: Container(
                          color: Color((math.Random().nextDouble() * 0xFFFFFF).toInt()).withOpacity(0.3),
                          child: ListView.builder(
                            controller: verticalScrollController1,
                            itemCount: _counter,
                            itemBuilder: (context, index) {
                              return Container(
                                color: Color((math.Random().nextDouble() * 0xFFFFFF).toInt()).withOpacity(0.3),
                                child: Padding(
                                  padding: const EdgeInsets.all(8.0),
                                  child: Row(
                                    children: [
                                      Text(
                                        'Item: $index',
                                        style: const TextStyle(color: Colors.black),
                                      )
                                    ],
                                  ),
                                ),
                              );
                            },
                          ),
                        ),
                      ),
                    ),
                    Flexible(
                      child: TwinScroller(
                        controller: verticalTwinScrollController,
                        childScrollController: verticalScrollController2,
                        child: Container(
                          color: Color((math.Random().nextDouble() * 0xFFFFFF).toInt()).withOpacity(0.1),
                          child: ListView.builder(
                            controller: verticalScrollController2,
                            itemCount: _counter,
                            itemBuilder: (context, index) {
                              return Container(
                                color: Color((math.Random().nextDouble() * 0xFFFFFF).toInt()).withOpacity(0.3),
                                child: Padding(
                                  padding: const EdgeInsets.all(8.0),
                                  child: Row(
                                    children: [
                                      Text(
                                        'Item: $index',
                                        style: const TextStyle(color: Colors.black),
                                      )
                                    ],
                                  ),
                                ),
                              );
                            },
                          ),
                        ),
                      ),
                    ),
                  ],
                ),
              ),
            ],
          ),
          Column(
            children: [
              TwinScroller(
                controller: horizontalTwinScrollController,
                childScrollController: horizontalScrollController2,
                child: Container(
                  height: 60,
                  color: Color((math.Random().nextDouble() * 0xFFFFFF).toInt()).withOpacity(0.3),
                  child: ListView.builder(
                    controller: horizontalScrollController2,
                    itemCount: _counter,
                    scrollDirection: Axis.horizontal,
                    itemBuilder: (context, index) {
                      return Container(
                        color: Color((math.Random().nextDouble() * 0xFFFFFF).toInt()).withOpacity(0.3),
                        child: Padding(
                          padding: const EdgeInsets.all(8.0),
                          child: Row(
                            children: [
                              Text(
                                'Item: $index',
                                style: const TextStyle(color: Colors.black),
                              )
                            ],
                          ),
                        ),
                      );
                    },
                  ),
                ),
              ),
              Expanded(
                child: Center(
                  child: Column(
                    mainAxisAlignment: MainAxisAlignment.center,
                    children: [
                      const Text(
                        'You have pushed the button this many times:',
                      ),
                      Text(
                        '$_counter',
                        style: Theme.of(context).textTheme.headlineMedium,
                      ),
                    ],
                  ),
                ),
              ),
            ],
          ),
          Column(
            children: [
              TwinScroller(
                controller: horizontalTwinScrollController,
                childScrollController: horizontalScrollController3,
                child: Container(
                  height: 60,
                  color: Color((math.Random().nextDouble() * 0xFFFFFF).toInt()).withOpacity(0.3),
                  child: ListView.builder(
                    controller: horizontalScrollController3,
                    itemCount: _counter,
                    scrollDirection: Axis.horizontal,
                    itemBuilder: (context, index) {
                      return Container(
                        color: Color((math.Random().nextDouble() * 0xFFFFFF).toInt()).withOpacity(0.3),
                        child: Padding(
                          padding: const EdgeInsets.all(8.0),
                          child: Row(
                            children: [
                              Text(
                                'Item: $index',
                                style: const TextStyle(color: Colors.black),
                              )
                            ],
                          ),
                        ),
                      );
                    },
                  ),
                ),
              ),
              Expanded(
                child: Center(
                  child: Column(
                    mainAxisAlignment: MainAxisAlignment.center,
                    children: [
                      MaterialButton(
                        child: const Text(
                          'Open Multiple TwinScrollers Page',
                          style: TextStyle(color: Colors.white),
                        ),
                        color: Colors.blue,
                        onPressed: () {
                          horizontalTwinScrollController.holdPositions();
                          Navigator.of(context).push(
                            MaterialPageRoute(
                                builder: (context) => ThreeTwinScrollerScreen(
                                      horizontalTwinScrollController: horizontalTwinScrollController,
                                    )),
                          );
                        },
                      ),
                    ],
                  ),
                ),
              ),
            ],
          ),
        ],
      ),
      bottomNavigationBar: BottomNavigationBar(
        currentIndex: stackIndex,
        onTap: (index) {
          setState(() {
            stackIndex = index;
          });
        },
        items: const [
          BottomNavigationBarItem(
            icon: Icon(
              Icons.view_list,
            ),
            label: 'Dual List',
          ),
          BottomNavigationBarItem(
            icon: Icon(
              Icons.view_list,
            ),
            label: 'Counter',
          ),
          BottomNavigationBarItem(
            icon: Icon(
              Icons.view_list,
            ),
            label: 'Multiple Page',
          ),
        ],
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: _incrementCounter,
        tooltip: 'Increment',
        child: const Icon(Icons.add),
      ),
    );
  }
}

class ThreeTwinScrollerScreen extends StatefulWidget {
  final TwinScrollController horizontalTwinScrollController;

  const ThreeTwinScrollerScreen(
      {Key? key, required this.horizontalTwinScrollController})
      : super(key: key);

  [@override](/user/override)
  State<ThreeTwinScrollerScreen> createState() => _ThreeTwinScrollerScreenState();
}

class _ThreeTwinScrollerScreenState extends State<ThreeTwinScrollerScreen> {
  final horizontalScrollController1 = ScrollController();
  final int _counter = 20;
  final verticalTwinScrollController = TwinScrollController();
  final verticalScrollController1 = ScrollController();
  final verticalScrollController2 = ScrollController();
  final verticalScrollController3 = ScrollController();
  final verticalScrollController4 = ScrollController();
  final verticalScrollController5 = ScrollController();

  [@override](/user/override)
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Multiple TwinScroller Scrolls'),
      ),
      body: Column(
        children: [
          TwinScroller(
            controller: widget.horizontalTwinScrollController,
            childScrollController: horizontalScrollController1,
            child: Container(
              height: 60,
              color: Color((math.Random().nextDouble() * 0xFFFFFF).toInt()).withOpacity(0.3),
              child: ListView.builder(
                controller: horizontalScrollController1,
                itemCount: _counter,
                scrollDirection: Axis.horizontal,
                itemBuilder: (context, index) {
                  return Container(
                    color: Color((math.Random().nextDouble() * 0xFFFFFF).toInt()).withOpacity(0.3),
                    child: Padding(
                      padding: const EdgeInsets.all(8.0),
                      child: Row(
                        children: [
                          Text(
                            'Item: $index',
                            style: const TextStyle(color: Colors.black),
                          )
                        ],
                      ),
                    ),
                  );
                },
              ),
            ),
          ),
          Flexible(
            child: Row(
              children: [
                Flexible(
                  child: TwinScroller(
                    controller: verticalTwinScrollController,
                    childScrollController: verticalScrollController1,
                    child: Container(
                      color: Color((math.Random().nextDouble() * 0xFFFFFF).toInt()).withOpacity(0.3),
                      child: ListView.builder(
                        controller: verticalScrollController1,
                        itemCount: _counter,
                        itemBuilder: (context, index) {
                          return Container(
                            color: Color((math.Random().nextDouble() * 0xFFFFFF).toInt()).withOpacity(0.3),
                            child: Padding(
                              padding: const EdgeInsets.symmetric(vertical: 9.0, horizontal: 8.0),
                              child: Row(
                                children: [
                                  Text(
                                    'Item: $index',
                                    style: const TextStyle(color: Colors.black),
                                  )
                                ],
                              ),
                            ),
                          );
                        },
                      ),
                    ),
                  ),
                ),
                Flexible(
                  child: TwinScroller(
                    controller: verticalTwinScrollController,
                    childScrollController: verticalScrollController2,
                    child: Container(
                      color: Color((math.Random().nextDouble() * 0xFFFFFF).toInt()).withOpacity(0.1),
                      child: ListView.builder(
                        controller: verticalScrollController2,
                        itemCount: _counter,
                        itemBuilder: (context, index) {
                          return Container(
                            color: Color((math.Random().nextDouble() * 0xFFFFFF).toInt()).withOpacity(0.3),
                            child: Padding(
                              padding: const EdgeInsets.symmetric(vertical: 10.0, horizontal: 8.0),
                              child: Row(
                                children: [
                                  Text(
                                    'Item: $index',
                                    style: const TextStyle(color: Colors.black),
                                  )
                                ],
                              ),
                            ),
                          );
                        },
                      ),
                    ),
                  ),
                ),
                Flexible(
                  child: TwinScroller(
                    controller: verticalTwinScrollController,
                    childScrollController: verticalScrollController3,
                    child: Container(
                      color: Color((math.Random().nextDouble() * 0xFFFFFF).toInt()).withOpacity(0.1),
                      child: ListView.builder(
                        controller: verticalScrollController3,
                        itemCount: _counter,
                        itemBuilder: (context, index) {
                          return Container(
                            color: Color((math.Random().nextDouble() * 0xFFFFFF).toInt()).withOpacity(0.3),
                            child: Padding(
                              padding: const EdgeInsets.symmetric(vertical: 11.0, horizontal: 8.0),
                              child: Row(
                                children: [
                                  Text(
                                    'Item: $index',
                                    style: const TextStyle(color: Colors.black),
                                  )
                                ],
                              ),
                            ),
                          );
                        },
                      ),
                    ),
                  ),
                ),
                Flexible(
                  child: TwinScroller(
                    controller: verticalTwinScrollController,
                    childScrollController: verticalScrollController4,
                    child: Container(
                      color: Color((math.Random().nextDouble() * 0xFFFFFF).toInt()).withOpacity(0.1),
                      child: ListView.builder(
                        controller: verticalScrollController4,
                        itemCount: _counter,
                        itemBuilder: (context, index) {
                          return Container(
                            color: Color((math.Random().nextDouble() * 0xFFFFFF).toInt()).withOpacity(0.3),
                            child: Padding(
                              padding: const EdgeInsets.symmetric(vertical: 12.0, horizontal: 8.0),
                              child: Row(
                                children: [
                                  Text(
                                    'Item: $index',
                                    style: const TextStyle(color: Colors.black),
                                  )
                                ],
                              ),
                            ),
                          );
                        },
                      ),
                    ),
                  ),
                ),
                Flexible(
                  child: TwinScroller(
                    controller: verticalTwinScrollController,
                    childScrollController: verticalScrollController5,
                    child: Container(
                      color: Color((math.Random().nextDouble() * 0xFFFFFF).toInt()).withOpacity(0.1),
                      child: ListView.builder(
                        controller: verticalScrollController5,
                        itemCount: _counter,
                        itemBuilder: (context, index) {
                          return Container(
                            color: Color((math.Random().nextDouble() * 0xFFFFFF).toInt()).withOpacity(0.3),
                            child: Padding(
                              padding: const EdgeInsets.symmetric(vertical: 13.0, horizontal: 8.0),
                              child: Row(
                                children: [
                                  Text(
                                    'Item: $index',
                                    style: const TextStyle(color: Colors.black),
                                  )
                                ],
                              ),
                            ),
                          );
                        },
                      ),
                    ),
                  ),
                ),
              ],
            ),
          ),
        ],
      ),
    );
  }
}

更多关于Flutter双栏滚动插件flutter_twin_scroller的使用的实战教程也可以访问 https://www.itying.com/category-92-b0.html

1 回复

更多关于Flutter双栏滚动插件flutter_twin_scroller的使用的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html


flutter_twin_scroller 是一个用于在 Flutter 中实现双栏滚动的插件。它允许你在同一屏幕上同时滚动两个不同的内容区域,这在某些特定场景(比如比较两个列表或显示相关但独立的内容)中非常有用。

安装 flutter_twin_scroller

首先,你需要在 pubspec.yaml 文件中添加 flutter_twin_scroller 依赖:

dependencies:
  flutter:
    sdk: flutter
  flutter_twin_scroller: ^0.1.0 # 请检查最新版本

然后运行 flutter pub get 来安装依赖。

使用 flutter_twin_scroller

以下是一个简单的示例,展示如何使用 flutter_twin_scroller 来实现双栏滚动。

import 'package:flutter/material.dart';
import 'package:flutter_twin_scroller/flutter_twin_scroller.dart';

void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(
          title: Text('Flutter Twin Scroller Example'),
        ),
        body: TwinScroller(
          leftChild: ListView.builder(
            itemCount: 20,
            itemBuilder: (context, index) {
              return ListTile(
                title: Text('Left Item $index'),
              );
            },
          ),
          rightChild: ListView.builder(
            itemCount: 20,
            itemBuilder: (context, index) {
              return ListTile(
                title: Text('Right Item $index'),
              );
            },
          ),
        ),
      ),
    );
  }
}

解释

  1. TwinScroller: 这是 flutter_twin_scroller 提供的主要组件。它需要两个子组件 leftChildrightChild,分别代表左侧和右侧的内容区域。

  2. ListView.builder: 在示例中,我们使用 ListView.builder 来生成可滚动的内容列表。你可以将任何可滚动的组件(如 SingleChildScrollViewGridView 等)作为 leftChildrightChild

  3. 同步滚动: TwinScroller 会自动处理左右两栏的同步滚动,确保它们在滚动时保持一致的滚动位置。

自定义配置

TwinScroller 还提供了一些可选的参数来自定义行为,例如:

  • scrollController: 可以使用自定义的 ScrollController 来控制滚动行为。
  • physics: 自定义滚动物理效果。
  • padding: 设置左右两栏的内边距。

示例:自定义滚动控制器

TwinScroller(
  scrollController: ScrollController(),
  physics: BouncingScrollPhysics(),
  padding: EdgeInsets.all(8.0),
  leftChild: ListView.builder(
    itemCount: 20,
    itemBuilder: (context, index) {
      return ListTile(
        title: Text('Left Item $index'),
      );
    },
  ),
  rightChild: ListView.builder(
    itemCount: 20,
    itemBuilder: (context, index) {
      return ListTile(
        title: Text('Right Item $index'),
      );
    },
  ),
)
回到顶部