Flutter智慧城市App
前言
即将是我的Flutter开发流程。尽管这个开发过程较为复杂,并非真正意义上的作品。但这是仅限于我整理学习笔记的一篇文章。如果有任何错误或问题,请您告知。希望能在评论区指出您的问题或建议!感谢您的关注与指导!
要求
任务 1:引导页功能模块描述:
当App启动时,默认会跳转至引导页面,在进入最终引导页面时,默认会显示5个圆形标记以指示当前的位置信息。
在进入最终引导页面时:
(1)展示"网络参数配置"按钮。单击该按钮后将弹出服务器IP地址(如192.168.1.10)和端口(如8080)设置对话框,并实现服务器IP地址与端口的保存与修改功能。
(2)展示"访问主站"按钮。单击该按钮即可跳转至主页。
任务 2:主页面功能模块描述:
主页呈现搜索输入框界面,在用户输入具体内容后,请您单击右侧配备的"搜索"功能键即可实现快速导航至相关新闻资讯列表网页查看相关内容。
系统自动展示轮播图集锦页,在此版块下您可直接点击任意一张图片链接进行浏览访问。
智慧城市管理平台为方便您快速获取各类服务资源信息,在线提供多个特色领域服务入口选项选择:从左至右分两排排列(手机端每排最多可容纳5个服务入口),其中第二排末尾特别设置"更多服务"功能按钮供扩展选择,请您根据个人需求点击相应服务入口展开详细信息查看。
首页显著突出热门话题模块,在手机端版面上最多可同时展示2个热门话题栏目(Pad设备最多可同时查看4个),每个话题入口均设计为带半径20dp圆角矩形图标形式,在图标下方标注简要标题说明,请您通过左侧"热门话题"分类按钮切换到目标话题内容进行深入学习。
系统主界面设置新闻专栏分类浏览模式,在顶部设置若干标签页选项用于区分不同新闻类别类型,请您根据兴趣选择关注类别并在底部看到详细新闻列表;列表项包含图片链接、新闻标题缩略形式以及关键信息提示等基础要素。
底部配置功能分区指引标识栏,在左侧依次设置首页功能区、全部服务入口、智慧党建平台、新闻资讯窗口以及个人中心区域等5个功能标识项供用户明确区分定位当前处于哪个功能区,请确保在任何时候都能快速识别当前所处位置并正确切换相应功能区域。
任务 3:预约检车功能模块描述
智慧城市App主页面上设有各类服务入口,在线用户可登录'预约检车'图标或相关信息跳转至预约检车界面。该功能包含四个服务按钮:查看须知、立即预约、查看记录和管理车辆;点击上方'返回主页面'按钮即可跳回智慧城市App主页。
默认情况下切换至"预约须知"页面,并显示相关提示信息。
点击"车辆管理"按钮切换至车辆管理页面,并列出车牌号、车架号等信息栏。
点击"立即预约"按钮后会跳转至立即预约页面:
(1)展示用户已录入的车辆列表并提供单选按钮进行选择。
(2)出现时间选择界面供用户设定具体时间。
(3)出现检测地点选择界面供用户挑选检测地点。
(4)确认后会将预约信息提交系统并显示成功提示。
通过点击"我的安排"按钮访问该页面。页面上列出所有已成功安排的车辆信息订单,并包含车牌号码、预定日期以及检车地点等详细信息。
任务 4:个人中心功能模块描述
- 首先进入个人中心页面,个人中心页面显示用户头像、账户、个人信息页面入口、订单列表页面入口、修改密码页面入口、意见反馈页面入口,点击“退出”按钮可退出登录。
- 点击个人信息跳转至个人信息页面,标签栏显示本页面标题,点击返回图标可返回到上一页,点击修改可保存修改的信息,可修改内容为:头像、昵称、性别、联系电话,注:证件号只显示前两位与后四位数字其他使用 * 号代替。
- 点击修改密码可进入修改密码页面,标签栏显示本页面标题,点击返回图标可返回到上一页,输入原密码与新密码,点击“确定”按钮可保存修改的信息。
- 点击订单列表可跳转到订单页面,标签栏显示本页面标题,点击返回图标可返回到上一页,页面内容展示所有订单、订单分类数据信息,订单显示信息有:订单号、订单类型、订单生成日期。点击订单可跳转至对应功能模块生成订单的详情页面。
- 点击意见反馈可跳转至意见反馈页面,标签栏显示本页面标题,点击返回图标可返回到上一页,输入反馈的内容,字数限制在 150 字以内,点击提交可提交反馈的意见。
任务 5:找房子功能模块描述(2 分)
智慧城市建设官方客户端中的各类应用入口(可自行优化设计)中,请选择“找房子”功能图标或相关信息。即可跳转至“找房子”功能详情页。任务说明:主要包含首页及信息详情两大版块。通过点击导航栏中的“返回”按钮即可回到智慧城市建设主界面。
- 主页面,页面包括顶部导航栏目、宣传幻灯片、搜索、功能分类和房源展示板块。具有返回按钮,点击返回按钮可以返回智慧城市主页面。下方搜索在页面搜索框下面显示 4 大分类按钮,分别为:二
手、租房、楼盘、中介,并图文显示。
(1)页面顶部具有返回按钮,点击返回按钮返回智慧城市主页面。
(2)产品功能宣传幻灯片展示。
(3)搜索,根据房源名称模糊查询,结果列表显示在房源展示区。
(4)功能分类,搜索下面显示四大分类,分别为:二手、租房、楼盘、中介,并图文显示。点击四大分类按钮,房源展示区更新该分类相应的房源信息并在页面下方以列表的形式展示,列表项每一房源均显示图片、所在小区或商圈名称、房源面积以及价格、房源简介等内容。
(5)房源展示分类栏目下方,展示默认最新发布的房源列表信
息,列表页每一房源均显示房源图片、所在小区或商圈名称、房源面积以及价格、房源简介等内容。
2.信息详情页,点击房源列表中的某个房源,进入到房源详情页面。详情页面分别展示房源图片、房源名称、建筑面积、房源单价、房源类型、房源介绍等信息。底部展示主页和打电话按钮,点击“主页”,返回找房主主页,点击“打电话”可以直接系统拨号页面,联
系对应详情中的电话号码。
任务 6:新闻功能模块描述(2 分)
- 新闻页面包含:轮播图、新闻分类、新闻列表等内容。轮播图可跳转新闻详情页面;新闻分类展示各类新闻主题;新闻列表根据最新发布时间排序,列表显示新闻图片、新闻名称、观看人数、点赞数等。
- 在新闻列表页面点击新闻名称跳转到新闻详情页面,信息如下:
(1)详情页面顶部栏显示新闻名称,点击“返回”按钮,返回上级目录。
(2)新闻详情内容按照(图片+文字)的形式进行展示,详情页面具有评论和查看评论列表功能,评论列表显示评论条数以及评论内容,用户可以对该新闻进行评论。
(3)详情页面还包括新闻推荐,以列表形式展示 1-3 篇推荐新闻,显示新闻名称、观看数、图片等信息。
材料
这次项目就叫“SmartCity”
图片如下 (iconFont下载的图片,仅供学习使用!) :





任务一 - 引导页实现
制作引导页必须使用PageView组件进行开发。建议作为参考材料提供给开发者。实际上非常容易。
在跳转页实现过程中,在移动应用开发中通常会依赖于路由表配置以确保页面之间的快速切换和平滑过渡。为了支持这一需求,在本项目中我们特别实现了相应的功能逻辑,并将其完整地集成到了项目架构之中
class MyApp extends StatelessWidget {
MyApp({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'SmartCity',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: judge(),
// 路由表
routes: {
"/guidepage":(context) => guidePage(),
"/indexpage":(context)=>indexPage(),
},
);
}
引导页
具体而言,在此页面中
具体而言,在此页面中
具体而言,在此页面中
具体而言,在此页面中
class User {
// 图片路径
final List list = [
"assets/images/1.png",
"assets/images/2.png",
"assets/images/3.png",
"assets/images/4.png",
"assets/images/5.png",
"assets/images/5.png",
"assets/images/6.png",
"assets/images/7.png",
"assets/images/8.png",
"assets/images/9.png",
];
// 图片说明
final List info = [
"预约检车",
"找房子",
"设备设置",
"云端管理",
"政府工作",
"错误排查",
"随赠品",
"火箭发射",
"协同工作",
"更多服务"
];
}
当它被需要时,在直接生成一个对象后就可以立即开始使用了!非常便捷。
// ignore_for_file: prefer_const_constructors
import 'package:flutter/material.dart';
import 'package:smartcity/Constant/constant.dart';
import 'package:smartcity/Page/indexPage/indexpage.dart';
class guidePage extends StatefulWidget {
guidePage({
Key? key,
}) : super(key: key);
@override
_guidePageState createState() => _guidePageState();
}
class _guidePageState extends State<guidePage> {
// 当前位置
int index = 0;
// ip地址
String ipAddress = "192.168.1.10";
// 端口号
String portNumber = "8080";
// 创建我们的数据的对象
User user = User();
@override
Widget build(BuildContext context) {
Size size = MediaQuery.of(context).size;
return Scaffold(
// 安全区,设置不被上面的那玩意儿挡住
body: SafeArea(
child: SingleChildScrollView(
scrollDirection: Axis.vertical,
child: Column(
children: [
Container(
color: Color(0x55ededed),
alignment: Alignment.center,
width: size.width,
height: size.height * 0.7,
child: PageView(
scrollDirection: Axis.horizontal,
onPageChanged: (value) {
setState(() {
index = value;
});
},
// 用列表的方式快速创建5张 引导页
children: List.generate(5, (index) {
return PicAndText(user.list[index], user.info[index]);
}),
// PicAndText(list[3], info[3]),
),
),
// 小圆点
dot()
],
),
),
),
);
}
// 引导页面设置
Container PicAndText(String image, String messges) {
return Container(
padding: EdgeInsets.all(30),
color: Colors.grey.withAlpha(50),
child: Column(
children: [
// 使用百分比进行布局
FractionallySizedBox(
widthFactor: 1,
// heightFactor: 0.5,
child: Container(
padding: EdgeInsets.all(30),
height: 500,
// color: Colors.grey,
child: Column(
children: [
// 放置图片
Container(
width: 400,
height: 300,
margin: EdgeInsets.only(bottom: 20),
decoration: BoxDecoration(
image: DecorationImage(image: AssetImage(image)),
color: Colors.cyan,
),
),
// 放置文本
Container(
width: 400,
height: 100,
// 当页面在第五页的时候,显示网络设置按钮
child: index != 4
? Text(
messges,
style: TextStyle(fontSize: 18),
)
: Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [
// 点击按钮弹出对话框
ElevatedButton(
onPressed: () {
// 对话框
showDialog(
context: context,
builder: (context) {
return AlertDialog(
actionsAlignment:
MainAxisAlignment.center,
title: Text('网络配置'),
// 内容部分用容器->Column放置 IP地址和 端口号对话框
content: Container(
height: 160,
child: Column(
crossAxisAlignment:
CrossAxisAlignment.start,
children: [
Text("当前ip地址:${ipAddress}"),
TextField(
decoration: InputDecoration(
hintText: "请输入新的IP地址"),
onChanged: (value) {
setState(() {
ipAddress = value;
print(value);
});
},
),
Spacer(),
Text("当前端口号:${portNumber}"),
TextField(
decoration: InputDecoration(
hintText: "请输入新的端口号"),
onChanged: (value) {
setState(() {
portNumber = value;
print(value);
});
},
),
],
),
),
// 放置确认按钮,用于退出这个对话框
actions: <Widget>[
ElevatedButton(
child: Text('确认'),
onPressed: () {
Navigator.of(context)
.pop("ok");
},
),
],
);
});
},
child: Text("显示网络设置")),
// 进入主页的按钮
ElevatedButton(
onPressed: () {
setState(() {
Navigator.of(context)
.pushNamed("/indexpage");
});
},
child: Text("进入主页")),
],
),
decoration: BoxDecoration(
borderRadius: BorderRadius.all(Radius.circular(20)),
color: Colors.white,
),
alignment: Alignment.center,
),
],
),
),
)
],
),
);
}
// 小圆点
Padding dot() {
return Padding(
padding: EdgeInsets.only(top: 20),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Container(
width: 10,
height: 10,
decoration: BoxDecoration(
borderRadius: BorderRadius.all(Radius.circular(10)),
color: index == 0 ? Colors.black : Colors.grey),
),
Container(
width: 10,
height: 10,
decoration: BoxDecoration(
borderRadius: BorderRadius.all(Radius.circular(10)),
color: index == 1 ? Colors.black : Colors.grey),
),
Container(
width: 10,
height: 10,
decoration: BoxDecoration(
borderRadius: BorderRadius.all(Radius.circular(10)),
color: index == 2 ? Colors.black : Colors.grey),
),
Container(
width: 10,
height: 10,
decoration: BoxDecoration(
borderRadius: BorderRadius.all(Radius.circular(10)),
color: index == 3 ? Colors.black : Colors.grey),
),
Container(
width: 10,
height: 10,
decoration: BoxDecoration(
borderRadius: BorderRadius.all(Radius.circular(10)),
color: index == 4 ? Colors.black : Colors.grey),
),
],
),
);
}
}
效果图如下:

任务二 - 主页实现
输入框
通过TextField组件进行实现, 通过点击该软件的搜索按钮直接跳转至对应页面, 为了满足需求, 在应用中需配置相应的路由设置, 因此, 在我们的应用开发中应将 routes 配置添加进去.
routes: {
"/guidepage":(context) => guidePage(),
"/indexpage":(context)=>indexPage(),
"/searchpage":(context)=>searchPage(),
},
下面将输入框的实现
Container(
alignment: Alignment.center,
margin: EdgeInsets.all(20),
padding: EdgeInsets.all(18),
width: size.width * 0.9,
height: 60,
decoration: BoxDecoration(
border: Border.all(color: Color(0x55000000), width: 2),
borderRadius: BorderRadius.all(Radius.circular(20))),
child: TextField(
decoration: InputDecoration(
suffixIcon: Icon(Icons.search),
// 将输入框内的下滑线变为透明的
focusedBorder: OutlineInputBorder(
borderSide: BorderSide(color: Color(0x00FF0000))),
// 将输入框内的下滑线变为透明的
enabledBorder: OutlineInputBorder(
borderSide: BorderSide(color: Color(0x00FF0000)))),
// 设置软件盘的搜索按钮
textInputAction: TextInputAction.search,
// 提交回调方法,点击软键盘的搜索按钮时,跳转到相应页面
onSubmitted: (v) {
print(v);
// 路由跳转
Navigator.of(context).pushNamed("/searchpage");
},
),
),
然后searchPage页面代码如下:
import 'package:flutter/material.dart';
class searchPage extends StatefulWidget {
const searchPage({Key? key}) : super(key: key);
@override
_searchPageState createState() => _searchPageState();
}
class _searchPageState extends State<searchPage> {
@override
Widget build(BuildContext context) {
return Scaffold(appBar: AppBar(title: Text("搜索"),),
body: Container(),);
}
}
效果如下:

轮播图
我在制作轮播时采用了PageView组件来实现功能。不过,在没有第三方插件flutter_swiper的情况下,该组件表现出色,并且非常有效!
使用PageVige
在制作轮播图时可将图片路径放置于列表中对当前索引值与列表长度进行模运算即可实现轮播效果具体操作如下例如当前索引值为1而列表长度为5则模运算结果为1若当前索引值为2同样得到模运算结果为2以此类推当索引值等于列表长度时将得到模运算结果为0此时指针会指向最后一个元素而下一个循环时指针又会重新指向第一个元素这样的循环操作就能实现轮播效果
Container(
width: size.width,
height: size.height * 0.25,
child: PageView.builder(
itemBuilder: (context, index) {
return Image.asset(
list[index % list.length], // 对当前页面下标进行取余,这样就可以实现无限轮播,
fit: BoxFit.cover,
);
},
onPageChanged: (index) {
setState(() {
_curIndex = index; // 将当前页面的下标取出,可以搭配进度指示器使用
});
},
),
),
效果如下:

Please ignore the 有道翻译 icon, as the author struggles with English proficiency and may require translation for certain words to understand their meanings.
自动轮播
该系统已实现了自动轮播功能。但需要注意的是,在开始播放之前必须手动触发播放启动。这需要用户在播放开始前主动点击启动按钮才能开始播放。您明白了吗?
Container(
width: size.width,
height: size.height * 0.25,
// color: Colors.redAccent,
child: PageView.builder(
itemBuilder: (context, index) {
return GestureDetector(
onTap: () => Navigator.of(context).pushNamed("/infopage"),
child: Image.asset(
list[_curIndex % list.length], // 对当前页面下标进行取余,这样就可以实现无限轮播,
fit: BoxFit.cover,
),
);
},
onPageChanged: (index) {
setState(() {
_curIndex = index; // 将当前页面的下标取出
// 计时器,让播图动起来
Timer _timer =
Timer.periodic(new Duration(seconds: 3), (timer) {
setState(() {
_curIndex++;
print(_curIndex);
});
});
if (_curIndex >= 5) {
setState(() {
_timer.cancel();
print("计数器已停止!");
});
}
});
},
),
)
效果如下:

服务入口
在这里使用的是\texttt{Table}工具来实现即表格布局. 因为后面的任务中需要单独设置一个\texttt{预约车检}入口因此通过判断是否为第一个服务入口
import 'package:flutter/material.dart';
import 'package:smartcity/Constant/constant.dart';
import 'application_service_entrance.dart';
import 'appointmentpage.dart';
// 这里面放置的是服务页面的入口
class applicationSeverce extends StatelessWidget {
applicationSeverce({Key? key}) : super(key: key);
// 创建对象
User user = User();
@override
Widget build(BuildContext context) {
Size size = MediaQuery.of(context).size;
return Container(
margin: EdgeInsets.symmetric(vertical: 20),
width: size.width * 0.8,
height: 130,
child: SingleChildScrollView(
// 使用表格布局
child: Table(
children: [
// 表格的每一行
TableRow(
// 创建5个单元格
children: List.generate(5, (index) {
return TableCell(
// 封装了图片和文字描述信息
child: Column(
children: [
GestureDetector(
onTap: () {
// 通过判断是否是第一个页面,如果他的下标不为零,那么就是其他服务页面
if (index != 0) {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) =>
applicationServiceEntrance(
// 为对应页面传递相应的页面名称,用下标值取名
text: user.info[index],
appFlag: index == 0 ? true : false,
)));
} else {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => Appointment()));
}
},
child: ClipOval(
child: Container(
width: 40,
height: 40,
decoration: BoxDecoration(
image: DecorationImage(
image: AssetImage(user.list[index]),
fit: BoxFit.cover)),
),
),
),
Text(user.info[index])
],
),
);
})),
TableRow(
// 创建5个单元格
children: List.generate(5, (index) {
return TableCell(
// 封装了图片和文字描述信息
child: GestureDetector(
onTap: () => Navigator.push(
context,
MaterialPageRoute(
builder: (context) => applicationServiceEntrance(
// 为对应页面传递相应的页面名称,用下标值取名
text: user.info[index + 5],
// 设置是否显示服务页面的底部导航栏
appFlag: false,
))),
child: index != 4
? Column(
children: [
ClipOval(
child: Container(
width: 40,
height: 40,
decoration: BoxDecoration(
image: DecorationImage(
image: AssetImage(user.list[index + 5]),
fit: BoxFit.cover)),
),
),
Text(user.info[index + 5])
],
)
: Container(
margin: EdgeInsets.only(top: 20),
child: Text(user.info[index + 5])),
),
);
})),
],
),
),
);
}
}
为此需要为除了第一个服务页面以外的其余9个服务分别设置共同页面
// ignore_for_file: prefer_const_literals_to_create_immutables, prefer_const_constructors
import 'package:flutter/material.dart';
import 'package:smartcity/Page/page.dart';
// 这里面放置的是除第一个服务页面之外的服务页面
class applicationServiceEntrance extends StatelessWidget {
applicationServiceEntrance(
{Key? key, required this.text, required this.appFlag})
: super(key: key);
final String text;
final bool appFlag;
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
leading: appFlag == false
? null
: TextButton(
onPressed: () => Navigator.push(
context, MaterialPageRoute(builder: (context) => page())),
child: Text(
"返回",
style: TextStyle(color: Colors.white),
),
),
title: Text(text),
centerTitle: true,
),
body: Container(),
);
}
}
效果如下:

热门主题模块
通过点击热门主题可以更改当前主题的颜色。当屏幕宽度超过600像素时可以显示4个布局项,并在此处通过LayoutBuilder获取屏幕尺寸并进行相应的判断。
// 显示热门主题模块
Container(
width: size.width * 0.85,
height: size.height * 0.2,
padding: EdgeInsets.symmetric(horizontal: 30),
margin: EdgeInsets.all(15),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(10),
color: Color(0xffededed)),
child: GestureDetector(
onTap: () {
Navigator.push(context,
MaterialPageRoute(builder: (context) => ThemePage()));
},
child: Column(
children: [
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
"热门主题",
style: TextStyle(
fontSize: 18,
color: Colors.grey,
fontWeight: FontWeight.w600),
)
],
),
LayoutBuilder(builder: (context, constrains) {
if (constrains.maxWidth < 600) {
return Container(
margin: EdgeInsets.only(top: 20),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [
colorBlock(Colors.primaries[1], "优雅红"),
colorBlock(Colors.primaries[2], "葡萄紫"),
],
),
);
} else {
return Container(
margin: EdgeInsets.only(top: 20),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [
colorBlock(Colors.primaries[1], "优雅红"),
colorBlock(Colors.primaries[2], "葡萄紫"),
colorBlock(Colors.primaries[3], "靛蓝紫"),
colorBlock(Colors.primaries[4], "笔墨蓝"),
],
),
);
}
}),
],
),
),
)
我们有必要设置一个文件用于存储热门主题页面。对于颜色的选择还是有些困扰吗?我将基于Flutter框架中的Colors.primaries数组索引位置提供一套标准的色彩方案。这个包含一套色彩方案的颜色列表中一共安置了18个精心挑选的颜色样本,请问您是否满意?完整的源码示例已附在下方
为了切换该应用的状态管理逻辑,请引入Flutter提供的State Management组件Provider。
class ThemePage extends StatefulWidget {
const ThemePage({Key? key}) : super(key: key);
@override
_ThemePageState createState() => _ThemePageState();
}
class _ThemePageState extends State<ThemePage> {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("主题"),
),
body: Padding(
padding: const EdgeInsets.all(8.0),
child: Wrap(children: [
...Colors.primaries.map((color) => Material(
color: color,
child: InkWell(
child: Container(
width: 50,
height: 50,
),
onTap: () {
// 通过 context 读取到 ColorThemeProvider 这个类下面的 changeColor 方法
context
.read<ColorThemeProvider>()
.changeColor(color: color);
},
),
))
]),
),
);
}
}
随后,在实现相关功能需求的基础上
class ColorThemeProvider extends ChangeNotifier {
late MaterialColor _color;
// get 方法
MaterialColor get color => _color;
// 构造函数
ColorThemeProvider() {
_color = Colors.yellow;
}
// 改变颜色,可选命名参数默认为 yellow
void changeColor({MaterialColor color = Colors.yellow}) {
_color = color;
// 调用 通知监听器 进行改变
notifyListeners();
}
}
之后我们去改变MaterialApp的primarySwatch。
class MyApp extends StatefulWidget {
MyApp({Key? key}) : super(key: key);
@override
State<MyApp> createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
@override
Widget build(BuildContext context) {
// 变更通知提供者
return ChangeNotifierProvider<ColorThemeProvider>(
create: (context) => ColorThemeProvider(),
child: Consumer<ColorThemeProvider>(
builder: (context, colorThemeProvider, child) => MaterialApp(
title: "SmartCity",
theme: ThemeData(
// 使用改变之后的颜色,默认是 yellow
primarySwatch: colorThemeProvider.color,
),
debugShowCheckedModeBanner: false,
home: Scaffold(
body: GuidePage(),
// body: Test(),
),
),
));
}
}
样式如下:

新闻专栏
编辑新闻稿时必须依赖数据来源。由于不愿寻找最新鲜的图片,我们不得不继续使用之前的老照片,但用户的评论内容则需要我们进行手动编辑,手头上的素材就靠你来搞定。看下面我整理的数据吧,简洁明了。
import 'package:flutter/material.dart';
class User {
// 图片路径
final List list = [
"assets/images/1.png",
"assets/images/2.png",
"assets/images/3.png",
"assets/images/4.png",
"assets/images/5.png",
"assets/images/5.png",
"assets/images/6.png",
"assets/images/7.png",
"assets/images/8.png",
"assets/images/9.png",
];
// 图片说明
final List info = [
"预约检车",
"找房子",
"设备设置",
"云端管理",
"政府工作",
"错误排查",
"随赠品",
"火箭发射",
"协同工作",
"更多服务"
];
// news paper
List newsPaperMessage = [
"预约检修,非常安逸,来了的人都说安逸得很,预约检修,非常安逸,来了的人都说安逸得很,很多人都来",
"找房子,非常安逸,来了的人都说安逸得很,预约检修,非常安逸,来了的人都说安逸得很,很多人都来",
"预约检修,非常安逸,来了的人都说安逸得很,预约检修,非常安逸,来了的人都说安逸得很,很多人都来",
"预约检修,非常安逸,来了的人都说安逸得很,预约检修,非常安逸,来了的人都说安逸得很,很多人都来",
"预约检修,非常安逸,来了的人都说安逸得很,预约检修,非常安逸,来了的人都说安逸得很,很多人都来",
"预约检修,非常安逸,来了的人都说安逸得很,预约检修,非常安逸,来了的人都说安逸得很,很多人都来",
"预约检修,非常安逸,来了的人都说安逸得很,预约检修,非常安逸,来了的人都说安逸得很,很多人都来",
"预约检修,非常安逸,来了的人都说安逸得很,预约检修,非常安逸,来了的人都说安逸得很,很多人都来",
"预约检修,非常安逸,来了的人都说安逸得很,预约检修,非常安逸,来了的人都说安逸得很,很多人都来",
"预约检修,非常安逸,来了的人都说安逸得很,预约检修,非常安逸,来了的人都说安逸得很,很多人都来",
];
// 新闻评论
List newsPaperPerson = [
"163","663","526","26","566","656","546","496","652","659"
];
// 新闻发布时间,切记时间别当真哈
List newsPaperTime = [
"2023/05/26",
"2026/07/14",
"2015/08/13",
"2056/04/24",
"2012/12/24",
"2013/11/29",
"2055/07/24",
"2046/06/16",
"2046/05/13",
"2013/01/26",
];
}
下面就是我们的UI代码:
// 显示新闻专栏
Container(
decoration: BoxDecoration(
borderRadius: BorderRadius.all(Radius.circular(18)),
color: Color(0XFFEDEDED),
),
margin: EdgeInsets.symmetric(vertical: 20),
padding: EdgeInsets.all(10),
width: size.width * 0.85,
// height: size.height * 0.18,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
"新闻专栏",
style: TextStyle(
fontSize: 19,
fontWeight: FontWeight.bold,
color: Colors.black54),
),
Column(children: List.generate(10, (index) => message(size, index)),)
],
),
),
],
),
),)
,
);
}
// 封装起来
Widget message(Size size,int index) {
return Container(
padding: EdgeInsets.all(5),
margin: EdgeInsets.only(top: 15),
width: size.width * 0.8,
height: size.height * 0.3,
decoration: BoxDecoration(
border: Border.all(color: Colors.white),
borderRadius: BorderRadius.all(Radius.circular(20))),
child: ListView(
children: [
Container(
height: 150,
child: Image.asset(user.list[index]),
),
SizedBox(
height: 0.01,
),
// 新闻标题
Text(
user.info[index],
style: TextStyle(fontSize: 17),
),
SizedBox(
height: size.height * 0.01,
width: 10,
),
// 新闻内容
Text(
user.newsPaperMessage[index],
overflow: TextOverflow.ellipsis,
),
SizedBox(
height: size.height * 0.01,
width: 10,
),
// 评论人数和发布日期
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text("${user.newsPaperPerson[index]}人评论"),
Text("${user.newsPaperTime[index]}")
],
)
],
),
);
}
样式如下:

底部导航栏
在完成底部导航栏制作后才开始撰写这篇博客,在接下来的时间里我将向大家详细介绍它的制作过程。由于最初编写这个App时忽视了脚手架的位置设置问题 现在不得不采取一些巧妙的方法来进行拼接 这只是增加了少量逻辑代码 并不会影响到我们的操作流程
首先,我们需要创建一个文件,里面放置我们的各个页面,如下:
// 放置各个页面
final List<Widget> pages = [
indexPage(),
All_Severce(),
SmartPartyBuilding(),
NewsPaperPage(),
PersonPage()
];
全部服务
全部服务

哦,我将主页也划上了圈,大家知道这并不影响我们的代码,对吧。
在此基础上
class _pageState extends State<page> {
// bottomCurIndex游标位置
int _bottomCurIndex = 0;
// 页面列表
final List<Widget> pages = [
indexPage(),
All_Severce(),
SmartPartyBuilding(),
NewsPaperPage(),
PersonPage()
];
@override
Widget build(BuildContext context) {
return Scaffold(
// 根据游标进行显示所需的页面
body: pages[_bottomCurIndex],
// 底部导航栏
bottomNavigationBar: BottomNavigationBar(
// 这个等会我放两张图片你就懂了
type: BottomNavigationBarType.fixed,
// 回调方法
onTap: (v) {
setState(() {
// 回调方法,将回调的结果给 _bottomCurIndex 进行储存
_bottomCurIndex = v;
});
},
// 放置多个 BottomNavigationBarItem()
items: const [
BottomNavigationBarItem(
icon: Icon(Icons.home),
label: "首页",
backgroundColor: Colors.teal),
BottomNavigationBarItem(
icon: Icon(Icons.admin_panel_settings),
label: "全部服务",
backgroundColor: Colors.teal),
BottomNavigationBarItem(
icon: Icon(Icons.assignment),
label: "智慧党建",
backgroundColor: Colors.teal),
BottomNavigationBarItem(
icon: Icon(Icons.bookmark_outline),
label: "新闻",
backgroundColor: Colors.teal),
BottomNavigationBarItem(
icon: Icon(Icons.account_box),
label: "个人中心",
backgroundColor: Colors.teal),
],
// 当前游标位置
currentIndex: _bottomCurIndex,
// showSelectedLabels: true,
// 显示未选中的标签
showUnselectedLabels: true,
),
);
}
}
样式如下:

到这里,我们就基本上将1、2题做完了。
任务三 - 预约检车功能模块描述
现在开始我们的任务三之旅,预约检车功能模块描述。
预约车检
点击主页中的预约车检图标入口,并进入预约车检界面。为了优化流程服务入口设置,请创建一个页面用于存放预约车检的相关信息。
// 服务入口
Container(
margin: EdgeInsets.all(10),
decoration: BoxDecoration(
color: Color(0xffededed),
borderRadius: BorderRadius.all(Radius.circular(10))),
alignment: Alignment.center,
width: size.width,
height: 130,
// color: Colors.orange,
padding: EdgeInsets.all(10),
child: Table(children: [
TableRow(
children: List.generate(
5,
(index) => TableCell(
child: GestureDetector(
onTap: () {
if (index == 0) {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => ServicePage()));
} else if (index == 1) {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => IndexSearchHouse()));
} else {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => Scaffold(
appBar: AppBar(
title: Text(user.imageName[index]),
),
)));
}
},
child: Column(
children: [
ClipOval(
child: Container(
width: 40,
height: 40,
decoration: BoxDecoration(
image: DecorationImage(
image: AssetImage(user.imageList[index]))),
),
),
Text(user.imageName[index])
],
),
),
),
),
),
TableRow(
children: List.generate(
5,
(index) => TableCell(
child: GestureDetector(
onTap: () {
if (index != 4) {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => Scaffold(
appBar: AppBar(
title: Text(
user.imageName[index + 5]),
),
)));
} else {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => Scaffold(
appBar: AppBar(
title: Text("更多服务"),
),
)));
}
},
child: index != 4
? Column(
children: [
Container(
alignment: Alignment.center,
width: 40,
height: 40,
decoration: BoxDecoration(
image: DecorationImage(
image: AssetImage(
user.imageList[index + 5]))),
),
Text(user.imageName[index + 5])
],
)
: Container(
child: Text("更多服务"),
margin: EdgeInsets.only(top: 20),
),
))))
]),
)
生成一个关于"预约车检"的相关文档
// 预约车检主页
// ignore_for_file: prefer_const_literals_to_create_immutables, prefer_const_constructors
import 'package:flutter/material.dart';
import 'package:smartcity/Constants/constants.dart';
import 'package:smartcity/Page/ServicePage/car_managepage.dart';
import 'index_appointmentpage.dart';
import 'my_appointmentpage.dart';
import 'promptly_appointmentpage.dart';
class ServicePage extends StatefulWidget {
const ServicePage({Key? key}) : super(key: key);
@override
_ServicePageState createState() => _ServicePageState();
}
int curBtb = 0;
class _ServicePageState extends State<ServicePage> {
// 创建常量对象
final User _user = User();
// 当加载该页面的时候就调用这个方法
@override
void initState() {
_user.getCarData();
}
@override
Widget build(BuildContext context) {
Size size = MediaQuery.of(context).size;
// 各个页面
List<Widget> _btnBody = [
// 主页
index_appointmentPage(),
// 立即预约页面
promptly_appointmentPage(),
// 我的预约页面
my_appointmentPage(),
// 车辆管理页面
car_managePage()
];
return Scaffold(
appBar: AppBar(
title: Text(_user.appointment_btbName[curBtb]),
centerTitle: true,
leading: TextButton(
onPressed: () {
Navigator.pop(context);
},
child: Text(
"返回",
style: TextStyle(color: Colors.white),
),
),
),
body: _btnBody[curBtb],
bottomNavigationBar: BottomNavigationBar(
onTap: (v) {
setState(() {
curBtb = v;
});
},
type: BottomNavigationBarType.fixed,
currentIndex: curBtb,
items: [
BottomNavigationBarItem(
icon: Icon(Icons.access_time_sharp), label: "预约车检"),
BottomNavigationBarItem(icon: Icon(Icons.add), label: "立即预约"),
BottomNavigationBarItem(
icon: Icon(Icons.add_ic_call_outlined), label: "我的预约"),
BottomNavigationBarItem(
icon: Icon(Icons.assignment_rounded), label: "车辆管理"),
],
),
);
}
}
样式如下:

车辆管理
import 'package:flutter/material.dart';
import 'package:smartcity/Constants/constants.dart';
class car_managePage extends StatefulWidget {
const car_managePage({Key? key}) : super(key: key);
@override
_car_managePageState createState() => _car_managePageState();
}
class _car_managePageState extends State<car_managePage> {
String carNum1 = "";
String carNum2 = "";
String carNum3 = "";
String carNum4 = "";
String carNum5 = "";
String carNum6 = "";
//
final golkey = GlobalKey<FormState>();
// 创建常量对象
final User _user = User();
@override
Widget build(BuildContext context) {
Size size = MediaQuery.of(context).size;
return SingleChildScrollView(
child: SizedBox(
width: size.width,
child: Form(
key: golkey,
child: Column(
children: [
Container(
padding: EdgeInsets.all(15),
margin: EdgeInsets.only(top: 20),
width: size.width * 0.85,
height: size.height * 0.5,
color: Colors.amber,
child: ListView(
children: [
Text("车牌号"),
TextFormField(
onSaved: (v) {
// print(v);
setState(() {
carNum1 = v!;
});
},
),
Text("车架号"),
TextFormField(
onSaved: (v) {
setState(() {
carNum2 = v!;
});
},
),
Text("车辆类型"),
TextFormField(
onSaved: (v) {
setState(() {
carNum3 = v!;
});
},
),
Text("车辆类型"),
TextFormField(
onSaved: (v) {
setState(() {
carNum4 = v!;
});
},
),
Text("公里数"),
TextFormField(
onSaved: (v) {
setState(() {
carNum5 = v!;
});
},
),
Text("手机号"),
TextFormField(
onSaved: (v) {
setState(() {
carNum6 = v!;
});
},
),
],
),
),
ElevatedButton(
onPressed: () {
golkey.currentState!.save();
_user.createFile(carNum1 +
"," +
carNum2 +
"," +
carNum3 +
"," +
carNum4 +
"," +
carNum5 +
"," +
carNum6 +
"\n");
},
child: Text("添加"),
)
],
),
),
),
);
}
}
样式如下:

立即预约
class my_appointmentPage extends StatefulWidget {
const my_appointmentPage({Key? key}) : super(key: key);
@override
_my_appointmentPageState createState() => _my_appointmentPageState();
}
late int num;
// 取出值
getData()async{
SharedPreferences prefs =await SharedPreferences.getInstance();
num = prefs.getInt("CarInfo") ?? 0;
}
class _my_appointmentPageState extends State<my_appointmentPage> {
@override
Widget build(BuildContext context) {
Size size = MediaQuery.of(context).size;
return Container(
alignment: Alignment.center,
child: Column(
children: [
Container(
width: size.width * 0.85,
height: size.height * 0.6,
color: Colors.amber,
child: ListView(
children: [
SizedBox(
width: double.infinity,
// height: 100,
child: Card(
child: Container(
margin: const EdgeInsets.all(10),
child: Column(
children: [
// Text("车牌号:" + primary_data[CarInfo][0]),
// Text("车架号:" + primary_data[CarInfo][1]),
// Text("车辆类型:" + primary_data[CarInfo][2]),
// Text("手机号:" + primary_data[CarInfo][3]),
// Text("公里数:" + primary_data[CarInfo][4]),
],
),
)),
)
],
),
)
],
),
);
}
}
