在没有 setState Flutter 的情况下滚动时禁用 CustomScrollView 的滚动
Disable scrolling of CustomScrollView while scrolling without setState Flutter
我在 CustomScrollView
中有多个小部件和列表,我想在某些像素限制条件下滚动时停止 CustomScrollView
滚动。
我可以使用 NeverScrollPhysics()
来停止它,但我不想在这里使用 setState()
函数,因为带有列表的 CustomScrollview 内容足够大,在滚动时重新加载时屏幕会变慢。
也尝试使用 Provider
,但构建器仅提供了一个 child 小部件,该小部件不适用于 sliver 列表。
这是使用 setState()
的代码:
NotificationListener(
onNotification: (ScrollNotification notif) {
if(notif is ScrollUpdateNotification) {
if (canScroll && notif.metrics.pixels > 100) {
canScroll = false;
setState(() {});
}
}
if(notif is ScrollEndNotification) {
if(!canScroll) {
canScroll = true;
setState(() {});
}
}
return true;
},
child: CustomScrollView(
shrinkWrap: true,
physics: canScroll ? BouncingScrollPhysics() : NeverScrollableScrollPhysics(),
slivers: [
SliverToBoxAdapter(),
List(),
List(),
],
),
),
有没有办法只重新加载 CustomScrollView
而没有 child ?否则在这种情况下有什么解决方法可以防止滚动吗?
感谢帮助
试试这个解决方案,对子小部件使用 const 构造函数,这样除非小部件更改,否则它不会重建
class MyHomePage extends StatelessWidget {
ValueNotifier<ScrollPhysics> canScroll =
ValueNotifier(const BouncingScrollPhysics());
MyHomePage({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return Scaffold(
body: NotificationListener(
onNotification: (ScrollNotification notif) {
if (notif is ScrollUpdateNotification) {
if (canScroll.value.runtimeType == BouncingScrollPhysics &&
notif.metrics.pixels > 100) {
canScroll.value = const NeverScrollableScrollPhysics();
debugPrint("End false");
}
}
if (notif is ScrollEndNotification) {
if (canScroll.value.runtimeType == NeverScrollableScrollPhysics) {
debugPrint("End");
Future.delayed(const Duration(milliseconds: 300),
() => canScroll.value = const BouncingScrollPhysics());
debugPrint("End1");
}
}
return true;
},
child: ValueListenableBuilder(
valueListenable: canScroll,
builder:
(BuildContext context, ScrollPhysics scrollType, Widget? child) =>
CustomScrollView(
physics: scrollType,
slivers: [
SliverToBoxAdapter(
child: Container(
height: 200,
color: Colors.black,
),
),
SliverToBoxAdapter(
child: Column(
children: [
Container(
height: 100,
color: Colors.blue,
),
Container(
height: 200,
color: Colors.grey,
),
Container(
height: 200,
color: Colors.blue,
),
Container(
height: 200,
color: Colors.grey,
),
Container(
height: 200,
color: Colors.blue,
),
],
),
),
],
),
),
),
);
}
}
您是否只需要阻止用户滚动它?我认为您可以尝试使用 jumoTo.
将列表控制到固定位置
...
final _controller = ScrollController();
@override
Widget build(BuildContext context) {
return NotificationListener(
onNotification: (ScrollNotification notif) {
if (notif is ScrollUpdateNotification) {
if (notif.metrics.pixels > 100) {
_controller.jumpTo(100)
}
}
return true;
},
child: CustomScrollView(
controller: _controller,
...
当构建方法被调用时,该构建方法中的所有小部件都将被重建,除了 const
个小部件,但是 const
个小部件不能接受动态参数(只能是常量值)。
Riverpod
在这种情况下提供了一个很好的解决方案,
使用 ProviderScope
,您可以通过 inherited widget
而不是小部件构造函数传递参数(就像使用导航传递参数时一样),因此承包商可以是 const
.
示例:
数据模块
TLDR 你需要使用 Freezed
包或覆盖 == operator
和 hashCode
几乎总是因为飞镖问题。
class DataClass {
final int age;
final String name;
const DataClass(this.age, this.name);
@override
bool operator ==(Object other) {
if (identical(this, other)) return true;
return other is DataClass && other.age == age && other.name == name;
}
@override
int get hashCode => age.hashCode ^ name.hashCode;
}
将我们的 ScopedProvider
设置为全局变量
final dataClassScope = ScopedProvider<DataClass>(null);
我们在列表中使用的小部件
class MyChildWidget extends ConsumerWidget {
const MyChildWidget();
@override
Widget build(BuildContext context, ScopedReader watch) {
final data = watch(dataClassScope);
// Note for better optimaization
// in case you are sure the data you are passing to this widget wouldn't change
// you can just use StatelessWidget and set the data as:
// final data = context.read(dataClassScope);
// use ConsumerWidget (or Consumer down in this child widget tree) if the data has to change
print('widget with name\n${data.name} rebuild');
return SliverToBoxAdapter(
child: Padding(
padding: const EdgeInsets.symmetric(vertical: 40, horizontal: 20),
child: Text(
'Name : ${data.name}\nAge ${data.age}',
textAlign: TextAlign.center,
),
),
);
}
}
最后是主要的 CustomScrollView
小部件
class MyMainWidget extends StatefulWidget {
const MyMainWidget();
@override
State<MyMainWidget> createState() => _MyMainWidgetState();
}
class _MyMainWidgetState extends State<MyMainWidget> {
bool canScroll = true;
void changeCanScrollState() {
setState(() => canScroll = !canScroll);
print('canScroll $canScroll');
}
final dataList = List.generate(
20,
(index) => DataClass(10 * index, '$index'),
);
@override
Widget build(BuildContext context) {
return Scaffold(
body: GestureDetector(
behavior: HitTestBehavior.opaque,
onTap: () {
changeCanScrollState();
},
child: CustomScrollView(
shrinkWrap: true,
physics: canScroll
? BouncingScrollPhysics()
: NeverScrollableScrollPhysics(),
slivers: [
for (int i = 0; i < dataList.length; i++)
ProviderScope(
overrides: [
dataClassScope.overrideWithValue(dataList[i]),
],
child: const MyChildWidget(),
),
],
),
),
);
}
}
不要忘记用 ProviderScope
包裹 MaterialApp
。
runApp(
ProviderScope(
child: MyApp(),
),
);
我在 CustomScrollView
中有多个小部件和列表,我想在某些像素限制条件下滚动时停止 CustomScrollView
滚动。
我可以使用 NeverScrollPhysics()
来停止它,但我不想在这里使用 setState()
函数,因为带有列表的 CustomScrollview 内容足够大,在滚动时重新加载时屏幕会变慢。
也尝试使用 Provider
,但构建器仅提供了一个 child 小部件,该小部件不适用于 sliver 列表。
这是使用 setState()
的代码:
NotificationListener(
onNotification: (ScrollNotification notif) {
if(notif is ScrollUpdateNotification) {
if (canScroll && notif.metrics.pixels > 100) {
canScroll = false;
setState(() {});
}
}
if(notif is ScrollEndNotification) {
if(!canScroll) {
canScroll = true;
setState(() {});
}
}
return true;
},
child: CustomScrollView(
shrinkWrap: true,
physics: canScroll ? BouncingScrollPhysics() : NeverScrollableScrollPhysics(),
slivers: [
SliverToBoxAdapter(),
List(),
List(),
],
),
),
有没有办法只重新加载 CustomScrollView
而没有 child ?否则在这种情况下有什么解决方法可以防止滚动吗?
感谢帮助
试试这个解决方案,对子小部件使用 const 构造函数,这样除非小部件更改,否则它不会重建
class MyHomePage extends StatelessWidget {
ValueNotifier<ScrollPhysics> canScroll =
ValueNotifier(const BouncingScrollPhysics());
MyHomePage({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return Scaffold(
body: NotificationListener(
onNotification: (ScrollNotification notif) {
if (notif is ScrollUpdateNotification) {
if (canScroll.value.runtimeType == BouncingScrollPhysics &&
notif.metrics.pixels > 100) {
canScroll.value = const NeverScrollableScrollPhysics();
debugPrint("End false");
}
}
if (notif is ScrollEndNotification) {
if (canScroll.value.runtimeType == NeverScrollableScrollPhysics) {
debugPrint("End");
Future.delayed(const Duration(milliseconds: 300),
() => canScroll.value = const BouncingScrollPhysics());
debugPrint("End1");
}
}
return true;
},
child: ValueListenableBuilder(
valueListenable: canScroll,
builder:
(BuildContext context, ScrollPhysics scrollType, Widget? child) =>
CustomScrollView(
physics: scrollType,
slivers: [
SliverToBoxAdapter(
child: Container(
height: 200,
color: Colors.black,
),
),
SliverToBoxAdapter(
child: Column(
children: [
Container(
height: 100,
color: Colors.blue,
),
Container(
height: 200,
color: Colors.grey,
),
Container(
height: 200,
color: Colors.blue,
),
Container(
height: 200,
color: Colors.grey,
),
Container(
height: 200,
color: Colors.blue,
),
],
),
),
],
),
),
),
);
}
}
您是否只需要阻止用户滚动它?我认为您可以尝试使用 jumoTo.
将列表控制到固定位置 ...
final _controller = ScrollController();
@override
Widget build(BuildContext context) {
return NotificationListener(
onNotification: (ScrollNotification notif) {
if (notif is ScrollUpdateNotification) {
if (notif.metrics.pixels > 100) {
_controller.jumpTo(100)
}
}
return true;
},
child: CustomScrollView(
controller: _controller,
...
当构建方法被调用时,该构建方法中的所有小部件都将被重建,除了 const
个小部件,但是 const
个小部件不能接受动态参数(只能是常量值)。
Riverpod
在这种情况下提供了一个很好的解决方案,
使用 ProviderScope
,您可以通过 inherited widget
而不是小部件构造函数传递参数(就像使用导航传递参数时一样),因此承包商可以是 const
.
示例:
数据模块
TLDR 你需要使用 Freezed
包或覆盖 == operator
和 hashCode
几乎总是因为飞镖问题。
class DataClass {
final int age;
final String name;
const DataClass(this.age, this.name);
@override
bool operator ==(Object other) {
if (identical(this, other)) return true;
return other is DataClass && other.age == age && other.name == name;
}
@override
int get hashCode => age.hashCode ^ name.hashCode;
}
将我们的 ScopedProvider
设置为全局变量
final dataClassScope = ScopedProvider<DataClass>(null);
我们在列表中使用的小部件
class MyChildWidget extends ConsumerWidget {
const MyChildWidget();
@override
Widget build(BuildContext context, ScopedReader watch) {
final data = watch(dataClassScope);
// Note for better optimaization
// in case you are sure the data you are passing to this widget wouldn't change
// you can just use StatelessWidget and set the data as:
// final data = context.read(dataClassScope);
// use ConsumerWidget (or Consumer down in this child widget tree) if the data has to change
print('widget with name\n${data.name} rebuild');
return SliverToBoxAdapter(
child: Padding(
padding: const EdgeInsets.symmetric(vertical: 40, horizontal: 20),
child: Text(
'Name : ${data.name}\nAge ${data.age}',
textAlign: TextAlign.center,
),
),
);
}
}
最后是主要的 CustomScrollView
小部件
class MyMainWidget extends StatefulWidget {
const MyMainWidget();
@override
State<MyMainWidget> createState() => _MyMainWidgetState();
}
class _MyMainWidgetState extends State<MyMainWidget> {
bool canScroll = true;
void changeCanScrollState() {
setState(() => canScroll = !canScroll);
print('canScroll $canScroll');
}
final dataList = List.generate(
20,
(index) => DataClass(10 * index, '$index'),
);
@override
Widget build(BuildContext context) {
return Scaffold(
body: GestureDetector(
behavior: HitTestBehavior.opaque,
onTap: () {
changeCanScrollState();
},
child: CustomScrollView(
shrinkWrap: true,
physics: canScroll
? BouncingScrollPhysics()
: NeverScrollableScrollPhysics(),
slivers: [
for (int i = 0; i < dataList.length; i++)
ProviderScope(
overrides: [
dataClassScope.overrideWithValue(dataList[i]),
],
child: const MyChildWidget(),
),
],
),
),
);
}
}
不要忘记用 ProviderScope
包裹 MaterialApp
。
runApp(
ProviderScope(
child: MyApp(),
),
);