Skip to content

Instantly share code, notes, and snippets.

@diegoveloper
Created April 5, 2020 06:07
Show Gist options
  • Save diegoveloper/c3d1e6e82bd7b39ba303a0d0364a22f7 to your computer and use it in GitHub Desktop.
Save diegoveloper/c3d1e6e82bd7b39ba303a0d0364a22f7 to your computer and use it in GitHub Desktop.
import 'package:flutter/material.dart';
import 'dart:ui';
import 'dart:math' as math;
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
theme: ThemeData.light(),
debugShowCheckedModeBanner: false,
home: Scaffold(
body: Center(
child: ShoesStorePage(),
),
),
);
}
}
const bottomBackgroundColor = Color(0xFFF1F2F7);
const brands = ['Nike', 'Adidas', 'Jordan', 'Puma', 'Reebok'];
const marginSide = 14.0;
const leftItemSeparator = const SizedBox(
width: 30,
);
class Shoe {
final String name;
final String image;
final double price;
final Color color;
const Shoe({
this.name,
this.image,
this.price,
this.color,
});
}
const shoes = [
const Shoe(
name: 'NIKE EPICT-REACT',
price: 130.00,
image:
'https://raw.githubusercontent.com/diegoveloper/flutter-samples/master/images/shoes/1.png',
color: Color(0xFF5574b9)),
const Shoe(
name: 'NIKE AIR-MAX',
price: 130.00,
image:
'https://raw.githubusercontent.com/diegoveloper/flutter-samples/master/images/shoes/2.png',
color: Color(0xFF52b8c3)),
const Shoe(
name: 'NIKE AIR-270',
price: 150.00,
image:
'https://raw.githubusercontent.com/diegoveloper/flutter-samples/master/images/shoes/3.png',
color: Color(0xFFE3AD9B)),
const Shoe(
name: 'NIKE EPICT-REACTII',
price: 160.00,
image:
'https://raw.githubusercontent.com/diegoveloper/flutter-samples/master/images/shoes/4.png',
color: Color(0xFF444547)),
];
const shoesBottom = [
const Shoe(
name: 'NIKE AIR-MAX',
price: 170.00,
image:
'https://raw.githubusercontent.com/diegoveloper/flutter-samples/master/images/shoes/3.png'),
const Shoe(
name: 'NIKE AIR FORCE',
price: 130.00,
image:
'https://raw.githubusercontent.com/diegoveloper/flutter-samples/master/images/shoes/4.png'),
];
class ShoesStorePage extends StatefulWidget {
@override
_ShoesStorePageState createState() => _ShoesStorePageState();
}
class _ShoesStorePageState extends State<ShoesStorePage> {
final _pageController = PageController(viewportFraction: 0.78);
final _pageNotifier = ValueNotifier(0.0);
Widget _buildHeader() {
return Padding(
padding: const EdgeInsets.only(top: 20.0),
child: Column(
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
'Discover',
textAlign: TextAlign.start,
style: TextStyle(
fontWeight: FontWeight.w700,
fontSize: 26,
color: Colors.black,
),
),
Spacer(),
CircleAvatar(
backgroundColor: Colors.grey[200],
child: IconButton(
icon: Icon(
Icons.search,
color: Colors.black,
),
onPressed: () {},
),
),
const SizedBox(
width: 10,
),
CircleAvatar(
backgroundColor: Colors.grey[200],
child: IconButton(
icon: Icon(
Icons.notifications_none,
color: Colors.black,
),
onPressed: () {},
),
),
],
),
const SizedBox(
height: 10,
),
SizedBox(
height: 40,
child: ListView.builder(
scrollDirection: Axis.horizontal,
itemCount: brands.length,
itemBuilder: (_, index) => Padding(
padding:
const EdgeInsets.symmetric(horizontal: 16.0, vertical: 4),
child: Text(
brands[index],
style: TextStyle(
fontWeight: FontWeight.w700,
color: index == 0 ? Colors.black : Colors.grey[400],
fontSize: 17,
),
),
),
),
),
],
),
);
}
Widget _buildBottom(BuildContext context) {
Widget _buildBottomItem(Shoe shoe) => Card(
elevation: 4,
child: Stack(
children: [
Center(
child: Column(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Align(
alignment: Alignment.centerRight,
child: Padding(
padding: const EdgeInsets.only(top: 5.0, right: 8),
child: Icon(Icons.favorite_border),
)),
Expanded(
child: Transform.rotate(
angle: degToRad(18),
child: Image.network(shoe.image)),
),
Text(
shoe.name,
style: TextStyle(fontSize: 12),
),
Text(
"\$${shoe.price}",
style: TextStyle(fontSize: 11),
),
const SizedBox(
height: 8,
),
],
),
),
RotatedBox(
quarterTurns: 3,
child: Container(
child: Padding(
padding: const EdgeInsets.symmetric(
horizontal: 18.0, vertical: 4),
child: Text(
'NEW',
style: TextStyle(
color: Colors.white,
fontSize: 12,
),
),
),
color: Colors.pinkAccent,
),
),
],
),
);
return Container(
color: bottomBackgroundColor,
height: MediaQuery.of(context).size.height * 0.29,
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: marginSide),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
'More',
style: TextStyle(
fontWeight: FontWeight.w700,
fontSize: 19,
),
),
IconButton(
icon: Icon(
Icons.arrow_forward,
color: Colors.black,
),
onPressed: null,
),
],
),
Expanded(
child: Row(
children: [
Expanded(
child: _buildBottomItem(shoesBottom.first),
),
const SizedBox(
width: 10,
),
Expanded(
child: _buildBottomItem(shoesBottom.last),
),
],
),
),
],
),
),
);
}
Widget _buildBottomNavigationBar() => BottomNavigationBar(
selectedItemColor: Colors.red,
backgroundColor: bottomBackgroundColor,
unselectedItemColor: Colors.grey[400],
elevation: 4,
type: BottomNavigationBarType.fixed,
items: [
BottomNavigationBarItem(
title: Text(''),
icon: Icon(
Icons.home,
),
),
BottomNavigationBarItem(
title: Text(''),
icon: Icon(
Icons.favorite_border,
),
),
BottomNavigationBarItem(
title: Text(''),
icon: Icon(
Icons.location_city,
),
),
BottomNavigationBarItem(
title: Text(''),
icon: Icon(
Icons.shopping_cart,
),
),
BottomNavigationBarItem(
title: Text(''),
icon: Icon(
Icons.person_outline,
),
)
],
);
void _listener() {
_pageNotifier.value = _pageController.page;
setState(() {});
}
@override
void initState() {
WidgetsBinding.instance.addPostFrameCallback((_) {
_pageController.addListener(_listener);
});
super.initState();
}
@override
void dispose() {
_pageController.removeListener(_listener);
_pageController.dispose();
_pageNotifier.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
Widget _buildLeftItem(String title, bool selected) => Text(
title,
style: TextStyle(
fontWeight: FontWeight.w700,
color: selected ? Colors.black : Colors.grey[400],
fontSize: 14,
),
);
final size = MediaQuery.of(context).size;
const marginCenter = EdgeInsets.symmetric(horizontal: 50, vertical: 15);
return Scaffold(
backgroundColor: Colors.white,
bottomNavigationBar: _buildBottomNavigationBar(),
body: Column(
children: [
Padding(
padding: const EdgeInsets.all(marginSide),
child: _buildHeader(),
),
Expanded(
child: Stack(
fit: StackFit.expand,
children: [
Positioned(
left: 0,
top: 0,
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 12.0),
child: RotatedBox(
quarterTurns: 3,
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
_buildLeftItem('New', false),
leftItemSeparator,
_buildLeftItem('Featured', true),
leftItemSeparator,
_buildLeftItem('Upcoming', false),
],
),
),
),
),
Positioned(
left: 0,
bottom: 0,
right: 0,
child: _buildBottom(context),
),
Positioned(
left: 0,
right: 0,
top: -10,
height: size.height * 0.50,
child: PageView.builder(
controller: _pageController,
itemCount: shoes.length,
itemBuilder: (context, index) {
final t = (index - _pageNotifier.value);
final rotationY = lerpDouble(0, 90, t);
final translationX = lerpDouble(0, -50, t);
final scale = lerpDouble(0, -0.2, t);
final translationXShoes = lerpDouble(0, 150, t);
final rotationShoe = lerpDouble(0, -45, t);
final transform = Matrix4.identity();
transform.translate(translationX);
transform.setEntry(3, 2, 0.001);
transform.scale(1 - scale);
transform.rotateY(degToRad(rotationY));
final transformShoe = Matrix4.identity();
transformShoe.translate(translationXShoes);
transformShoe.rotateZ(degToRad(rotationShoe));
return Padding(
padding: const EdgeInsets.symmetric(
vertical: 28.0,
),
child: InkWell(
hoverColor: Colors.white,
onTap: () {
Navigator.of(context).push(PageRouteBuilder(
transitionDuration:
const Duration(milliseconds: 800),
pageBuilder: (_, animation, __) =>
FadeTransition(
opacity: animation,
child: ShoesStoreDetailPage(
shoe: shoes[index],
),
),
));
},
child: Stack(
overflow: Overflow.visible,
children: [
Transform(
alignment: Alignment.center,
transform: transform,
child: Stack(
children: [
Hero(
tag:
'hero_background_${shoes[index].name}',
child: Card(
elevation: 6,
shape: RoundedRectangleBorder(
borderRadius:
BorderRadius.circular(20),
),
margin: marginCenter,
color: shoes[index].color,
child: const SizedBox.expand(),
),
),
Container(
margin: marginCenter,
padding: const EdgeInsets.all(12.0),
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment:
CrossAxisAlignment.stretch,
children: [
Row(
mainAxisAlignment:
MainAxisAlignment
.spaceBetween,
children: [
Text(
shoes[index]
.name
.split(' ')
.join('\n'),
style: TextStyle(
color: Colors.white,
fontWeight: FontWeight.w600,
fontSize: 18,
),
),
Icon(
Icons.favorite_border,
color: Colors.white,
)
],
),
const SizedBox(
height: 10,
),
Text(
"\$${shoes[index].price}",
style: TextStyle(
color: Colors.white,
fontSize: 12,
),
),
Spacer(),
Align(
alignment: Alignment.bottomRight,
child: Icon(
Icons.arrow_forward,
color: Colors.white,
),
),
],
),
),
],
),
),
Center(
child: Transform(
alignment: Alignment.center,
transform: transformShoe,
child: Hero(
tag: 'hero_image_${shoes[index].name}',
child: Image.network(
shoes[index].image,
height: size.width / 2.5,
),
),
),
),
],
),
),
);
}),
),
],
),
),
],
),
);
}
}
class ShoesStoreDetailPage extends StatelessWidget {
final Shoe shoe;
const ShoesStoreDetailPage({Key key, this.shoe}) : super(key: key);
@override
Widget build(BuildContext context) {
final size = MediaQuery.of(context).size;
return Scaffold(
body: Stack(
fit: StackFit.expand,
children: [
Positioned(
top: -size.width / 2,
right: -size.width / 3,
width: size.width * 1.4,
height: size.width * 1.4,
child: Hero(
tag: 'hero_background_${shoe.name}',
child: Container(
decoration: BoxDecoration(
color: shoe.color,
shape: BoxShape.circle,
),
),
),
),
Align(
alignment: Alignment.topCenter,
child: SizedBox(
height: kToolbarHeight + 20,
child: AppBar(
backgroundColor: Colors.transparent,
elevation: 0,
title: Text(
shoe.name.split(' ').first,
style: TextStyle(
color: Colors.white,
fontSize: 25,
fontWeight: FontWeight.w700),
),
actions: [
Padding(
padding: const EdgeInsets.only(right: 14.0),
child: Material(
elevation: 10,
shape: CircleBorder(
side: BorderSide(
color: shoe.color,
)),
color: shoe.color,
child: Padding(
padding: const EdgeInsets.all(5.0),
child: Icon(Icons.favorite_border),
),
),
),
]),
)),
Align(
alignment: Alignment.topCenter,
child: Padding(
padding: EdgeInsets.only(top: size.height * 0.1),
child: Hero(
tag: 'hero_image_${shoe.name}',
child: Image.network(
shoe.image,
height: MediaQuery.of(context).size.width / 1.2,
),
),
),
),
],
),
);
}
}
num degToRad(num deg) => deg * (math.pi / 180.0);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment