Skip to content

Commit 31efd83

Browse files
committed
page view animated
1 parent c8dcb70 commit 31efd83

File tree

3 files changed

+254
-2
lines changed

3 files changed

+254
-2
lines changed

README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ You can follow me on twitter [@diegoveloper](https://www.twitter.com/diegovelope
4444
| <center> <img src="https://media.giphy.com/media/h4x6fHw65l3KdnDrTi/giphy.gif" width="250"> </center> | <center> <img src="https://media.giphy.com/media/Y07CGmYvcNyl9rkgbY/giphy.gif" width="250">
4545
</center>
4646

47-
| App Clone / Photo Concept | |
47+
| App Clone / Photo Concept | Animations / Page View Animated |
4848
|--|--|
49-
| <center> <img src="https://media.giphy.com/media/XybRawN2vj4YV3fhul/giphy.gif" width="250"> </center> | <center>
49+
| <center> <img src="https://media.giphy.com/media/XybRawN2vj4YV3fhul/giphy.gif" width="250"> </center> | <center> <img src="https://media.giphy.com/media/UuNiEk20TKjn4F4H7a/giphy.gif" width="250">
5050
</center>

lib/animations/main_animations.dart

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import 'package:flutter_samples/animations/custom_appbar/my_custom_appbar_page.d
44
import 'package:flutter_samples/animations/foldable/foldable_animation.dart';
55
import 'package:flutter_samples/animations/list_details/list_page.dart';
66
import 'package:flutter_samples/animations/menu_exploration/main_menu_exploration.dart';
7+
import 'package:flutter_samples/animations/page_view_animated/page_view_animated.dart';
78
import 'package:flutter_samples/animations/split_widget/main_split_widget.dart';
89
import 'package:flutter_samples/animations/turn_on_the_light/turn_on_the_light.dart';
910
import 'package:flutter_samples/main.dart';
@@ -88,6 +89,13 @@ class MainAnimationsState extends State<MainAnimations> {
8889
onButtonTap(MainMenuExploration());
8990
},
9091
),
92+
93+
MyMenuButton(
94+
title: "Page View Animated",
95+
actionTap: () {
96+
onButtonTap(PageViewAnimated());
97+
},
98+
),
9199
],
92100
),
93101
),
Lines changed: 244 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,244 @@
1+
import 'dart:ui';
2+
3+
import 'package:flutter/material.dart';
4+
5+
class Movie {
6+
final String url;
7+
final String title;
8+
9+
const Movie({this.url, this.title});
10+
}
11+
12+
const movies = [
13+
const Movie(
14+
url: 'https://i.ytimg.com/vi/YcHKrNMwWyQ/movieposter.jpg', title: 'Dora'),
15+
const Movie(
16+
url:
17+
'https://cdn.shopify.com/s/files/1/0057/3728/3618/products/5cae019e64c0ee10ead36a00e60f0137_eeb2d749-fdbe-46fd-978a-870cc7e0ddf7_500x.jpg?v=1573593942',
18+
title: 'Joker'),
19+
const Movie(
20+
url:
21+
'https://encrypted-tbn0.gstatic.com/images?q=tbn%3AANd9GcRAog3B6UEzMDOhehRXmjQbV2qYGOHYMh3jGGwqL7zwnwRJ6YyD',
22+
title: 'Predator'),
23+
const Movie(
24+
url:
25+
'https://images-na.ssl-images-amazon.com/images/I/719fSnntGgL._AC_SL1500_.jpg',
26+
title: 'Anabelle',
27+
),
28+
];
29+
30+
class PageViewAnimated extends StatefulWidget {
31+
@override
32+
_PageViewAnimatedState createState() => _PageViewAnimatedState();
33+
}
34+
35+
class _PageViewAnimatedState extends State<PageViewAnimated> {
36+
final pageController = PageController(viewportFraction: 0.7);
37+
final ValueNotifier<double> _pageNotifier = ValueNotifier(0.0);
38+
39+
void _listener() {
40+
_pageNotifier.value = pageController.page;
41+
setState(() {});
42+
}
43+
44+
@override
45+
void initState() {
46+
WidgetsBinding.instance.addPostFrameCallback((_) {
47+
pageController.addListener(_listener);
48+
});
49+
super.initState();
50+
}
51+
52+
@override
53+
void dispose() {
54+
pageController.removeListener(_listener);
55+
pageController.dispose();
56+
super.dispose();
57+
}
58+
59+
@override
60+
Widget build(BuildContext context) {
61+
final borderRadius = BorderRadius.circular(30);
62+
final size = MediaQuery.of(context).size;
63+
64+
return Scaffold(
65+
body: Stack(
66+
children: [
67+
Positioned.fill(
68+
child: ValueListenableBuilder<double>(
69+
valueListenable: _pageNotifier,
70+
builder: (context, value, child) {
71+
return Stack(
72+
children: movies.reversed
73+
.toList()
74+
.asMap()
75+
.entries
76+
.map(
77+
(entry) => Positioned.fill(
78+
child: ClipRect(
79+
clipper: MyClipper(
80+
percentage: value,
81+
title: entry.value.title,
82+
index: entry.key,
83+
),
84+
child: Image.network(
85+
entry.value.url,
86+
fit: BoxFit.cover,
87+
),
88+
),
89+
),
90+
)
91+
.toList(),
92+
);
93+
}),
94+
),
95+
Positioned(
96+
left: 0,
97+
right: 0,
98+
bottom: 0,
99+
height: size.height / 3,
100+
child: Container(
101+
decoration: BoxDecoration(
102+
gradient: LinearGradient(
103+
colors: [
104+
Colors.white,
105+
Colors.white,
106+
Colors.white,
107+
Colors.white60,
108+
Colors.white24,
109+
],
110+
begin: Alignment.bottomCenter,
111+
end: Alignment.topCenter,
112+
)),
113+
),
114+
),
115+
PageView.builder(
116+
itemCount: movies.length,
117+
controller: pageController,
118+
itemBuilder: (context, index) {
119+
final lerp =
120+
lerpDouble(0, 1, (index - _pageNotifier.value).abs());
121+
122+
double opacity =
123+
lerpDouble(0.0, 0.5, (index - _pageNotifier.value).abs());
124+
if (opacity > 1.0) opacity = 1.0;
125+
if (opacity < 0.0) opacity = 0.0;
126+
return Transform.translate(
127+
offset: Offset(0.0, lerp * 50),
128+
child: Opacity(
129+
opacity: (1 - opacity),
130+
child: Align(
131+
alignment: Alignment.bottomCenter,
132+
child: Card(
133+
borderOnForeground: true,
134+
elevation: 4,
135+
shape: RoundedRectangleBorder(
136+
borderRadius: borderRadius,
137+
),
138+
clipBehavior: Clip.hardEdge,
139+
child: SizedBox(
140+
height: size.height / 1.5,
141+
width: size.width,
142+
child: Column(
143+
mainAxisSize: MainAxisSize.min,
144+
crossAxisAlignment: CrossAxisAlignment.stretch,
145+
children: [
146+
Expanded(
147+
flex: 2,
148+
child: Padding(
149+
padding: const EdgeInsets.only(
150+
top: 23.0, left: 23.0, right: 23.0),
151+
child: ClipRRect(
152+
borderRadius: borderRadius,
153+
child: Image.network(
154+
movies[index].url,
155+
fit: BoxFit.cover,
156+
),
157+
),
158+
),
159+
),
160+
Expanded(
161+
child: Padding(
162+
padding: const EdgeInsets.all(15.0),
163+
child: Text(
164+
movies[index].title,
165+
textAlign: TextAlign.center,
166+
style: TextStyle(
167+
fontWeight: FontWeight.w700,
168+
fontSize: 24,
169+
),
170+
),
171+
),
172+
),
173+
],
174+
),
175+
),
176+
),
177+
),
178+
),
179+
);
180+
}),
181+
Positioned(
182+
left: size.width / 4,
183+
bottom: 20,
184+
width: size.width / 2,
185+
child: RaisedButton(
186+
color: Colors.black,
187+
child: Text(
188+
'BUY TICKET',
189+
style: TextStyle(color: Colors.white),
190+
),
191+
onPressed: () => null),
192+
),
193+
Positioned(
194+
top: 30,
195+
left: 10,
196+
child: DecoratedBox(
197+
decoration: BoxDecoration(
198+
boxShadow: [
199+
BoxShadow(
200+
color: Colors.black38,
201+
offset: Offset(5, 5),
202+
blurRadius: 20,
203+
spreadRadius: 5),
204+
],
205+
),
206+
child: BackButton(
207+
color: Colors.white,
208+
),
209+
),
210+
),
211+
],
212+
),
213+
);
214+
}
215+
}
216+
217+
class MyClipper extends CustomClipper<Rect> {
218+
final double percentage;
219+
final String title;
220+
final int index;
221+
222+
MyClipper({
223+
this.percentage = 0.0,
224+
this.title,
225+
this.index,
226+
});
227+
228+
@override
229+
Rect getClip(Size size) {
230+
int currentIndex = movies.length - 1 - index;
231+
final realPercent = (currentIndex - percentage).abs();
232+
if (currentIndex == percentage.truncate()) {
233+
return Rect.fromLTWH(
234+
0.0, 0.0, size.width * (1 - realPercent), size.height);
235+
}
236+
if (percentage.truncate() > currentIndex) {
237+
return Rect.fromLTWH(0.0, 0.0, 0.0, size.height);
238+
}
239+
return Rect.fromLTWH(0.0, 0.0, size.width, size.height);
240+
}
241+
242+
@override
243+
bool shouldReclip(CustomClipper oldClipper) => true;
244+
}

0 commit comments

Comments
 (0)