本文为列表UI和详情页面的第二部门,集中写动态内容,导航,详情页,优化字体,图标,文本。
增加图片及字体
增加明细页面中用到的图片和雅黑字体(Raleway fonts)到assets中,创建fonts目录,下载ttf文件至该目录中。增加图片到assets目录中。打开pubspec.yaml文件增加以下内容
assets:
- drive-steering-wheel.jpg
fonts:
- family: Raleway
fonts:
- asset: fonts/Raleway-Regular.ttf
- asset: fonts/Raleway-Thin.ttf
style: normal
动态列表内容
创建展示课程对象的类来建立课程列表项的动态内容。为了简化开发,课程价格数据类型为整型,其他(标题、年级、内容)为字符串类型。创建lesson.dart文件,构造体类代码如下:
class Lesson {
String title;
String level;
double indicatorValue;
int price;
String content;
Lesson(
{this.title, this.level, this.indicatorValue, this.price, this.content});
}
在lib文件夹下建立model文件夹保存该文件,使用前使用以下代码导入lesson.dart
import 'package:beautiful_list/model/lesson.dart';
通常,数据需要通过API请求获取。本案例为方便直接在main.dart文件里调用getLessions()返回课程列表
List getLessons(){
return [
Lesson(
title: "Flutter入门",
level: "初级",
indicatorValue: 0.33,
price: 20,
content:
"首先花几分钟阅读本节中的信息。 启动您的应用程序,然后单击“设置”菜单。 在设置页面上,单击保存按钮。 您应该在页面中间看到一个圆形的进度指示器显示,并且由于启动了modal,因此无法单击用户界面元素。"
),
Lesson(
title: "Angular入门",
level: "初级",
indicatorValue: 0.33,
price: 50,
content:
"首先花几分钟阅读本节中的信息。 启动您的应用程序,然后单击“设置”菜单。 在设置页面上,单击保存按钮。 您应该在页面中间看到一个圆形的进度指示器显示,并且由于启动了modal,因此无法单击用户界面元素。"
),
Lesson(
title: "webRTC实战",
level: "中级",
indicatorValue: 0.66,
price: 30,
content:
"首先花几分钟阅读本节中的信息。 启动您的应用程序,然后单击“设置”菜单。 在设置页面上,单击保存按钮。 您应该在页面中间看到一个圆形的进度指示器显示,并且由于启动了modal,因此无法单击用户界面元素。"
),
Lesson(
title: "golang并发设计",
level: "中级",
indicatorValue: 0.33,
price: 50,
content:
"首先花几分钟阅读本节中的信息。 启动您的应用程序,然后单击“设置”菜单。 在设置页面上,单击保存按钮。 您应该在页面中间看到一个圆形的进度指示器显示,并且由于启动了modal,因此无法单击用户界面元素。"
),
Lesson(
title: "设计模式",
level: "高级",
indicatorValue: 1.0,
price: 50,
content:
"首先花几分钟阅读本节中的信息。 启动您的应用程序,然后单击“设置”菜单。 在设置页面上,单击保存按钮。 您应该在页面中间看到一个圆形的进度指示器显示,并且由于启动了modal,因此无法单击用户界面元素。"
),
Lesson(
title: "分布式计算",
level: "高级",
indicatorValue: 1.0,
price: 50,
content:
"首先花几分钟阅读本节中的信息。 启动您的应用程序,然后单击“设置”菜单。 在设置页面上,单击保存按钮。 您应该在页面中间看到一个圆形的进度指示器显示,并且由于启动了modal,因此无法单击用户界面元素。"
),
Lesson(
title: "机器学习",
level: "高级",
indicatorValue: 1.0,
price: 50,
content:
"首先花几分钟阅读本节中的信息。 启动您的应用程序,然后单击“设置”菜单。 在设置页面上,单击保存按钮。 您应该在页面中间看到一个圆形的进度指示器显示,并且由于启动了modal,因此无法单击用户界面元素。"
),
];
}
接下来重构main.dart文件。首先增加一个List类型的变量。初始化课程变量指向getLessons()函数来加载所有的课程。
class ListPage extends StatelessWidget {
final String title;
List lessons;
ListPage({Key key, this.title}) : super(key: key);
@override
Widget build(BuildContext context) {
lessons = getLessons();
....
final makeBody = Container(
child: ListView.builder(
scrollDirection: Axis.vertical,
shrinkWrap: true,
itemCount: lessons.length,
itemBuilder: (BuildContext context, int index){
return makeCard(lessons[index]);
}
),
);
重构下面的final变量为class方法:makeCard变为makeCard(),makeListTile变为makeListTile(lesson)
Card makeCard(Lesson lesson) => Card(
elevation: 8.0,
margin: EdgeInsets.symmetric(horizontal: 10.0, vertical: 6.0),
child: Container(
decoration: BoxDecoration(color: Color.fromRGBO(64, 75, 96, .9)),
child: makeListTile(lesson),
),
);
makeListTile变量变为makeListTile(Lesson lesson)方法。对于更复杂的应用来说,这种自定义组件可以更好的增加重用性。使用课程对象取代原有硬核值,使用LinearProgressIndicator组件取代图片indicator,增加Padding包含文本组件,变更如下:
ListTile makeListTile(Lesson lesson) => ListTile(
contentPadding:
EdgeInsets.symmetric(horizontal: 20.0, vertical: 10.0),
leading: Container(
padding: EdgeInsets.only(right: 12.0),
decoration: BoxDecoration(
border: Border(
right: BorderSide(width: 1.0, color: Colors.white24))),
child: Icon(Icons.autorenew, color: Colors.white),
),
title: Text(
lesson.title,
style: TextStyle(color: Colors.white, fontWeight: FontWeight.bold),
),
subtitle: Row(
children: <Widget>[
Expanded(
flex: 1,
child: Container(
// tag: 'hero',
child: LinearProgressIndicator(
backgroundColor: Color.fromRGBO(209, 224, 224, 0.2),
value: lesson.indicatorValue,
valueColor: AlwaysStoppedAnimation(Colors.green)),
)),
Expanded(
flex: 4,
child: Padding(
padding: EdgeInsets.only(left: 10.0),
child: Text(lesson.level,
style: TextStyle(color: Colors.white))),
)
],
),
trailing:
Icon(Icons.keyboard_arrow_right, color: Colors.white, size: 30.0),
onTap: () {
Navigator.push(
context, MaterialPageRoute(builder: (context) => DetailPage()));
},
);
运行应用如下
image.png
导航
Flutter提供navigator导航组件。ListTile包含onTap()函数,增加以下代码至该函数中
onTap: () {
Navigator.push(context,
MaterialPageRoute(
builder: (context) => DetailPage(lesson: lesson)
)
);
}
代码执行从当前视图跳转到详情页面。
详情页
image.png创建detail_page.dart文件并创建无状态组件命名为DetailPage。
import 'package:beautiful_list/model/lesson.dart';
import 'package:flutter/material.dart';
class DetailPage extends StatelessWidget {
final Lesson lesson;
DetailPage({Key key, this.lesson}) : super(key: key);
@override
Widget build(BuildContext context) {
return Scaffold(
body: Column(
children: <Widget>[topContent, bottomContent],
),
);
}
}
接着创建头部内容(topContent)和底部内容(bottomContent)。头部内容为Stack组件,children包含背景图,container文本和一个固定位置(positioned组件)返回按键。
final topContent = Stack(
children: <Widget>[
Container(
padding: EdgeInsets.only(left: 10.0),
height: MediaQuery.of(context).size.height * 0.5,
decoration: BoxDecoration(
image: DecorationImage(
image: AssetImage("drive-steering-wheel.jpg"),
fit: BoxFit.cover,
),
)),
Container(
height: MediaQuery.of(context).size.height * 0.5,
padding: EdgeInsets.all(40.0),
width: MediaQuery.of(context).size.width,
decoration: BoxDecoration(color: Color.fromRGBO(58, 66, 86, .9)),
child: Center(
child: topContentText,
),
),
Positioned(
left: 8.0,
top: 60.0,
child: InkWell(
onTap: () {
Navigator.pop(context);
},
child: Icon(Icons.arrow_back, color: Colors.white),
),
)
],
);
topContentText为一个Column组件,如图
代码如下
final topContentText = Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
SizedBox(height: 80.0),
Icon(
Icons.directions_car,
color: Colors.white,
size: 30.0,
),
Container(
width: 90.0,
child: Divider(color: Colors.green),
),
SizedBox(height: 10.0),
Text(
lesson.title,
style: TextStyle(color: Colors.white, fontSize: 35.0),
),
SizedBox(height: 10.0),
Row(
mainAxisAlignment: MainAxisAlignment.start,
children: <Widget>[
Expanded(flex: 1, child: levelIndicator),
Expanded(
flex: 6,
child: Padding(
padding: EdgeInsets.only(left: 10.0),
child: Text(
lesson.level,
style: TextStyle(color: Colors.white),
))),
Expanded(flex: 2, child: coursePrice)
],
),
],
);
增加level indicator级别显示符和course prices课程价格。
final levelIndicator = Container(
child: Container(
child: LinearProgressIndicator(
backgroundColor: Color.fromRGBO(209, 224, 224, 0.2),
value: lesson.indicatorValue,
valueColor: AlwaysStoppedAnimation(Colors.green)),
),
);
final coursePrice = Container(
padding: const EdgeInsets.all(7.0),
alignment: Alignment.center,
decoration: BoxDecoration(
border: Border.all(color: Colors.white),
borderRadius: BorderRadius.circular(5.0)),
child: Text(
// "\$20",
"\$" + lesson.price.toString(),
style: TextStyle(color: Colors.white),
),
);
底部内容代码
final bottomContentText = Text(
lesson.content,
style: TextStyle(fontSize: 18.0),
);
final readButton = Container(
padding: EdgeInsets.symmetric(vertical: 16.0),
width: MediaQuery.of(context).size.width,
child: RaisedButton(
onPressed: () => {},
color: Color.fromRGBO(58, 66, 86, 1.0),
child:
Text("学习该课程", style: TextStyle(color: Colors.white)),
));
final bottomContent = Container(
// height: MediaQuery.of(context).size.height,
width: MediaQuery.of(context).size.width,
// color: Theme.of(context).primaryColor,
padding: EdgeInsets.all(30.0),
child: Center(
child: Column(
children: <Widget>[bottomContentText, readButton],
),
),
);
detail_page.dart完整代码
import 'package:beautiful_list/model/lesson.dart';
import 'package:flutter/material.dart';
class DetailPage extends StatelessWidget {
final Lesson lesson;
DetailPage({Key key, this.lesson}) : super(key: key);
@override
Widget build(BuildContext context) {
final levelIndicator = Container(
child: Container(
child: LinearProgressIndicator(
backgroundColor: Color.fromRGBO(209, 224, 224, 0.2),
value: lesson.indicatorValue,
valueColor: AlwaysStoppedAnimation(Colors.green)),
),
);
final coursePrice = Container(
padding: const EdgeInsets.all(7.0),
alignment: Alignment.center,
decoration: BoxDecoration(
border: Border.all(color: Colors.white),
borderRadius: BorderRadius.circular(5.0)),
child: Text(
// "\$20",
"\$" + lesson.price.toString(),
style: TextStyle(color: Colors.white),
),
);
final topContentText = Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
SizedBox(height: 80.0),
Icon(
Icons.directions_car,
color: Colors.white,
size: 30.0,
),
Container(
width: 90.0,
child: Divider(color: Colors.green),
),
SizedBox(height: 10.0),
Text(
lesson.title,
style: TextStyle(color: Colors.white, fontSize: 35.0),
),
SizedBox(height: 10.0),
Row(
mainAxisAlignment: MainAxisAlignment.start,
children: <Widget>[
Expanded(flex: 1, child: levelIndicator),
Expanded(
flex: 6,
child: Padding(
padding: EdgeInsets.only(left: 10.0),
child: Text(
lesson.level,
style: TextStyle(color: Colors.white),
))),
Expanded(flex: 2, child: coursePrice)
],
),
],
);
final topContent = Stack(
children: <Widget>[
Container(
padding: EdgeInsets.only(left: 10.0),
height: MediaQuery.of(context).size.height * 0.5,
decoration: BoxDecoration(
image: DecorationImage(
image: AssetImage("drive-steering-wheel.jpg"),
fit: BoxFit.cover,
),
)),
Container(
height: MediaQuery.of(context).size.height * 0.5,
padding: EdgeInsets.all(40.0),
width: MediaQuery.of(context).size.width,
decoration: BoxDecoration(color: Color.fromRGBO(58, 66, 86, .9)),
child: Center(
child: topContentText,
),
),
Positioned(
left: 8.0,
top: 60.0,
child: InkWell(
onTap: () {
Navigator.pop(context);
},
child: Icon(Icons.arrow_back, color: Colors.white),
),
)
],
);
final bottomContentText = Text(
lesson.content,
style: TextStyle(fontSize: 18.0),
);
final readButton = Container(
padding: EdgeInsets.symmetric(vertical: 16.0),
width: MediaQuery.of(context).size.width,
child: RaisedButton(
onPressed: () => {},
color: Color.fromRGBO(58, 66, 86, 1.0),
child:
Text("学习该课程", style: TextStyle(color: Colors.white)),
));
final bottomContent = Container(
// height: MediaQuery.of(context).size.height,
width: MediaQuery.of(context).size.width,
// color: Theme.of(context).primaryColor,
padding: EdgeInsets.all(30.0),
child: Center(
child: Column(
children: <Widget>[bottomContentText, readButton],
),
),
);
return Scaffold(
body: Column(
children: [topContent, bottomContent],
));
}
}
运行代码,结果如下:
image.png
网友评论