Created
October 29, 2020 08:08
-
-
Save imaNNeo/bd9bb1e8dd4592f3a9c8e895fd46a220 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| import 'package:flutter/material.dart'; | |
| import 'dart:math' as math; | |
| main() => runApp(MaterialApp(home: MyHomePage())); | |
| final game1 = GameModel( | |
| image: 'https://img.techpowerup.org/201029/game1.png', | |
| title: 'Road Fight', | |
| subtitle: 'Shooting Cars', | |
| ); | |
| final game2 = GameModel( | |
| image: 'https://img.techpowerup.org/201029/game2.png', | |
| title: 'Vikings', | |
| subtitle: 'Sons of Ragnar', | |
| ); | |
| List<GameModel> games = [ | |
| game1, | |
| game2, | |
| game1, | |
| game2, | |
| game1, | |
| game2, | |
| game1, | |
| game2, | |
| game1, | |
| game2, | |
| game1, | |
| game2, | |
| game1, | |
| game2, | |
| game1, | |
| game2, | |
| ]; | |
| class MyHomePage extends StatelessWidget { | |
| @override | |
| Widget build(BuildContext context) { | |
| return Scaffold( | |
| appBar: AppBar( | |
| title: Text('Flutter 4 Fun'), | |
| ), | |
| body: Center( | |
| child: HorizontalSnappingList( | |
| itemWidth: 188.0, | |
| itemHorizontalMargin: 0, | |
| itemCount: games.length, | |
| itemBuilder: (context, i) => GameItemWidget(gameModel: games[i]), | |
| ), | |
| ), | |
| ); | |
| } | |
| } | |
| class GameModel { | |
| final String image; | |
| final String title; | |
| final String subtitle; | |
| GameModel({ | |
| this.image, | |
| this.title, | |
| this.subtitle, | |
| }); | |
| } | |
| class GameItemWidget extends StatelessWidget { | |
| final double itemWidth; | |
| final double itemHeight; | |
| final GameModel gameModel; | |
| const GameItemWidget({ | |
| Key key, | |
| this.itemWidth = 188.0, | |
| this.itemHeight = 200.0, | |
| @required this.gameModel, | |
| }) : super(key: key); | |
| @override | |
| Widget build(BuildContext context) { | |
| return Container( | |
| width: itemWidth, | |
| height: itemHeight, | |
| child: Stack( | |
| fit: StackFit.expand, | |
| children: [ | |
| Image.network( | |
| gameModel.image, | |
| fit: BoxFit.fitHeight, | |
| ), | |
| Padding( | |
| padding: const EdgeInsets.all(16.0), | |
| child: Column( | |
| crossAxisAlignment: CrossAxisAlignment.start, | |
| children: [ | |
| Text( | |
| gameModel.title, | |
| style: TextStyle( | |
| color: Colors.white, | |
| fontWeight: FontWeight.bold, | |
| fontSize: 18, | |
| ), | |
| ), | |
| SizedBox( | |
| height: 4, | |
| ), | |
| Text( | |
| gameModel.subtitle, | |
| style: TextStyle( | |
| color: Colors.white, | |
| fontSize: 14, | |
| ), | |
| ), | |
| ], | |
| ), | |
| ) | |
| ], | |
| ), | |
| ); | |
| } | |
| } | |
| class HorizontalSnappingList extends StatefulWidget { | |
| final int itemCount; | |
| final IndexedWidgetBuilder itemBuilder; | |
| final double itemWidth; | |
| final double itemHorizontalMargin; | |
| final double listHeight; | |
| final double itemsConsumedWidth; | |
| HorizontalSnappingList({ | |
| @required this.itemBuilder, | |
| this.itemCount, | |
| this.itemWidth = 120.0, | |
| this.itemHorizontalMargin = 4.0, | |
| this.listHeight = 200.0, | |
| }) : itemsConsumedWidth = itemWidth + (itemHorizontalMargin * 2); | |
| @override | |
| _HorizontalSnappingListState createState() => _HorizontalSnappingListState(); | |
| } | |
| class _HorizontalSnappingListState extends State<HorizontalSnappingList> { | |
| ScrollController _scrollController; | |
| double scrollOffset = 0; | |
| @override | |
| void initState() { | |
| _scrollController = new ScrollController(); | |
| _scrollController.addListener(() { | |
| setState(() { | |
| scrollOffset = _scrollController.position.pixels / widget.itemsConsumedWidth; | |
| }); | |
| }); | |
| super.initState(); | |
| } | |
| @override | |
| Widget build(BuildContext context) { | |
| return SizedBox( | |
| height: widget.listHeight, | |
| child: ListView.builder( | |
| padding: EdgeInsets.only(left: 16), | |
| controller: _scrollController, | |
| scrollDirection: Axis.horizontal, | |
| physics: SnappingListScrollPhysic( | |
| itemWidth: widget.itemWidth + (widget.itemHorizontalMargin * 2)), | |
| itemCount: widget.itemCount, | |
| itemBuilder: (context, i) { | |
| double scale = ((1 - math.min((i - scrollOffset).abs(), 1.0)) * (1 - 0.8)) + 0.8; | |
| return Transform.scale( | |
| scale: scale, | |
| child: widget.itemBuilder(context, i), | |
| ); | |
| }, | |
| ), | |
| ); | |
| } | |
| } | |
| class SnappingListScrollPhysic extends ScrollPhysics { | |
| final double itemWidth; | |
| const SnappingListScrollPhysic({ | |
| @required this.itemWidth, | |
| ScrollPhysics parent, | |
| }) : super(parent: parent); | |
| @override | |
| SnappingListScrollPhysic applyTo(ScrollPhysics ancestor) => SnappingListScrollPhysic( | |
| parent: buildParent(ancestor), | |
| itemWidth: itemWidth, | |
| ); | |
| double _getItem(ScrollPosition position) => (position.pixels) / itemWidth; | |
| double _getPixels(ScrollPosition position, double item) => | |
| math.min(item * itemWidth, position.maxScrollExtent); | |
| double _getTargetPixels(ScrollPosition position, Tolerance tolerance, double velocity) { | |
| double item = _getItem(position); | |
| if (velocity < -tolerance.velocity) { | |
| item -= 0.5; | |
| } else if (velocity > tolerance.velocity) { | |
| item += 0.5; | |
| } | |
| return _getPixels(position, item.roundToDouble()); | |
| } | |
| @override | |
| Simulation createBallisticSimulation(ScrollMetrics position, double velocity) { | |
| // If we're out of range and not headed back in range, defer to the parent | |
| // ballistics, which should put us back in range at a page boundary. | |
| if ((velocity <= 0.0 && position.pixels <= position.minScrollExtent) || | |
| (velocity >= 0.0 && position.pixels >= position.maxScrollExtent)) { | |
| return super.createBallisticSimulation(position, velocity); | |
| } | |
| final Tolerance tolerance = this.tolerance; | |
| final double target = _getTargetPixels(position, tolerance, velocity); | |
| if (target != position.pixels) { | |
| return ScrollSpringSimulation(spring, position.pixels, target, velocity, | |
| tolerance: tolerance); | |
| } | |
| return null; | |
| } | |
| @override | |
| bool get allowImplicitScrolling => false; | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment