应用程序的功能
它在底部导航栏中有两个项目,在第一部分(课程)中,用户可以添加课程列表,每次添加新课程时,它将更新他们的整体 GPA 和最高平均水平。另一个是简单的“我需要在决赛中获得什么才能通过”部分(决赛预测器)。
以下是屏幕顶部的代码,显示了用户的整体 gpa、课程数量和最高平均值
import 'package:flutter/material.dart';
import 'package:gradecalculator/models/course.dart';
class TopDisplay extends StatefulWidget {
final List<Course> courses;
final maxAverage;
final overallGPA;
TopDisplay(this.courses, this.maxAverage, this.overallGPA);
@override
_TopDisplayState createState() => _TopDisplayState();
}
class _TopDisplayState extends State<TopDisplay> {
@override
Widget build(BuildContext context) {
final mediaQuery = MediaQuery.of(context);
return Card(
margin: EdgeInsets.all(10),
elevation: 5,
color: Colors.grey[400],
child:Row(
children: <Widget> [
Container(
margin: EdgeInsets.all(10),
child: CircleAvatar(
child: widget.courses.isEmpty ? Text(
"0.0",
style: TextStyle(
color: Colors.white,
fontWeight: FontWeight.bold,
fontSize: 25,
),
)
: Text(
"${widget.overallGPA.toStringAsFixed(2)}",
style: TextStyle(
color: Colors.white,
fontWeight: FontWeight.bold,
fontSize: 25
),
),
radius: 45,
backgroundColor: Colors.red,
),
),
Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: <Widget>[
SizedBox(
height: mediaQuery.size.height * 0.07,
),
Row(
children:<Widget> [
const Text(
'Number of Courses: ',
style: TextStyle(
color: Colors.white,
fontSize: 16,
fontWeight: FontWeight.bold
),
),
Text(
'${widget.courses.length}',
style: TextStyle(
color: Colors.white,
fontSize: 16
),
),
],
),
Row(
children: <Widget>[
const Text(
'Highest Average: ',
style: TextStyle(
color: Colors.white,
fontSize: 16,
fontWeight: FontWeight.bold
),
),
widget.courses.isEmpty ? Text(
'0.0',
style: TextStyle(
color: Colors.white,
fontSize: 16
),
)
:
Text(
'${widget.maxAverage}%',
style: TextStyle(
color: Colors.white,
fontSize: 16
),
)
]
),
],
),
],
),
);
}
}
问题
如果我添加课程,一切都会正常工作,并且将显示需要显示的所有内容。如果我导航到决赛预测器,然后返回课程,课程列表仍会显示,但总体 GPA 和最高平均数会回到 0.0。
带有底部导航栏的选项卡屏幕的代码
import 'package:flutter/material.dart';
import 'predictions_screens.dart';
import '../models/course.dart';
import 'home_page.dart';
class TabsScreen extends StatefulWidget {
final List<Course> listedCourses;
TabsScreen(this.listedCourses);
@override
_TabsScreenState createState() => _TabsScreenState();
}
class _TabsScreenState extends State<TabsScreen> {
final List<Map<String, Object>> _pages = [
{'page': HomePage(), 'title': 'Courses'},
{'page': PredictionsScreen(), 'title': 'Final Grade Calculator'}
];
int _selectedPageIndex = 0;
void _selectPage(int index) {
setState(() {
_selectedPageIndex = index;
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
centerTitle: true,
title: Text(_pages[_selectedPageIndex]['title']),
),
body: _pages[_selectedPageIndex]['page'],
bottomNavigationBar: BottomNavigationBar(
onTap: _selectPage,
backgroundColor: Theme.of(context).primaryColor,
unselectedItemColor: Colors.white,
selectedItemColor: Theme.of(context).accentColor,
currentIndex: _selectedPageIndex,
items: [
BottomNavigationBarItem(
backgroundColor: Theme.of(context).primaryColor,
icon: Icon(Icons.book),
title: Text('Courses')),
BottomNavigationBarItem(
backgroundColor: Theme.of(context).primaryColor,
icon: Icon(Icons.scatter_plot),
title: Text('Finals Predictor'))
],
),
);
}
}
main.dart 文件中的代码
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'screens/predictions_screens.dart';
import 'screens/tabs_screen.dart';
import './models/course.dart';
import './screens/home_page.dart';
void main() {
//To ensure that the app only is used in portrait mode
WidgetsFlutterBinding.ensureInitialized();
SystemChrome.setPreferredOrientations(
[DeviceOrientation.portraitUp, DeviceOrientation.portraitDown]);
runApp(GradeCalculator());
}
class GradeCalculator extends StatefulWidget {
@override
_GradeCalculatorState createState() => _GradeCalculatorState();
}
class _GradeCalculatorState extends State<GradeCalculator> {
List<Course> coursesOnScreen = [];
@override
Widget build(BuildContext context) {
return MaterialApp(
title: "Grade Calculator",
theme: ThemeData(
primarySwatch: Colors.red,
accentColor: Colors.blueAccent[200],
),
//home: HomePage(),
routes: {
'/': (ctx) => TabsScreen(coursesOnScreen),
HomePage.routeName: (ctx) => HomePage(),
PredictionsScreen.routeName: (ctx) => PredictionsScreen()
},
);
}
}
课程列表输出代码
import 'package:flutter/material.dart';
import 'package:gradecalculator/models/course.dart';
class CourseList extends StatelessWidget {
static const routeName = '/course-list';
final List<Course> courses;
final Function deletec;
final Function setHighestAverage;
final Function calculateGPA;
CourseList(this.courses, this.deletec, this.setHighestAverage, this.calculateGPA);
@override
Widget build(BuildContext context) {
return ListView.builder(
itemCount: courses.length,
itemBuilder: (ctx, index){
return Card(
elevation: 5,
margin: EdgeInsets.symmetric(
vertical: 5,
horizontal: 8,
),
child: ListTile(
leading: CircleAvatar(
radius: 20,
child: Padding(
padding: EdgeInsets.all(6),
child: FittedBox(
child: Text(
'${courses[index].courseAverage}'
),
),
),
),
title: Text(
courses[index].courseName,
style: TextStyle(
fontWeight: FontWeight.bold
),
),
subtitle: Text(
'Course Weight: ${courses[index].courseWeight}'
),
trailing: IconButton(
icon: Icon(
Icons.delete,
color: Colors.grey,
),
onPressed: () {
deletec(courses[index].courseName);
setHighestAverage(courses);
calculateGPA(courses);
},
),
),
);
}
);
}
}
调用 TopDisplay 的主页文件
import 'package:flutter/material.dart';
import '../models/course.dart';
import '../widgets/newCourse.dart';
import '../widgets/course_list.dart';
import '../widgets/top_display.dart';
class HomePage extends StatefulWidget {
static const routeName = '/home-page';
final List<Course> visibleCourses = [];
@override
_HomePageState createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> {
double maxAverage = 0.00;
double overallGPA = 0.00;
//List for courses as users add courses, this is where they will be stored
//Calculates the total GPA shown in the top chart
void calculateGPA(List<Course> courseList) {
double totalWeight = 0;
double intermediateGPA = 0;
for (int i = 0; i < courseList.length; i++) {
totalWeight += courseList[i].courseWeight;
intermediateGPA +=
(courseList[i].courseAverage * courseList[i].courseWeight);
overallGPA = (intermediateGPA / totalWeight);
}
}
//Finds the highest average that is displayed in the top chart
void setHighestAverage(List<Course> courseList) {
calculateGPA(courseList);
for (int i = 0; i < courseList.length; i++) {
if (courseList[i].courseAverage >= maxAverage) {
maxAverage = courseList[i].courseAverage;
}
}
}
//Opens up the modal sheet that the user can use to enter a new course
void _startAddNewCourse(BuildContext ctx) {
showModalBottomSheet(
context: ctx,
builder: (_) {
return GestureDetector(
onTap: () {},
behavior: HitTestBehavior.opaque,
child: NewCourse(_addNewCourse),
);
},
);
}
//Initializes the new course and adds it to the courses list above
void _addNewCourse(String cTitle, double cAverage, double cWeight) {
final newc = Course(
courseName: cTitle,
courseAverage: cAverage,
courseWeight: cWeight,
);
setState(() {
widget.visibleCourses.add(newc);
setHighestAverage(widget.visibleCourses);
});
}
void _deleteCourse(String courseName) {
setState(() {
widget.visibleCourses.removeWhere((c) {
return c.courseName == courseName;
});
});
}
@override
Widget build(BuildContext context) {
//Setting up a mediaquery variable so that sizes can be determined based on phone size
final mediaQuery = MediaQuery.of(context);
return SafeArea(
child: Scaffold(
body: SingleChildScrollView(
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: <Widget>[
TopDisplay(widget.visibleCourses, maxAverage, overallGPA),
Container(
height: mediaQuery.size.height * 0.7,
child: CourseList(
widget.visibleCourses, _deleteCourse, setHighestAverage, calculateGPA),
),
],
),
),
floatingActionButton: FloatingActionButton(
onPressed: () => _startAddNewCourse(context),
backgroundColor: Colors.red,
child: Icon(Icons.add),
),
),
);
}
}