rajath_pai

scrolling parallax effect

Aug 3rd, 2021
850
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
  1. import 'package:flutter/cupertino.dart';
  2. import 'package:flutter/material.dart';
  3. import 'package:flutter/rendering.dart';
  4.  
  5. final Color darkBlue = Color.fromARGB(255, 18, 32, 47);
  6.  
  7. void main() {
  8.   runApp(MyApp());
  9. }
  10.  
  11. class MyApp extends StatelessWidget {
  12.   @override
  13.   Widget build(BuildContext context) {
  14.     return MaterialApp(
  15.       theme: ThemeData.dark().copyWith(scaffoldBackgroundColor: darkBlue),
  16.       debugShowCheckedModeBanner: false,
  17.       home: Scaffold(
  18.         body: Center(
  19.           child: ExampleParallax(),
  20.         ),
  21.       ),
  22.     );
  23.   }
  24. }
  25.  
  26. class ExampleParallax extends StatelessWidget {
  27.   const ExampleParallax({
  28.     Key? key,
  29.   }) : super(key: key);
  30.  
  31.   @override
  32.   Widget build(BuildContext context) {
  33.     return SingleChildScrollView(
  34.       child: Column(
  35.         children: [
  36.           for (final location in locations)
  37.             LocationListItem(
  38.               imageUrl: location.imageUrl,
  39.               name: location.name,
  40.               country: location.place,
  41.             ),
  42.         ],
  43.       ),
  44.     );
  45.   }
  46. }
  47.  
  48. class LocationListItem extends StatelessWidget {
  49.   LocationListItem({
  50.     Key? key,
  51.     required this.imageUrl,
  52.     required this.name,
  53.     required this.country,
  54.   }) : super(key: key);
  55.  
  56.   final String imageUrl;
  57.   final String name;
  58.   final String country;
  59.   final GlobalKey _backgroundImageKey = GlobalKey();
  60.  
  61.   @override
  62.   Widget build(BuildContext context) {
  63.     return Padding(
  64.       padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 16),
  65.       child: AspectRatio(
  66.         aspectRatio: 16 / 9,
  67.         child: ClipRRect(
  68.           borderRadius: BorderRadius.circular(16),
  69.           child: Stack(
  70.             children: [
  71.               _buildParallaxBackground(context),
  72.               _buildGradient(),
  73.               _buildTitleAndSubtitle(),
  74.             ],
  75.           ),
  76.         ),
  77.       ),
  78.     );
  79.   }
  80.  
  81.   Widget _buildParallaxBackground(BuildContext context) {
  82.     return Flow(
  83.       delegate: ParallaxFlowDelegate(
  84.         scrollable: Scrollable.of(context)!,
  85.         listItemContext: context,
  86.         backgroundImageKey: _backgroundImageKey,
  87.       ),
  88.       children: [
  89.         Image.network(
  90.           imageUrl,
  91.           key: _backgroundImageKey,
  92.           fit: BoxFit.cover,
  93.         ),
  94.       ],
  95.     );
  96.   }
  97.  
  98.   Widget _buildGradient() {
  99.     return Positioned.fill(
  100.       child: DecoratedBox(
  101.         decoration: BoxDecoration(
  102.           gradient: LinearGradient(
  103.             colors: [Colors.transparent, Colors.black.withOpacity(0.7)],
  104.             begin: Alignment.topCenter,
  105.             end: Alignment.bottomCenter,
  106.             stops: [0.6, 0.95],
  107.           ),
  108.         ),
  109.       ),
  110.     );
  111.   }
  112.  
  113.   Widget _buildTitleAndSubtitle() {
  114.     return Positioned(
  115.       left: 20,
  116.       bottom: 20,
  117.       child: Column(
  118.         mainAxisSize: MainAxisSize.min,
  119.         crossAxisAlignment: CrossAxisAlignment.start,
  120.         children: [
  121.           Text(
  122.             name,
  123.             style: const TextStyle(
  124.               color: Colors.white,
  125.               fontSize: 20,
  126.               fontWeight: FontWeight.bold,
  127.             ),
  128.           ),
  129.           Text(
  130.             country,
  131.             style: const TextStyle(
  132.               color: Colors.white,
  133.               fontSize: 14,
  134.             ),
  135.           ),
  136.         ],
  137.       ),
  138.     );
  139.   }
  140. }
  141.  
  142. class ParallaxFlowDelegate extends FlowDelegate {
  143.   ParallaxFlowDelegate({
  144.     required this.scrollable,
  145.     required this.listItemContext,
  146.     required this.backgroundImageKey,
  147.   }) : super(repaint: scrollable.position);
  148.  
  149.   final ScrollableState scrollable;
  150.   final BuildContext listItemContext;
  151.   final GlobalKey backgroundImageKey;
  152.  
  153.   @override
  154.   BoxConstraints getConstraintsForChild(int i, BoxConstraints constraints) {
  155.     return BoxConstraints.tightFor(
  156.       width: constraints.maxWidth,
  157.     );
  158.   }
  159.  
  160.   @override
  161.   void paintChildren(FlowPaintingContext context) {
  162.     // Calculate the position of this list item within the viewport.
  163.     final scrollableBox = scrollable.context.findRenderObject() as RenderBox;
  164.     final listItemBox = listItemContext.findRenderObject() as RenderBox;
  165.     final listItemOffset = listItemBox.localToGlobal(
  166.         listItemBox.size.centerLeft(Offset.zero),
  167.         ancestor: scrollableBox);
  168.  
  169.     // Determine the percent position of this list item within the
  170.     // scrollable area.
  171.     final viewportDimension = scrollable.position.viewportDimension;
  172.     final scrollFraction =
  173.         (listItemOffset.dy / viewportDimension).clamp(0.0, 1.0);
  174.  
  175.     // Calculate the vertical alignment of the background
  176.     // based on the scroll percent.
  177.     final verticalAlignment = Alignment(0.0, scrollFraction * 2 - 1);
  178.  
  179.     // Convert the background alignment into a pixel offset for
  180.     // painting purposes.
  181.     final backgroundSize =
  182.         (backgroundImageKey.currentContext!.findRenderObject() as RenderBox)
  183.             .size;
  184.     final listItemSize = context.size;
  185.     final childRect =
  186.         verticalAlignment.inscribe(backgroundSize, Offset.zero & listItemSize);
  187.  
  188.     // Paint the background.
  189.     context.paintChild(
  190.       0,
  191.       transform:
  192.           Transform.translate(offset: Offset(0.0, childRect.top)).transform,
  193.     );
  194.   }
  195.  
  196.   @override
  197.   bool shouldRepaint(ParallaxFlowDelegate oldDelegate) {
  198.     return scrollable != oldDelegate.scrollable ||
  199.         listItemContext != oldDelegate.listItemContext ||
  200.         backgroundImageKey != oldDelegate.backgroundImageKey;
  201.   }
  202. }
  203.  
  204. class Parallax extends SingleChildRenderObjectWidget {
  205.   Parallax({
  206.     required Widget background,
  207.   }) : super(child: background);
  208.  
  209.   @override
  210.   RenderObject createRenderObject(BuildContext context) {
  211.     return RenderParallax(scrollable: Scrollable.of(context)!);
  212.   }
  213.  
  214.   @override
  215.   void updateRenderObject(
  216.       BuildContext context, covariant RenderParallax renderObject) {
  217.     renderObject.scrollable = Scrollable.of(context)!;
  218.   }
  219. }
  220.  
  221. class ParallaxParentData extends ContainerBoxParentData<RenderBox> {}
  222.  
  223. class RenderParallax extends RenderBox
  224.     with RenderObjectWithChildMixin<RenderBox>, RenderProxyBoxMixin {
  225.   RenderParallax({
  226.     required ScrollableState scrollable,
  227.   }) : _scrollable = scrollable;
  228.  
  229.   ScrollableState _scrollable;
  230.  
  231.   ScrollableState get scrollable => _scrollable;
  232.  
  233.   set scrollable(ScrollableState value) {
  234.     if (value != _scrollable) {
  235.       if (attached) {
  236.         _scrollable.position.removeListener(markNeedsLayout);
  237.       }
  238.       _scrollable = value;
  239.       if (attached) {
  240.         _scrollable.position.addListener(markNeedsLayout);
  241.       }
  242.     }
  243.   }
  244.  
  245.   @override
  246.   void attach(covariant PipelineOwner owner) {
  247.     super.attach(owner);
  248.     _scrollable.position.addListener(markNeedsLayout);
  249.   }
  250.  
  251.   @override
  252.   void detach() {
  253.     _scrollable.position.removeListener(markNeedsLayout);
  254.     super.detach();
  255.   }
  256.  
  257.   @override
  258.   void setupParentData(covariant RenderObject child) {
  259.     if (child.parentData is! ParallaxParentData) {
  260.       child.parentData = ParallaxParentData();
  261.     }
  262.   }
  263.  
  264.   @override
  265.   void performLayout() {
  266.     size = constraints.biggest;
  267.  
  268.     // Force the background to take up all available width
  269.     // and then scale its height based on the image's aspect ratio.
  270.     final background = child!;
  271.     final backgroundImageConstraints =
  272.         BoxConstraints.tightFor(width: size.width);
  273.     background.layout(backgroundImageConstraints, parentUsesSize: true);
  274.  
  275.     // Set the background's local offset, which is zero.
  276.     (background.parentData as ParallaxParentData).offset = Offset.zero;
  277.   }
  278.  
  279.   @override
  280.   void paint(PaintingContext context, Offset offset) {
  281.     // Get the size of the scrollable area.
  282.     final viewportDimension = scrollable.position.viewportDimension;
  283.  
  284.     // Calculate the global position of this list item.
  285.     final scrollableBox = scrollable.context.findRenderObject() as RenderBox;
  286.     final backgroundOffset =
  287.         localToGlobal(size.centerLeft(Offset.zero), ancestor: scrollableBox);
  288.  
  289.     // Determine the percent position of this list item within the
  290.     // scrollable area.
  291.     final scrollFraction =
  292.         (backgroundOffset.dy / viewportDimension).clamp(0.0, 1.0);
  293.  
  294.     // Calculate the vertical alignment of the background
  295.     // based on the scroll percent.
  296.     final verticalAlignment = Alignment(0.0, scrollFraction * 2 - 1);
  297.  
  298.     // Convert the background alignment into a pixel offset for
  299.     // painting purposes.
  300.     final background = child!;
  301.     final backgroundSize = background.size;
  302.     final listItemSize = size;
  303.     final childRect =
  304.         verticalAlignment.inscribe(backgroundSize, Offset.zero & listItemSize);
  305.  
  306.     // Paint the background.
  307.     context.paintChild(
  308.         background,
  309.         (background.parentData as ParallaxParentData).offset +
  310.             offset +
  311.             Offset(0.0, childRect.top));
  312.   }
  313. }
  314.  
  315. class Location {
  316.   const Location({
  317.     required this.name,
  318.     required this.place,
  319.     required this.imageUrl,
  320.   });
  321.  
  322.   final String name;
  323.   final String place;
  324.   final String imageUrl;
  325. }
  326.  
  327. const urlPrefix =
  328.     'https://flutter.dev/docs/cookbook/img-files/effects/parallax';
  329. const locations = [
  330.   Location(
  331.     name: 'Mount Rushmore',
  332.     place: 'U.S.A',
  333.     imageUrl: '$urlPrefix/01-mount-rushmore.jpg',
  334.   ),
  335.   Location(
  336.     name: 'Gardens By The Bay',
  337.     place: 'Singapore',
  338.     imageUrl: '$urlPrefix/02-singapore.jpg',
  339.   ),
  340.   Location(
  341.     name: 'Machu Picchu',
  342.     place: 'Peru',
  343.     imageUrl: '$urlPrefix/03-machu-picchu.jpg',
  344.   ),
  345.   Location(
  346.     name: 'Vitznau',
  347.     place: 'Switzerland',
  348.     imageUrl: '$urlPrefix/04-vitznau.jpg',
  349.   ),
  350.   Location(
  351.     name: 'Bali',
  352.     place: 'Indonesia',
  353.     imageUrl: '$urlPrefix/05-bali.jpg',
  354.   ),
  355.   Location(
  356.     name: 'Mexico City',
  357.     place: 'Mexico',
  358.     imageUrl: '$urlPrefix/06-mexico-city.jpg',
  359.   ),
  360.   Location(
  361.     name: 'Cairo',
  362.     place: 'Egypt',
  363.     imageUrl: '$urlPrefix/07-cairo.jpg',
  364.   ),
  365. ];
RAW Paste Data