Когда я прокручиваю один вид прокрутки колеса списка, другой список либо отстает, либо прокручивается не плавно.
https://pub.dev/packages/linked_scroll_controller позволяет синхронизировать списки, но не поддерживает FixedExtendScrollPhysics.
Выход : -
https://pub.dev/packages/linked_scroll_controller отлично работает, если мы используем ScrollPhysics, но выдает ошибку при использовании с виджетом, который использует FixedExtendScrollPhysics. Я хочу, чтобы оба списка перемещались Синхронизация, то есть, если я перемещаю зеленый список, я хочу, чтобы красный список перемещался одновременно, и наоборот
Код :
import 'dart:math';
import 'package:flutter/material.dart';
void main() => runApp(const MyApp());
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'List',
theme: ThemeData(
primarySwatch: Colors.blue,
),
debugShowCheckedModeBanner: false,
home: const List(),
);
}
}
class List extends StatefulWidget {
const List({Key? key}) : super(key: key);
@override
_ListState createState() => _ListState();
}
class _ListState extends State<List> {
final scrollController = FixedExtentScrollController();
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text("List"),
backgroundColor: Colors.green,
),
body: Row(
children: [
SizedBox(
height: 600,
width: 300,
child: ListWheelScrollView(
itemExtent: 100,
physics: const FixedExtentScrollPhysics(),
onSelectedItemChanged: (value) {
setState(() {
scrollController.animateToItem(value,
duration: const Duration(milliseconds: 200),
curve: Curves.easeInOut);
});
},
children: [
for (int i = 0; i < 5; i++) ...[
Container(
color: Colors.green,
height: 50,
width: 50,
)
]
]),
),
SizedBox(
height: 600,
width: 300,
child: ListWheelScrollView(
controller: scrollController,
physics: const FixedExtentScrollPhysics(),
itemExtent: 100,
children: [
for (int i = 0; i < 5; i++) ...[
Container(
color: Colors.red,
height: 50,
width: 50,
)
]
]),
)
],
));
}
}
@SeptianDika Я использовал провайдера, но происходит то же самое, и я не думаю, что всего два виджета с использованием setState — плохая идея.
import 'package:flutter/material.dart';
class MyWidget extends StatefulWidget {
MyWidget({Key? key}) : super(key: key);
@override
State<MyWidget> createState() => _MyWidgetState();
}
class _MyWidgetState extends State<MyWidget> {
late ScrollController _controllerA;
late ScrollController _controllerB;
/// FixedExtentScrollController
/// late FixedExtentScrollController _controllerA;
/// late FixedExtentScrollController _controllerB;
@override
void initState() {
super.initState();
_controllerA = ScrollController();
_controllerB = ScrollController();
/// _controllerA = FixedExtentScrollController();
/// _controllerB = FixedExtentScrollController();
_controllerA.addListener(() {
if (_controllerA.position.hasPixels) {
_controllerB.jumpTo(_controllerA.offset);
}
});
/// if you neet bind _controllerB to _controllerA
/// _controllerB.addListener(() {
/// if (_controllerB.position.hasPixels) {
/// _controllerA.jumpTo(_controllerB.offset);
/// }
///});
}
@override
void dispose() {
_controllerA.dispose();
_controllerB.dispose();
super.dispose();
}
Widget _listView(Color color, ScrollController controller) {
var width = MediaQuery.of(context).size.width / 2;
return Container(
width: width,
child: ListView.builder(
controller: controller,
shrinkWrap: true,
itemCount: 100,
itemExtent: 50,
itemBuilder: (context, index) {
return Container(
decoration: BoxDecoration(
color: color, border: Border.all(color: Colors.white)),
);
}),
);
}
/// ListWheelScrollView
/// Widget _listView(Color color, FixedExtentScrollController controller) {
/// var width = MediaQuery.of(context).size.width / 2;
/// return Container(
/// width: width,
/// child: ListWheelScrollView(
/// physics: FixedExtentScrollPhysics(),
/// controller: controller,
/// itemExtent: 50,
/// children: [
/// ...List.generate(
/// 100,
/// (index) => Container(
/// decoration: BoxDecoration(
/// color: color,
/// border: Border.all(color: Colors.white)),
/// ))
/// ]),
/// );
/// }
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
body: Row(
children: [
_listView(Colors.red, _controllerA),
_listView(Colors.yellow, _controllerB),
],
)),
);
}
}
Этот код не работает с FixedExtendScrollPhysics
Нет, работает нормально. Я добавил реализацию ListWheelScrollView, удачи~~~
Это работает, но если я постепенно прокручиваю список, он отстает
Попробуйте разделить два controller
и добавить слушателя следующим образом:
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'List',
theme: ThemeData(
primarySwatch: Colors.blue,
),
debugShowCheckedModeBanner: false,
home: const List(),
);
}
}
class List extends StatefulWidget {
const List({Key? key}) : super(key: key);
@override
_ListState createState() => _ListState();
}
class _ListState extends State<List> {
final scrollController1 = FixedExtentScrollController();
final scrollController2 = FixedExtentScrollController();
@override
void initState() {
super.initState();
scrollController1.addListener(() {
if (scrollController1.position.hasPixels) {
scrollController2.animateTo(
scrollController1.offset,
duration: const Duration(milliseconds: 10), //adjust delay you need
curve: Curves.easeInOut,
);
}
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text("List"),
backgroundColor: Colors.green,
),
body: Row(
children: [
SizedBox(
height: 600,
width: 150,
child: ListWheelScrollView(
itemExtent: 100,
controller: scrollController1,
physics: const FixedExtentScrollPhysics(),
children: [
for (int i = 0; i < 5; i++) ...[
Container(
color: Colors.green,
height: 50,
width: 50,
)
]
]),
),
SizedBox(
height: 600,
width: 150,
child: ListWheelScrollView(
controller: scrollController2,
physics: const FixedExtentScrollPhysics(),
itemExtent: 100,
children: [
for (int i = 0; i < 5; i++) ...[
Container(
color: Colors.red,
height: 50,
width: 50,
)
]
]),
)
],
));
}
}
Список все еще отстает
Действительно интересный вопрос. Проблема заключалась в синхронизации обоих прокруток. Я внес несколько изменений в ваш код для достижения желаемого результата.
Основная идея состоит в том, чтобы удалить слушателя с другой прокрутки, прежде чем форсировать пиксели. После прокрутки добавьте того же слушателя. Но поскольку это происходит мгновенно, а фактическая прокрутка иногда происходит в будущем, они не полностью перекрываются.
Поэтому мне пришлось ввести CancelableCompleter
из асинхронной библиотеки, чтобы убедиться, что операция добавления не произойдет, если произойдет другое событие прокрутки.
С forcePixels
прокрутка к другому колесу не откладывается, поэтому CancelableCompleter не требуется.
// ignore_for_file: invalid_use_of_protected_member
import 'package:flutter/material.dart';
void main() => runApp(const MyApp());
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'List',
theme: ThemeData(
primarySwatch: Colors.blue,
),
debugShowCheckedModeBanner: false,
home: const List(),
);
}
}
class List extends StatefulWidget {
const List({Key? key}) : super(key: key);
@override
_ListState createState() => _ListState();
}
class _ListState extends State<List> {
final _firstScrollController = FixedExtentScrollController();
final _secondScrollController = FixedExtentScrollController();
@override
void initState() {
super.initState();
_firstScrollController.addListener(_firstScrollListener);
_secondScrollController.addListener(_secondScrollListener);
}
@override
void dispose() {
_firstScrollController
..removeListener(_firstScrollListener)
..dispose();
_secondScrollController
..removeListener(_secondScrollListener)
..dispose();
super.dispose();
}
void _firstScrollListener() {
_secondScrollController.removeListener(_secondScrollListener);
_secondScrollController.position.forcePixels(_firstScrollController.offset);
_secondScrollController.addListener(_secondScrollListener);
}
void _secondScrollListener() {
_firstScrollController.removeListener(_firstScrollListener);
_firstScrollController.position.forcePixels(_secondScrollController.offset);
_firstScrollController.addListener(_firstScrollListener);
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text("List"),
backgroundColor: Colors.green,
),
body: Row(
children: [
SizedBox(
height: 600,
width: 300,
child: ListWheelScrollView(
itemExtent: 100,
controller: _firstScrollController,
physics: const FixedExtentScrollPhysics(),
onSelectedItemChanged: (value) {
print('first wheel : item selected: $value');
},
children: [
for (int i = 0; i < 25; i++) ...[
Container(
color: Colors.green,
height: 50,
width: 50,
)
]
]),
),
SizedBox(
height: 600,
width: 300,
child: ListWheelScrollView(
controller: _secondScrollController,
physics: const FixedExtentScrollPhysics(),
itemExtent: 100,
onSelectedItemChanged: (value) {
print('second wheel : item selected: $value');
},
children: [
for (int i = 0; i < 25; i++) ...[
Container(
color: Colors.red,
height: 50,
width: 50,
)
]
]),
)
],
));
}
}
Я использую защитную функцию-член forcePixels
, поскольку Flutter не предоставляет никакого способа установить пиксели без анимации без создания подкласса ScrollPosition. Если у вас все в порядке с этим предупреждением линтера, все в порядке. Если нет, нам придется расширить ListWheelScrollView, чтобы использовать ScrollPosition, где мы могли бы вносить изменения по мере необходимости.
@Ramji сделал несколько изменений.
Интересный факт: вы используете BlueStacks для разработки, и я там работаю.
использование forcePixels влияет на производительность? Не могли бы вы добавить, как работает код? Мне нравится Bluestack. Я использую Bluestack с детства, потому что не могу позволить себе телефон.
Удален CancelableCompleter, так как он не требуется. Код намного проще. Я также отредактировал объяснение.
Есть ли способ включать и выключать параллелизм списка, то есть он может прокручиваться индивидуально и синхронно в соответствии с некоторыми условиями.
Вы можете удалить слушателей или перейти к другим слушателям и вернуть их обратно к предоставленным слушателям в зависимости от ваших условий.
Лучший подход — использовать linked_scroll_controller.
Виджеты прокрутки создадут контроллер прокрутки по умолчанию (класс ScrollController), если он не указан. Контроллер прокрутки создает ScrollPosition для управления состоянием отдельного виджета Scrollable.
Чтобы связать наши контроллеры прокрутки, мы будем использовать linked_scroll_controller, контроллер прокрутки, который позволяет синхронизировать два или более представления прокрутки.
late LinkedScrollControllerGroup _verticalControllersGroup;
late ScrollController _verticalController1;
late ScrollController _verticalController2;
@override
void initState() {
super.initState();
_verticalControllersGroup = LinkedScrollControllerGroup();
_verticalController1 = _verticalControllersGroup.addAndGet();
_verticalController2 = _verticalControllersGroup.addAndGet();
}
Теперь используйте эти контроллеры в своих списках.
Ниже приведены два хороших примера, которые помогут вам использовать linked_scroll_controller в вашем случае.
Flutter: как создавать связанные виджеты прокрутки
Flutter: создание таблицы с прокруткой в двух направлениях с фиксированным заголовком и столбцом
Создание прокрутки в двух направлениях с помощью linked_scroll_controller
linked_scroll_controller не поддерживает физику фиксированной расширенной прокрутки, и моему приложению требуется эта физика.
Основная причина, по которой ваша прокрутка не является гладкой, заключается в том, что вы используете
setState();
с помощью setState, чтобы отображать все виджеты страниц, которые находятся внутри связанного класса, если страница содержит анимацию, анимация будет отставать. Попробуйте удалить setState() или отделить ListWheelScrollView() в другой класс, или вы можете попробовать использовать управление состоянием, например Provider, GetX или BLoC.