熟能生巧,跟着老外写页面第1篇
完成效果:
页面截图.gif
项目结构:
image.png
mian.dart:
import 'package:flutter/material.dart';
import 'package:eat/ui/profile_screen.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
// This widget is the root of your application.
@override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: ProfileScreen(),
);
}
}
profile_screen.dart:
import 'package:flutter/material.dart';
import 'package:eat/model/meal.dart';
import 'package:vector_math/vector_math_64.dart' as math;
import 'package:eat/ui/WorkoutScreen.dart';
import 'package:page_transition/page_transition.dart';
class ProfileScreen extends StatelessWidget {
@override
Widget build(BuildContext context) {
final height = MediaQuery.of(context).size.height;
final width = MediaQuery.of(context).size.width;
return Scaffold(
backgroundColor: const Color(0xFFE9E9E9),
body: SafeArea(
child: Stack(
children: <Widget>[
Positioned(
top: 0,
left: 0,
height: height*0.3,
right: 0,
child: ClipRRect(
borderRadius: const BorderRadius.vertical(
bottom: const Radius.circular(40),
),
child: Container(
padding: EdgeInsets.only(left: 20, right: 20),
color: Colors.white,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
ListTile(
title: Text("Date Year", style: TextStyle(
fontWeight: FontWeight.w400,
fontSize: 14
),),
subtitle: Text('Hello Eason',style: TextStyle(
fontWeight: FontWeight.w800,
fontSize: 16,
color: Colors.black
)),
trailing: ClipOval(child: Image.asset('images/head.jpg')),
),
Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: <Widget>[
_RadialProgress(width: width*0.4, height: width*0.4,),
Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.start,
children: <Widget>[
_IngredientProgress(
ingredient: "Protein",
progress: 0.3,
width: width*0.3,
progressColor: Colors.green,
leftAmount: 72,
),
_IngredientProgress(
ingredient: "Carbs",
width: width*0.3,
progress: 0.2,
progressColor: Colors.red,
leftAmount: 252,
),
_IngredientProgress(
ingredient: "Protein",
width: width*0.3,
progress: 0.1,
progressColor: Colors.yellowAccent,
leftAmount: 61,
)
],
)
],
)
],
),
),
),
),
Positioned(
top: height*0.32,
left: 0,
right: 0,
child: Container(
height: height * 0.52,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Padding(
padding: const EdgeInsets.only(
bottom: 8,
left: 32,
right: 16
),
child: Text(
"MEALS FOR TODAY",
style:const TextStyle(
color: Colors.blueGrey,
fontSize: 16,
fontWeight: FontWeight.w700
),
),
),
Expanded(
child: SingleChildScrollView(
scrollDirection: Axis.horizontal,
child: Row(
children: <Widget>[
SizedBox( width:30 ),
_MealCard(meal: meals[0]),
_MealCard(meal: meals[1]),
_MealCard(meal: meals[2])
],
),
)
),
SizedBox(height: 20,),
Expanded(
child: GestureDetector(
onTap: (){
Navigator.push(context, PageTransition(type: PageTransitionType.scale, alignment:Alignment(0, 0.5), child: WorkoutScreen()));
},
child: Container(
margin: EdgeInsets.only(
left: 32,
right: 32,
bottom: 10
),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(30),
gradient: LinearGradient(
begin: Alignment.topCenter,
end: Alignment.bottomCenter,
colors: [
const Color(0xFF20008B),
const Color(0xFF200087)
]
)
),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Padding( padding: EdgeInsets.only(left: 20, top: 10), child: Text('Some Text', style: TextStyle( color:Colors.white70, fontSize: 16),)),
Padding( padding: EdgeInsets.only(left: 20, top: 5, bottom: 10), child: Text('Some Text2', style: TextStyle( color:Colors.white, fontSize: 18, fontWeight: FontWeight.w700),)),
Padding(
padding: EdgeInsets.only(left: 20, right: 20),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[
Container(
child: Icon(Icons.music_video, color: Colors.white, size: 40, ),
width: 60,
height: 60,
decoration: BoxDecoration(
color: Colors.white.withOpacity(0.6),
borderRadius: BorderRadius.circular(6)
),
),
Container(
child: Icon(Icons.face, color: Colors.white, size: 40, ),
width: 60,
height: 60,
decoration: BoxDecoration(
color: Colors.white.withOpacity(0.6),
borderRadius: BorderRadius.circular(6)
),
),Container(
child: Icon(Icons.movie, color: Colors.white, size: 40, ),
width: 60,
height: 60,
decoration: BoxDecoration(
color: Colors.white.withOpacity(0.6),
borderRadius: BorderRadius.circular(6)
),
),Container(
child: Icon(Icons.person, color: Colors.white, size: 40, ),
width: 60,
height: 60,
decoration: BoxDecoration(
color: Colors.white.withOpacity(0.6),
borderRadius: BorderRadius.circular(6)
),
),
],
),
)
],
),
),
),
)
],
),
),
)
],
),
),
bottomNavigationBar: ClipRRect(
borderRadius: BorderRadius.vertical(
top: const Radius.circular(40)
),
child: BottomNavigationBar(
iconSize: 40,
selectedIconTheme: IconThemeData(
color: const Color(0xff200087),
),
unselectedIconTheme: IconThemeData(
color: Colors.black12,
),
items: [
BottomNavigationBarItem(
icon: Icon(
Icons.home
),
title: Text('', style: TextStyle( color: Colors.white),)
),
BottomNavigationBarItem(
icon: Icon(
Icons.search
),
title: Text('', style: TextStyle( color: Colors.white),)
),
BottomNavigationBarItem(
icon: Icon(
Icons.person
),
title: Text('', style: TextStyle( color: Colors.white),)
)
],
),
),
);
}
}
class _MealCard extends StatelessWidget {
final Meal meal;
const _MealCard({Key key, @required this.meal}):super(key : key);
@override
Widget build(BuildContext context) {
// TODO: implement build
return Container(
margin:const EdgeInsets.only(right: 20, bottom: 10),
child: Material(
borderRadius: BorderRadius.all(Radius.circular(20)),
elevation: 4,
child: Column(
mainAxisSize: MainAxisSize.max,
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Flexible(
fit: FlexFit.tight,
child: ClipRRect(
borderRadius: BorderRadius.vertical(top: Radius.circular(20)),
child: Image.asset(
meal.imagePath,
width: 150,
fit: BoxFit.fill,
)
),
),
Flexible(
fit: FlexFit.tight,
child: Padding(
padding: const EdgeInsets.only(left: 8.0),
child: Column(
crossAxisAlignment:CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[
SizedBox(height: 2,),
Text(meal.mealTime, style:TextStyle( fontWeight: FontWeight.w500, fontSize: 14, color: Colors.blueGrey),),
Text(meal.name, style:TextStyle( fontWeight: FontWeight.w500, fontSize: 14, color: Colors.black)),
Text(meal.kiloCaloriesBrunt, style:TextStyle( fontSize: 14, color: Colors.blueGrey)),
Text(meal.timeTaken, style:TextStyle( fontSize: 14, color: Colors.blueGrey)),
SizedBox(
height: 16,
)
],
),
),
)
],
),
),
);
}
}
class _RadialProgress extends StatelessWidget{
final double width, height;
const _RadialProgress({Key key, this.width, this.height}) : super(key: key);
@override
Widget build(BuildContext context) {
// TODO: implement build
return CustomPaint(
painter: _RadioPainter(progress:0.7),
child: Container(
width: width,
height: height,
child: Center(
child: RichText(
textAlign: TextAlign.center,
text: TextSpan(
children: [
TextSpan(
text: "1731",
style: TextStyle(
fontSize: 26,
fontWeight: FontWeight.w700,
color: const Color(0xFF200087)
)
),
TextSpan(text:'\n'),
TextSpan(
text: "some text",
style: TextStyle(
fontSize: 18,
fontWeight: FontWeight.w500,
color: const Color(0xFF200087)
)
)
]
),
),
),
),
);
}
}
class _RadioPainter extends CustomPainter{
final double progress;
_RadioPainter({this.progress});
@override
void paint(Canvas canvas, Size size) {
Paint paint = Paint()
..strokeWidth = 10
..color = Color(0xFF200087)
..style = PaintingStyle.stroke
..strokeCap = StrokeCap.round;
Offset center = Offset(size.width / 2, size.height / 2);
double relativeProgress = 360 * progress;
// canvas.drawCircle(center, size.width/2, paint);
canvas.drawArc(
Rect.fromCircle(center: center, radius: size.width/2),
math.radians(-90),
math.radians(relativeProgress),
false,
paint
);
}
@override
bool shouldRepaint(CustomPainter oldDelegate) {
// TODO: implement shouldRepaint
return true;
}
}
class _IngredientProgress extends StatelessWidget{
final String ingredient;
final double leftAmount, width;
final double progress;
final Color progressColor;
const _IngredientProgress({Key key, this.ingredient, this.leftAmount, this.width, this.progress, this.progressColor}) : super(key: key);
@override
Widget build(BuildContext context) {
// TODO: implement build
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Text( ingredient, style: TextStyle(
fontSize: 14,
fontWeight: FontWeight.w700
)),
Row(
children: <Widget>[
Container(
height: 10,
width: width,
decoration: BoxDecoration(
borderRadius: BorderRadius.all(Radius.circular(5)),
color: Colors.black12,
),
child: Align(
child: Container(
height: 10,
width: width*progress,
decoration: BoxDecoration(
borderRadius: BorderRadius.all(Radius.circular(5)),
color: progressColor,
),
),
alignment: Alignment.topLeft,
),
),
Text("${leftAmount}", style: TextStyle(color: Colors.blueGrey),)
],
)
],
);
}
}
WorkoutScreen.dart:
import 'package:flutter/material.dart';
class WorkoutScreen extends StatelessWidget{
@override
Widget build(BuildContext context) {
// TODO: implement build
return Scaffold(
backgroundColor: const Color(0xFF20008B),
body: Container(
alignment: Alignment.topCenter,
padding: EdgeInsets.only(top: MediaQuery.of(context).padding.top),
child: GestureDetector(
onTap: (){ Navigator.of(context).pop(); },
child: Icon( Icons.close, size: 50, color: Colors.white, )
),
),
);
}
}
pubspec.yaml:
name: eat
description: A new Flutter application.
# The following defines the version and build number for your application.
# A version number is three numbers separated by dots, like 1.2.43
# followed by an optional build number separated by a +.
# Both the version and the builder number may be overridden in flutter
# build by specifying --build-name and --build-number, respectively.
# In Android, build-name is used as versionName while build-number used as versionCode.
# Read more about Android versioning at https://developer.android.com/studio/publish/versioning
# In iOS, build-name is used as CFBundleShortVersionString while build-number used as CFBundleVersion.
# Read more about iOS versioning at
# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
version: 1.0.0+1
environment:
sdk: ">=2.1.0 <3.0.0"
dependencies:
flutter:
sdk: flutter
page_transition: ^1.1.6
# The following adds the Cupertino Icons font to your application.
# Use with the CupertinoIcons class for iOS style icons.
cupertino_icons: ^0.1.2
dev_dependencies:
flutter_test:
sdk: flutter
# For information on the generic Dart part of this file, see the
# following page: https://dart.dev/tools/pub/pubspec
# The following section is specific to Flutter.
flutter:
# The following line ensures that the Material Icons font is
# included with your application, so that you can use the icons in
# the material Icons class.
uses-material-design: true
# To add assets to your application, add an assets section, like this:
assets:
- images/fruit.jpg
- images/head.jpg
# - images/a_dot_ham.jpeg
# An image asset can refer to one or more resolution-specific "variants", see
# https://flutter.dev/assets-and-images/#resolution-aware.
# For details regarding adding assets from package dependencies, see
# https://flutter.dev/assets-and-images/#from-packages
# To add custom fonts to your application, add a fonts section here,
# in this "flutter" section. Each entry in this list should have a
# "family" key with the font family name, and a "fonts" key with a
# list giving the asset and other descriptors for the font. For
# example:
# fonts:
# - family: Schyler
# fonts:
# - asset: fonts/Schyler-Regular.ttf
# - asset: fonts/Schyler-Italic.ttf
# style: italic
# - family: Trajan Pro
# fonts:
# - asset: fonts/TrajanPro.ttf
# - asset: fonts/TrajanPro_Bold.ttf
# weight: 700
#
# For details regarding fonts from package dependencies,
# see https://flutter.dev/custom-fonts/#from-packages
fruit.jpg:
fruit.jpg
head.jpg:自己找
独家编程小技巧:
image.png
_MealCard 内部定义急接参meal之后,可以选中meal,按住alt+enter出现自动补全构造语法,以前的我是死记硬背的,后来直接释放了这部分记忆用来学习
知识点:
1.绘制圆弧
2.便捷的圆形头像
3.路由动画
网友评论