add carousel, card_swipe, and focus_image samples (#119)

* add carousel, card_swipe, and focus_image samples

* fix image assets

* fix more asset images

* add repeating animation

* fix import

* add copyright headers

* remove Center widget

* imageAssetName

* use ClipRect, refactor _SwipeableCardState

* use offset.zero

* add comments

* remove reference to coverflow package

* change spread to toList()

* refactor coverflow sample

don't set width and height
use const
data -> images

* return widget directly, fix formatting

* inline transitionsBuilder

* image -> imageAssetName

* _rectTween() => _createTween()

* _expandToPageRoute -> _createRoute

* move non-updating widgets out of AnimatedBuilder

* code review updates to animations demos
pull/120/head
John Ryan 6 years ago committed by GitHub
parent 8e4d8c138b
commit 4966440a29
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -3,8 +3,13 @@
// found in the LICENSE file. // found in the LICENSE file.
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'src/basics/animation_controller_demo.dart'; import 'src/basics/animation_controller_demo.dart';
import 'src/misc/card_swipe.dart';
import 'src/misc/carousel.dart';
import 'src/misc/expand_card.dart'; import 'src/misc/expand_card.dart';
import 'src/misc/focus_image.dart';
import 'src/misc/repeating_animation.dart';
void main() => runApp(AnimationSamples()); void main() => runApp(AnimationSamples());
@ -24,6 +29,11 @@ final basicDemos = [
final miscDemos = [ final miscDemos = [
Demo('Expandable Card', ExpandCardDemo.routeName, Demo('Expandable Card', ExpandCardDemo.routeName,
(context) => ExpandCardDemo()), (context) => ExpandCardDemo()),
Demo('Carousel', CarouselDemo.routeName, (context) => CarouselDemo()),
Demo('Focus Image', FocusImageDemo.routeName, (context) => FocusImageDemo()),
Demo('Card Swipe', CardSwipeDemo.routeName, (context) => CardSwipeDemo()),
Demo('Repeating Animation', RepeatingAnimationDemo.routeName,
(context) => RepeatingAnimationDemo()),
]; ];
final basicDemoRoutes = final basicDemoRoutes =

@ -0,0 +1,188 @@
// Copyright 2019 The Flutter team. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'dart:ui';
import 'package:flutter/material.dart';
import 'package:flutter/physics.dart';
class CardSwipeDemo extends StatefulWidget {
static String routeName = '/misc/card_swipe';
@override
_CardSwipeDemoState createState() => _CardSwipeDemoState();
}
class _CardSwipeDemoState extends State<CardSwipeDemo> {
List<String> fileNames;
void initState() {
super.initState();
_resetCards();
}
void _resetCards() {
fileNames = [
'assets/eat_cape_town_sm.jpg',
'assets/eat_new_orleans_sm.jpg',
'assets/eat_sydney_sm.jpg',
];
}
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Card Swipe'),
),
body: Padding(
padding: const EdgeInsets.all(12.0),
child: Center(
child: Column(
children: <Widget>[
Expanded(
child: ClipRect(
child: Stack(
overflow: Overflow.clip,
children: <Widget>[
for (final fileName in fileNames)
SwipeableCard(
imageAssetName: fileName,
onSwiped: () {
setState(() {
fileNames.remove(fileName);
});
},
),
],
),
),
),
RaisedButton(
child: const Text('Refill'),
onPressed: () {
setState(() {
_resetCards();
});
},
),
],
),
),
),
);
}
}
class Card extends StatelessWidget {
final String imageAssetName;
Card(this.imageAssetName);
Widget build(BuildContext context) {
return AspectRatio(
aspectRatio: 3 / 5,
child: DecoratedBox(
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(20.0),
image: DecorationImage(
image: AssetImage(imageAssetName),
fit: BoxFit.cover,
),
),
),
);
}
}
class SwipeableCard extends StatefulWidget {
final String imageAssetName;
final VoidCallback onSwiped;
SwipeableCard({
this.onSwiped,
this.imageAssetName,
});
_SwipeableCardState createState() => _SwipeableCardState();
}
class _SwipeableCardState extends State<SwipeableCard>
with SingleTickerProviderStateMixin {
AnimationController _controller;
Animation<Offset> _animation;
double _dragStartX;
bool _isSwipingLeft = false;
void initState() {
super.initState();
_controller = AnimationController.unbounded(vsync: this);
_animation = _controller.drive(Tween<Offset>(
begin: Offset.zero,
end: Offset(1, 0),
));
}
Widget build(BuildContext context) {
return SlideTransition(
position: _animation,
child: GestureDetector(
onHorizontalDragStart: _dragStart,
onHorizontalDragUpdate: _dragUpdate,
onHorizontalDragEnd: _dragEnd,
child: Card(widget.imageAssetName),
),
);
}
/// Sets the starting position the user dragged from.
void _dragStart(DragStartDetails details) {
_dragStartX = details.localPosition.dx;
}
/// Changes the animation to animate to the left or right depending on the
/// swipe, and sets the AnimationController's value to the swiped amount.
void _dragUpdate(DragUpdateDetails details) {
var isSwipingLeft = (details.localPosition.dx - _dragStartX) < 0;
if (isSwipingLeft != _isSwipingLeft) {
_isSwipingLeft = isSwipingLeft;
_updateAnimation(details.localPosition.dx);
}
setState(() {
// Calculate the amount dragged in unit coordinates (between 0 and 1)
// using this widgets width.
_controller.value =
(details.localPosition.dx - _dragStartX).abs() / context.size.width;
});
}
/// Runs the fling / spring animation using the final velocity of the drag
/// gesture.
void _dragEnd(DragEndDetails details) {
var velocity =
(details.velocity.pixelsPerSecond.dx / context.size.width).abs();
_animate(velocity: velocity);
}
void _updateAnimation(double dragPosition) {
_animation = _controller.drive(Tween<Offset>(
begin: Offset.zero,
end: _isSwipingLeft ? Offset(-1, 0) : Offset(1, 0),
));
}
void _animate({double velocity = 0}) {
var description = SpringDescription(mass: 50, stiffness: 1, damping: 1);
var simulation =
SpringSimulation(description, _controller.value, 1, velocity);
_controller.animateWith(simulation).then((_) {
widget.onSwiped();
});
}
void dispose() {
_controller.dispose();
super.dispose();
}
}

@ -0,0 +1,108 @@
// Copyright 2019 The Flutter team. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'package:flutter/material.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/widgets.dart';
class CarouselDemo extends StatelessWidget {
static String routeName = '/misc/carousel';
static const List<String> fileNames = [
'assets/eat_cape_town_sm.jpg',
'assets/eat_new_orleans_sm.jpg',
'assets/eat_sydney_sm.jpg',
];
final List<Widget> images =
fileNames.map((file) => Image.asset(file, fit: BoxFit.cover)).toList();
@override
Widget build(context) {
return Scaffold(
appBar: AppBar(
title: Text('Carousel Demo'),
),
body: Center(
child: Padding(
padding: const EdgeInsets.all(16),
child: AspectRatio(
aspectRatio: 1,
child: Carousel(itemBuilder: widgetBuilder),
),
),
),
);
}
Widget widgetBuilder(context, int index) {
return images[index % images.length];
}
}
typedef void OnCurrentItemChangedCallback(int currentItem);
class Carousel extends StatefulWidget {
final IndexedWidgetBuilder itemBuilder;
const Carousel({Key key, @required this.itemBuilder});
@override
_CarouselState createState() => _CarouselState();
}
class _CarouselState extends State<Carousel> {
PageController _controller;
int _currentPage;
bool _pageHasChanged = false;
@override
void initState() {
super.initState();
_currentPage = 0;
_controller = PageController(
viewportFraction: .85,
initialPage: _currentPage,
);
}
@override
Widget build(context) {
var size = MediaQuery.of(context).size;
return PageView.builder(
onPageChanged: (value) {
setState(() {
_pageHasChanged = true;
_currentPage = value;
});
},
controller: _controller,
itemBuilder: (context, index) => AnimatedBuilder(
animation: _controller,
builder: (context, child) {
var result = _pageHasChanged ? _controller.page : _currentPage * 1.0;
// The horizontal position of the page between a 1 and 0
var value = result - index;
value = (1 - (value.abs() * .5)).clamp(0.0, 1.0) as double;
return Center(
child: SizedBox(
height: Curves.easeOut.transform(value) * size.height,
width: Curves.easeOut.transform(value) * size.width,
child: child,
),
);
},
child: widget.itemBuilder(context, index),
),
);
}
@override
void dispose() {
_controller.dispose();
super.dispose();
}
}

@ -5,7 +5,7 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
class ExpandCardDemo extends StatelessWidget { class ExpandCardDemo extends StatelessWidget {
static const String routeName = '/expand_card'; static const String routeName = '/misc/expand_card';
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {

@ -0,0 +1,113 @@
// Copyright 2019 The Flutter team. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'package:flutter/material.dart';
class FocusImageDemo extends StatelessWidget {
static String routeName = '/misc/focus_image';
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('Focus Image')),
body: Grid(),
);
}
}
class Grid extends StatelessWidget {
Widget build(BuildContext context) {
return Scaffold(
body: GridView.builder(
itemCount: 40,
gridDelegate:
SliverGridDelegateWithFixedCrossAxisCount(crossAxisCount: 4),
itemBuilder: (context, index) {
return (index >= 20)
? SmallCard('assets/eat_cape_town_sm.jpg')
: SmallCard('assets/eat_new_orleans_sm.jpg');
},
),
);
}
}
Route _createRoute(BuildContext parentContext, String image) {
return PageRouteBuilder(
pageBuilder: (context, animation, secondaryAnimation) {
return _SecondPage(image);
},
transitionsBuilder: (context, animation, secondaryAnimation, child) {
var rectAnimation = _createTween(parentContext)
.chain(CurveTween(curve: Curves.ease))
.animate(animation);
return Stack(
children: [
PositionedTransition(rect: rectAnimation, child: child),
],
);
},
);
}
Tween<RelativeRect> _createTween(BuildContext context) {
var windowSize = MediaQuery.of(context).size;
var box = context.findRenderObject() as RenderBox;
var rect = box.localToGlobal(Offset.zero) & box.size;
var relativeRect = RelativeRect.fromSize(rect, windowSize);
return RelativeRectTween(
begin: relativeRect,
end: RelativeRect.fill,
);
}
class SmallCard extends StatelessWidget {
final String imageAssetName;
SmallCard(this.imageAssetName);
Widget build(BuildContext context) {
return Card(
child: Material(
child: InkWell(
onTap: () {
var nav = Navigator.of(context);
nav.push(_createRoute(context, imageAssetName));
},
child: Image.asset(
imageAssetName,
fit: BoxFit.cover,
),
),
),
);
}
}
class _SecondPage extends StatelessWidget {
final String imageAssetName;
_SecondPage(this.imageAssetName);
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.black,
body: Center(
child: Material(
child: InkWell(
onTap: () => Navigator.of(context).pop(),
child: AspectRatio(
aspectRatio: 1,
child: Image.asset(
imageAssetName,
fit: BoxFit.cover,
),
),
),
),
),
);
}
}

@ -0,0 +1,60 @@
// Copyright 2019 The Flutter team. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'package:flutter/material.dart';
class RepeatingAnimationDemo extends StatefulWidget {
static String routeName = '/misc/repeating_animation';
@override
RepeatingAnimationDemoState createState() => RepeatingAnimationDemoState();
}
class RepeatingAnimationDemoState extends State<RepeatingAnimationDemo>
with SingleTickerProviderStateMixin {
AnimationController _controller;
Animation<BorderRadius> _borderRadius;
@override
void initState() {
super.initState();
_controller =
AnimationController(duration: const Duration(seconds: 2), vsync: this)
..repeat(reverse: true);
_borderRadius = BorderRadiusTween(
begin: BorderRadius.circular(100.0),
end: BorderRadius.circular(0.0),
).animate(_controller);
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('Repeating Animation')),
body: Center(
child: AnimatedBuilder(
animation: _borderRadius,
builder: (context, child) {
return Container(
width: 200,
height: 200,
decoration: BoxDecoration(
color: Colors.deepPurple,
borderRadius: _borderRadius.value,
),
);
},
),
),
);
}
@override
void dispose() {
_controller.dispose();
super.dispose();
}
}
Loading…
Cancel
Save