Flutter ListView 滚动动画

源码下载

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
import 'package:flutter/material.dart';

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Parallax Scrolling Effect',
home: Scaffold(
appBar: AppBar(
title: Text('Parallax Scrolling Effect'),
),
body: Center(
child: ListView.builder(
itemBuilder: (context, index) => ItemWidget(index: index),
itemCount: 50,
),
),
),
);
}
}

class ItemWidget extends StatelessWidget {
ItemWidget({Key? key, required this.index}) : super(key: key);
final int index;
final keyImage = GlobalKey();

@override
Widget build(BuildContext context) {
return Container(
padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 16),
// width: double.infinity,
height: 200,
child: Flow(
delegate: ParallaxFlowDelegate(
scrollable: Scrollable.of(context)!,
itemContext: context,
keyImage: keyImage),
children: [
Image.network(
'https://source.unsplash.com/random/300x800?sig=$index',
fit: BoxFit.cover,
key: keyImage,
),
],
),
);
}
}

class ParallaxFlowDelegate extends FlowDelegate {
final ScrollableState scrollable;
final BuildContext itemContext;
final GlobalKey keyImage;

ParallaxFlowDelegate({
required this.scrollable,
required this.itemContext,
required this.keyImage,
}) : super(repaint: scrollable.position);

@override
BoxConstraints getConstraintsForChild(int i, BoxConstraints constraints) {
return BoxConstraints.tightFor(width: constraints.maxWidth);
}

@override
void paintChildren(FlowPaintingContext context) {
final scrollableBox = scrollable.context.findRenderObject() as RenderBox;
final itemBox = itemContext.findRenderObject() as RenderBox;
final itemOffset = itemBox.localToGlobal(
itemBox.size.centerLeft(Offset.zero),
ancestor: scrollableBox,
);
final viewportDimension = scrollable.position.viewportDimension;
final scrollFraction = (itemOffset.dy / viewportDimension).clamp(0, 1);

final verticalAlignment = Alignment(0, scrollFraction * 2 - 1);

final imageBox = keyImage.currentContext!.findRenderObject() as RenderBox;
final childRect = verticalAlignment.inscribe(
imageBox.size,
Offset.zero & context.size,
);

context.paintChild(
0,
transform: Transform.translate(
offset: Offset(0, childRect.top),
).transform,
);
}

@override
bool shouldRepaint(covariant ParallaxFlowDelegate oldDelegate) {
return scrollable != oldDelegate.scrollable ||
itemContext != oldDelegate.itemContext ||
keyImage != oldDelegate.keyImage;
}
}