Skip to content

Instantly share code, notes, and snippets.

@diegoveloper
Created April 5, 2020 05:52
Show Gist options
  • Save diegoveloper/b739c9eec3308b983d2c6c1b4f2b3ba5 to your computer and use it in GitHub Desktop.
Save diegoveloper/b739c9eec3308b983d2c6c1b4f2b3ba5 to your computer and use it in GitHub Desktop.
import 'package:flutter/material.dart';
import 'dart:ui';
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: SportsStorePage(),
),
),
);
}
}
const leftMargin = 40.0;
class Ball {
final int price;
final String model;
final String name;
final String image;
final Color color;
final Color textColor;
const Ball({
this.price,
this.model,
this.name,
this.image,
this.color,
this.textColor,
});
}
//24,30,31
const balls = [
const Ball(
price: 24,
name: 'Nike Strike',
model: 'Armory Black',
color: Colors.black,
textColor: Colors.white,
image: 'https://raw.githubusercontent.com/diegoveloper/flutter-samples/master/images/balls/ball1.png'),
const Ball(
price: 30,
name: 'UEFA CL 18',
model: 'UEFA blue',
color: Color(0xFF07205A),
textColor: Colors.white,
image: 'https://raw.githubusercontent.com/diegoveloper/flutter-samples/master/images/balls/ball2.png'),
const Ball(
price: 31,
name: 'Nike Strike X',
model: 'UEFA Turquoise',
color: Color(0xFF6EE897),
textColor: Colors.black,
image: 'https://raw.githubusercontent.com/diegoveloper/flutter-samples/master/images/balls/ball3.png'),
const Ball(
price: 40,
name: 'Adidas beau jeu',
model: 'UEFA 2016',
color: Color(0xFF743AD6),
textColor: Colors.white,
image: 'https://raw.githubusercontent.com/diegoveloper/flutter-samples/master/images/balls/ball4.png'),
];
class SportsStorePage extends StatefulWidget {
@override
_SportsStorePageState createState() => _SportsStorePageState();
}
class _SportsStorePageState extends State<SportsStorePage> {
final _priceNotifier = ValueNotifier<int>(balls.first.price);
final _pageController = PageController(viewportFraction: 0.9);
final _pageNotifier = ValueNotifier<double>(0.0);
Widget _buildHeader() {
Widget _buildGroup(bool selected, IconData icon) {
return Row(
children: [
Icon(
icon,
color: selected ? Colors.black : Colors.grey[300],
size: 13,
),
const SizedBox(
width: 3,
),
Text(
'Soccer ball',
style: TextStyle(
fontSize: 13,
color: selected ? Colors.black : Colors.grey,
),
),
const SizedBox(
height: 10,
),
],
);
}
return Column(
children: [
Padding(
padding: const EdgeInsets.only(left: leftMargin),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
'Sports store',
textAlign: TextAlign.start,
style: TextStyle(
fontWeight: FontWeight.w800,
fontSize: 26,
color: Colors.black,
fontStyle: FontStyle.italic,
),
),
Padding(
padding: const EdgeInsets.only(right: 20.0),
child: IconButton(
icon: Icon(
Icons.search,
color: Colors.grey[400],
),
onPressed: () {},
),
),
],
),
),
const SizedBox(
height: 10,
),
Padding(
padding: const EdgeInsets.only(left: leftMargin),
child: Container(
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(10),
border: Border.all(
color: Colors.grey[300],
width: 1,
),
),
child: Padding(
padding: const EdgeInsets.all(13.0),
child: Row(children: [
_buildGroup(true, Icons.battery_charging_full),
const SizedBox(
width: 20,
),
_buildGroup(false, Icons.shopping_basket),
]),
),
),
),
],
);
}
Widget _buildBottomNavigationBar() => BottomNavigationBar(
selectedItemColor: Colors.black,
unselectedItemColor: Colors.grey[300],
elevation: 4,
type: BottomNavigationBarType.fixed,
items: [
BottomNavigationBarItem(
title: Text(''),
icon: Icon(
Icons.home,
),
),
BottomNavigationBarItem(
title: Text(''),
icon: Icon(
Icons.shopping_basket,
),
),
BottomNavigationBarItem(
title: Text(''),
icon: Icon(
Icons.shopping_cart,
),
),
BottomNavigationBarItem(
title: Text(''),
icon: Icon(
Icons.wb_sunny,
),
)
],
);
Widget _buildBottomWidget() {
return Padding(
padding: const EdgeInsets.only(left: leftMargin),
child: Column(
children: [
ListTile(
contentPadding: EdgeInsets.zero,
leading: CircleAvatar(
backgroundColor: Colors.grey,
backgroundImage: NetworkImage('https://as00.epimg.net/img/comunes/fotos/fichas/deportistas/m/mes/large/15167.png'),
),
title: Text(
'Lionel Messi',
style: TextStyle(
fontWeight: FontWeight.w600,
),
),
subtitle: Text(
'Barcelona',
),
),
const SizedBox(
height: 30,
),
],
),
);
}
void _listener() {
_pageNotifier.value = _pageController.page;
setState(() {});
}
@override
void initState() {
lastPrice = balls.first.price;
WidgetsBinding.instance.addPostFrameCallback((_) {
_pageController.addListener(_listener);
});
super.initState();
}
@override
void dispose() {
_pageController.removeListener(_listener);
_pageController.dispose();
_pageNotifier.dispose();
_priceNotifier.dispose();
super.dispose();
}
int lastPrice = 0;
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.white,
bottomNavigationBar: _buildBottomNavigationBar(),
body: Padding(
padding: const EdgeInsets.only(top: 20.0),
child: Column(
children: [
_buildHeader(),
Expanded(
child: Padding(
padding: const EdgeInsets.symmetric(vertical: 46),
child: Stack(
children: [
Positioned(
left: 0,
top: 0,
width: MediaQuery.of(context).size.width / 3,
bottom: 20,
child: Padding(
padding: const EdgeInsets.only(left: leftMargin),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Spacer(),
ValueListenableBuilder<int>(
valueListenable: _priceNotifier,
builder: (context, value, child) {
return TweenAnimationBuilder<int>(
duration: const Duration(milliseconds: 200),
tween:
IntTween(begin: lastPrice, end: value),
builder: (context, animationValue, child) {
return Text(
"\$$animationValue",
style: TextStyle(
fontSize: 24,
fontWeight: FontWeight.w600,
color: Colors.grey[700],
),
);
},
);
}),
Spacer(),
Text(
'Available size',
textAlign: TextAlign.start,
style: TextStyle(
color: Colors.grey[400],
fontSize: 12,
),
),
const SizedBox(
height: 20,
),
SizedBox(
height: 30,
child: Row(
children: [
Expanded(
child: OutlineButton(
padding: EdgeInsets.zero,
onPressed: null,
child: Text(
'3',
textAlign: TextAlign.right,
),
),
),
const SizedBox(
width: 10,
),
Expanded(
child: OutlineButton(
padding: EdgeInsets.zero,
onPressed: null,
child: Text(
'4',
textAlign: TextAlign.right,
),
),
),
const SizedBox(
width: 10,
),
Expanded(
child: OutlineButton(
padding: EdgeInsets.zero,
onPressed: null,
child: Text(
'5',
textAlign: TextAlign.right,
),
),
),
],
),
),
],
),
),
),
ValueListenableBuilder<double>(
valueListenable: _pageNotifier,
builder: (context, value, child) => PageView.builder(
controller: _pageController,
itemCount: balls.length,
onPageChanged: (index) {
//every time the page changed, get the current page and get the price from the balls array
//update the value to the notifier and object inside [ValueListenableBuilder] will rebuild
_priceNotifier.value = balls[index].price;
lastPrice = balls[index].price;
},
itemBuilder: (context, index) {
final lerp = lerpDouble(
1, 0, (index - _pageNotifier.value).abs());
double opacity = lerpDouble(
1.0, 0.0, (index - _pageNotifier.value).abs());
opacity = opacity < 0 ? 0 : opacity;
return Opacity(
opacity: opacity,
child: Stack(
fit: StackFit.expand,
children: [
Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Expanded(
child: Padding(
padding: const EdgeInsets.only(
top: 8.0,
left: 20,
),
child: Hero(
tag: 'hero_text_${balls[index].name}',
child: Material(
color: Colors.transparent,
child: Text(
balls[index]
.name,
maxLines: 2,
textAlign: TextAlign.start,
style: TextStyle(
color: Colors.black,
fontWeight: FontWeight.w700,
fontSize: 24,
),
),
),
),
),
),
Expanded(
child: InkWell(
onTap: () {
Navigator.of(context)
.push(PageRouteBuilder(
transitionDuration: const Duration(
milliseconds: 800),
pageBuilder: (_, animation, __) =>
FadeTransition(
opacity: animation,
child: SportsStoreDetailPage(
ball: balls[index],
),
),
));
},
child: Hero(
tag:
'hero_background_${balls[index].name}',
child: Container(
decoration: BoxDecoration(
color: balls[index].color,
borderRadius:
BorderRadius.circular(20.0),
),
child: Align(
alignment: Alignment.bottomLeft,
child: Padding(
padding:
const EdgeInsets.all(30),
child: Material(
color: Colors.transparent,
child: Text(
balls[index]
.model
.split(" ")
.join("\n"),
maxLines: 2,
textAlign: TextAlign.start,
style: TextStyle(
color: balls[index]
.textColor,
fontWeight:
FontWeight.w600,
),
),
),
),
),
),
),
),
),
],
),
Center(
child: Transform.scale(
alignment: Alignment.centerRight,
scale: lerp,
child: Hero(
tag: 'hero_ball_${balls[index].name}',
child: Image.network(
balls[index].image,
height:
MediaQuery.of(context).size.width /
3,
),
),
),
),
],
),
);
},
),
),
],
),
),
),
_buildBottomWidget(),
],
),
),
);
}
}
class SportsStoreDetailPage extends StatelessWidget {
final Ball ball;
const SportsStoreDetailPage({Key key, this.ball}) : super(key: key);
@override
Widget build(BuildContext context) {
final size = MediaQuery.of(context).size;
return Scaffold(
body: Stack(
fit: StackFit.expand,
children: [
Positioned(
top: 0,
right: 0,
width: size.width / 2.5,
height: size.height / 1.8,
child: Hero(
tag: 'hero_background_${ball.name}',
child: Container(
decoration: BoxDecoration(
color: ball.color,
borderRadius: BorderRadius.only(
bottomLeft: Radius.circular(30),
),
),
),
),
),
Positioned(
left: 30,
top: 100,
child: Hero(
tag: 'hero_text_${ball.name}',
child: Material(
color: Colors.transparent,
child: Text(
ball.name,
style: TextStyle(
color: Colors.black,
fontSize: 40,
fontWeight: FontWeight.w700),
),
),
),
),
Positioned(
right: 30,
top: size.height / 4,
child: Hero(
tag: 'hero_ball_${ball.name}',
child: Image.network(
ball.image,
height: MediaQuery.of(context).size.width / 2.2,
),
),
),
Positioned(
left: 20,
top: 40,
child: BackButton(),
),
],
),
);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment