好客租房实现效果:
项目完整地址(已开源)
https://github.com/huanggengzhong/GoodHouse
中间经历过96次commit记录,根据commit可以回退到各个阶段,以便查看那时的代码.
练习 dart 语言的插件
下载安装dart sdk(网址:http://www.cndartlang.com/920.html),下载vscode插件(code runner),dart 文件右键运行即可.
开始项目创建
项目【菜单】— 【查看】—【命令面板】— 【Flutter:New Project】
测试页面代码:lib/main.dart
import 'package:flutter/material.dart';
void main ()=>runApp(MyApp());
class MyApp extends StatelessWidget{
@override
Widget build(BuildContext context){
return MaterialApp(
home:Scaffold(
appBar: AppBar(
title:const Text("Home")
),
)
);
}
}
运行命令,在终端按 r 会自动刷新模拟器界面.
flutter run
编写一个简单页面-实现
效果:

步骤:
- 添加 PageContent 组件(安装 Awesome Flutter Snippets
插件有快捷键)- 新建文件 /widgets/page_content.dart
- 添加 material 依赖;//importM
- 编写无状态组件//StatelessW
- 添加 name 参数 //起类名 PageContent ,声明 name:final String name;点击快速修复生成构造函数
- 使用 Scaffold //返回一个 Scaffold
- 添加 HomePage 页面
- 新建文件 /pages/home/index.dart
- 添加 material, page_content 依赖//import ‘package:first_flutter_app/widgets/page_content.dart’;
- 编写无状态组件
- 使用 PageContent
- 添加 Application 应用根组件
- 新建文件 /application.dart
- 添加依赖和 HomePage 界面引入 //可以点击组件后用 ctrl+单击自动引入
- 使用 MaterialApp //在 main 里导入 Application
- 测试
路由配置
- 安装(如果安装不了,可以参考我的另一篇博客方法https://blog.csdn.net/xiaodi520520/article/details/99672182)
dependencies:
// 下面有空格,安装好后再通过.lock文件吧版本号写回去
fluro: any
- 添加 /pages/login.dart
- 参考 /pages/home/index.dart 完善登陆页。
- 在 application 里把入口文件改成 loginPage
配置步骤
步骤:
- 创建 routes.dart 文件 并编写 Routes 类的基本结构
- 定义路由名称
static String home="/";
static String login="/login";
- 定义路由处理函数
//fluro官网有介绍
static Handler _homeHandler = Handler(handlerFunc: (BuildContext context, Map<String, dynamic> params) {
return HomePage();
static Handler _loginHandler = Handler(handlerFunc: (BuildContext context, Map<String, dynamic> params) {
return LoginPage();
- 编写函数 configureRoutes 关联路由名称和处理函数
static void configureRoutes(Router router) {
router.define(home, handler: _homeHandler);
router.define(login,handler:_loginHandler);
- 在 Application 中引入和调用路由
import 'package:goodhouse/routes.dart';
Router router=Router();
Routes.configureRoutes(router);
- 测试路由
在 page_content 文件中添加按钮查看效果.
FlatButton(child:Text(Routes.home),onPressed:(){
Navigator.pushNamed(context,Routes.home);
}),
FlatButton(child:Text(Routes.login),onPressed:(){
Navigator.pushNamed(context,Routes.login);
}),
优化路由(参数传递)
带参数页面处理
- 在 /pages 目录添加 room_detail/index.dart 文件
- 实现 RoomDetailPage
- 在 /routes.dart 添加 _roomDetailPage
- 在 /routes.dart 的 configureRoutes 中添加 RoomDetailPage;
- 修改 PageContent 测试
登录页面
scafford
- appBar
- title— Text
- body
- 用户名— TextField
- 密码— TextField
- 登陆按钮— RaisedButton
- 注册链接— Row[Text,FlatButton]
添加密码显示与隐藏
- 将无状态组件改成有状态组件— 快速方法组件名右键重构
- 添加可点击的图标— IconButton
TextField(
decoration: InputDecoration(
labelText:"密码",
hintText:"请输入密码",
suffixIcon:IconButton(icon:Icon(
showPassword?Icons.visibility_off:Icons.visibility
),
onPressed: (){
setState(() {
showPassword=!showPassword;
});
}) //使用文本框 TextField 自带的属性【后缀图标 suffixIcon】
),
),
````
3. 添加状态— showPassword
```js
bool showPassword=false;
- 根据状态展示不同内容
- 给图标添加点击事件
- 测试
细节优化
边距/异形屏幕问题?
使用 SafeArea(解决超出问题)
minimum: EdgeInsets.all(30.0); //解决padding问题
垂直高度不足问题?
使用 ListView(不够可以滚动) 替代 Column
添加注册页面
步骤:
- 添加文件 /pages/register.dart
- 将 login.dart 文件拷贝到 register.dart
- 修改类名称
- 修改 title
- 在路由中添加 register
- 添加 route name
- 添加 route handler
- 在 configureRoutes 中关联 name 和 router
- 修改了组件类型,需要重启 app 后测试
注册页面优化
步骤:
- 删除密码显示逻辑
- 添加确认密码
- 修改按钮及下方链接到文案
- 优化登陆注册跳转,使用 Navigator.pushReplacementNamed
Navigator.pushReplacementNamed(context, "login"); //可以删除记录
首页开始
参考pubpacka官方代码:
(注意我的环境有两处要改)
// fontSize: 30
fontSize: 30.0
// selectedItemColor: Colors.amber[800],
fixedColor:Colors.blue,
修改后的代码如下:
import 'package:flutter/material.dart';
import 'package:goodhouse/widgets/page_content.dart';
// 1. 需要准备 4 个 tab 内容区(tabView)
List<Widget> tabViewList = [
PageContent(name: '首页'),
PageContent(name: '搜索'),
PageContent(name: '咨询'),
PageContent(name: '我的'),
];
// 2. 需要准备 4 个 BottomNavigationBarItem
List<BottomNavigationBarItem> barItemList = [
BottomNavigationBarItem(title: Text('首页'), icon: Icon(Icons.home)),
BottomNavigationBarItem(title: Text('搜索'), icon: Icon(Icons.search)),
BottomNavigationBarItem(title: Text('咨询'), icon: Icon(Icons.info)),
BottomNavigationBarItem(title: Text('我的'), icon: Icon(Icons.account_circle)),
];
// 3. 编写有状态组件
class HomePage extends StatefulWidget {
HomePage({
Key key}) : super(key: key);
@override
_HomePageState createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> {
int _selectedIndex = 0;
void _onItemTapped(int index) {
setState(() {
_selectedIndex = index;
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
body:tabViewList[_selectedIndex],
bottomNavigationBar: BottomNavigationBar(
type: BottomNavigationBarType.fixed,
items: barItemList,
currentIndex: _selectedIndex,
fixedColor: Colors.green,
onTap: _onItemTapped,
),
);
}
}
首页第一屏页面
底部 tab 实现
步骤:
- 新建文件 /pages/home/tab_index/index.dart
- 添加依赖,编写无状态组件
- 简化实现顶部区域–appBar
- body 部分包含多个组件且可以滚动—使用 ListView
- 在 home/index.dart 中使用 TabIndex 组件
List < Widget > tabViewList = [
TabIndex(),
// PageContent(name: '首页'),
PageContent((name: "搜索")),
PageContent((name: "咨询")),
PageContent((name: "我的"))
];
轮播图的实现
步骤:
-
准备组件框架代码
- 新建文件 /widgets/common_swipper.dart
- 添加依赖 material 和 flutter_swiper
- 准备图片数据
- 编写无状态组件
- 添加 images 参数 并在构造函数中赋值
-
编写 swiper 核心代码
- 参照官网使用 swipper
- 修改 itemBuilder 和 itemCount
- Swiper 父组件指定高度
//父组件获取屏幕高度的固定方法 var width = MediaQuery.of(context).size.width;
-
测试
- 在 tabIndex 中使用 CommonSwiper
首页导航
1.数据准备
import 'package:flutter/material.dart';
//准备数据:title,image
class IndexNavigatorItem {
final String title;
final String imageUrl;
final Function (BuildContext contenxt) onTap;
IndexNavigatorItem(this.title,this.imageUrl,this.onTap);
}
List<IndexNavigatorItem> indexNavigatorItemList=[
IndexNavigatorItem('整租','static/images/home_index_navigator_total.png',(BuildContext context){
Navigator.of(context).pushNamed('login');
}),
IndexNavigatorItem('合租','static/images/home_index_navigator_share.png',(BuildContext context){
Navigator.of(context).pushNamed('login');
}),
IndexNavigatorItem('地图找房','static/images/home_index_navigator_map.png',(BuildContext context){
Navigator.of(context).pushNamed('login');
}),
IndexNavigatorItem('去出租','static/images/home_index_navigator_rent.png',(BuildContext context){
Navigator.of(context).pushNamed('login');
}),
];
- 添加依赖 material 和 index_navigator_item
- 编写无状态组件
- 完成页面结构
import 'package:flutter/material.dart';
import 'package:goodhouse/pages/home/tab_index/index_navigator_item.dart';
class IndexNavigator extends StatelessWidget {
const IndexNavigator({
Key key}) : super(key: key);
@override
Widget build(BuildContext context) {
return Container(
padding:EdgeInsets.only(top:6.0,bottom:6.0),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: indexNavigatorItemList
.map((item)=>InkWell(//InkWell可以实现水波纹效果
onTap: (){
item.onTap(context);
},
child: Column(
children:<Widget>[
Image.asset(item.imageUrl,
width:47.5,),
Text(item.title,style:TextStyle(
fontSize: 14.0,
fontWeight: FontWeight.w500
))
]
),
)).toList()//转化成List
),
);
}
}
使用图片缓存进行封装
步骤:
- 准备
- 安装 flutter_advanced_networkimage: ^0.5.0 依赖
- 添加文件 /widgets/common_image.dart
- 引入两个依赖
- 编写 正则 根据图片地址判断是网络图片还是本地图片
final networkUrlRef=RegExp('^http');//网络图片 final localworkUrlRef=RegExp('^static');//本地图片
-
编写框架代码
- 编写无状态组件
- 完善组件参数 src width height fit
-
完成核心逻辑
- 如果是网络图片,使用 flutter_advanced_networkimage
- 如果是本地图片,使用 Image.asset
- 返回 Container
import 'dart:html';
import 'package:flutter/material.dart';
import 'package:flutter_advanced_networkimage/provider.dart';
final networkUrlRef=RegExp('^http');//网络图片
final localworkUrlRef=RegExp('^static');//本地图片
class CommonImage extends StatelessWidget {
final String src;
final double width;
final double height;
final BoxFit fit;//图片的适应模式类型
const CommonImage({
this.src,Key key, this.width, this.height, this.fit}) : super(key: key);//把this.src放在最前面
@override
Widget build(BuildContext context) {
if(networkUrlRef.hasMatch(src)){
//正则判断是否网络图片
return Image(
width: width,
height: height,
fit: fit,
image: AdvancedNetworkImage(
src,
useDiskCache: true,//磁盘缓存
cacheRule: CacheRule(maxAge:Duration(days:7)),//保存时间
timeoutDuration: Duration(seconds: 20)//超时时间
),
);
}
if(localworkUrlRef.hasMatch(src)){
return Image.asset(
src,
width: width,
height: height,
fit: fit,
);
}
assert(false,"图片地址不合法");//抛出异常
return Container();
}
}
- 使用 CommonImage
CommonImage((src: item.imageUrl), (width: 47.5));
首页-tabIndex-推荐-准备
1.数据准备
class IndexRecommendItem{
final String title;
final String subTitle;
final String imageUrl;
final String navigateUrl;
const IndexRecommendItem(this.title,this.subTitle,this.imageUrl,this.navigateUrl);
}
const List<IndexRecommendItem> indexRecommendData=[
const IndexRecommendItem(
'家住回龙观','归属的感觉', 'static/images/home_index_recommend_1.png', 'login'),
const IndexRecommendItem(
'宜居四五环', '大都市生活','static/images/home_index_recommend_2.png', 'login'),
const IndexRecommendItem(
'喧嚣三里屯', '繁华的背后','static/images/home_index_recommend_3.png', 'login'),
const IndexRecommendItem(
'比邻十号线','地铁心连心', 'static/images/home_index_recommend_4.png', 'login'),
];
步骤:
-
准备
- 使用上一节准备好的数据
- 新建文件 pages/home/tab_index/index_recommond.dart
-
编写核心代码
- 添加依赖,无状态组件,dataList 参数,indexRecommendData 改成常量
- 添加背景色及边距
- 添加 wrap
-
测试
import 'package:flutter/material.dart';
import 'package:goodhouse/pages/home/tab_index/index_recommond_data.dart';
class IndexRecommond extends StatelessWidget {
final List<IndexRecommendItem> dataList