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), 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; } }
|