https://github.com/huanggengzhong/GoodHouse
中间经历过96次commit记录,根据commit可以回退到各个阶段,以便查看那时的代码.
下载安装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
效果:
步骤:
dependencies:
// 下面有空格,安装好后再通过.lock文件吧版本号写回去
fluro: any
步骤:
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();
static void configureRoutes(Router router) {
router.define(home, handler: _homeHandler);
router.define(login,handler:_loginHandler);
import 'package:goodhouse/routes.dart';
Router router=Router();
Routes.configureRoutes(router);
FlatButton(child:Text(Routes.home),onPressed:(){
Navigator.pushNamed(context,Routes.home);
}),
FlatButton(child:Text(Routes.login),onPressed:(){
Navigator.pushNamed(context,Routes.login);
}),
带参数页面处理
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
步骤:
步骤:
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,
),
);
}
}
步骤:
List < Widget > tabViewList = [
TabIndex(),
// PageContent(name: '首页'),
PageContent((name: "搜索")),
PageContent((name: "咨询")),
PageContent((name: "我的"))
];
步骤:
准备组件框架代码
编写 swiper 核心代码
//父组件获取屏幕高度的固定方法
var width = MediaQuery.of(context).size.width;
测试
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');
}),
];
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
),
);
}
}
步骤:
final networkUrlRef=RegExp('^http');//网络图片
final localworkUrlRef=RegExp('^static');//本地图片
编写框架代码
完成核心逻辑
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((src: item.imageUrl), (width: 47.5));
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'),
];
步骤:
准备
编写核心代码
测试
import 'package:flutter/material.dart';
import 'package:goodhouse/pages/home/tab_index/index_recommond_data.dart';
class IndexRecommond extends StatelessWidget {
final List<IndexRecommendItem> dataList;
const IndexRecommond({Key key,this.dataList=indexRecommendData}) : super(key: key);
@override
Widget build(BuildContext context) {
return Container(
padding: EdgeInsets.all(10.0),
decoration: BoxDecoration(color: Color(0x08000000)),
child: Column(
children:<Widget>[
Row(
mainAxisAlignment:MainAxisAlignment.spaceBetween,
children:<Widget>[
Text('房屋推荐',style:TextStyle(
color:Colors.black,
fontWeight:FontWeight.w600,
)),
Text('更多',style:TextStyle(
color:Colors.black54
))
]
),
Padding(padding: EdgeInsets.all(5),),
Wrap(
spacing: 10.0,
runSpacing: 10.0,
children:dataList.map((e) => Container(
width: (MediaQuery.of(context).size.width -30.0)/2,
height: 100.0,
decoration: BoxDecoration(color:Colors.red),
)).toList()
),
]
),
);
}
}
优化提取 item
import 'package:flutter/material.dart';
import 'package:goodhouse/pages/home/tab_index/index_recommond_data.dart';
import 'package:goodhouse/widgets/common_image.dart';
var textStyle=TextStyle(fontSize:14.0,fontWeight:FontWeight.w500);
class IndexRecommendItemWidget extends StatelessWidget {
final IndexRecommendItem data;
const IndexRecommendItemWidget(this.data, {Key key}) : super(key: key);
@override
Widget build(BuildContext context) {
return GestureDetector(//手势组件
onTap: (){
Navigator.of(context).pushNamed(data.navigateUrl);
},
child: Container(
decoration: BoxDecoration(
color:Colors.white,
),
width:(MediaQuery.of(context).size.width-30.0)/2,
padding: EdgeInsets.all(10.0),
child:Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children:<Widget>[
Column(
children: <Widget>[
Text(data.title,style:textStyle),
Text(data.subTitle,style:textStyle),
],
),
CommonImage(src:data.imageUrl,width:55.0)
]
)
),
);
}
}
数据准备 home/info/data.dart
// 资讯数据准备,注意下面的格式
class InfoItem {
final String title;
final String imageUrl;
final String source;
final String time;
final String navigateUrl;
const InfoItem(
{this.title, this.imageUrl, this.source, this.time, this.navigateUrl});
}
const List<InfoItem> infoData = [
const InfoItem(
title: '置业选择 | 安贞西里 三室一厅 河间的古雅别院',
imageUrl:
'https://wx2.sinaimg.cn/mw1024/005SQLxwly1g6f89l4obbj305v04fjsw.jpg',
source: "新华网",
time: "两天前",
navigateUrl: 'login'),
];
内容编码 info/index.dart
import 'package:flutter/material.dart';
import 'package:goodhouse/pages/home/info/data.dart';
import 'package:goodhouse/pages/home/info/item_widget.dart';
class Info extends StatelessWidget {
final bool showTitle;//考虑到资讯和首页有些显示,所以采用复用
final List<InfoItem> dataList;
const Info({Key key,this.showTitle=false, this.dataList=infoData}) : super(key: key);//infoData是data.dart文件内容
@override
Widget build(BuildContext context) {
return Container(
child: Column(
children:<Widget>[
if(showTitle)Container(
alignment:Alignment.centerLeft,
padding:EdgeInsets.all(10.0),
child:Text('最新资讯',style:TextStyle(color:Colors.black,fontWeight:FontWeight.w600))
),
Column(
children:dataList.map((myitem) =>
// Container(
// height:100.0,
// margin:EdgeInsets.only(bottom:10.0),
// decoration:BoxDecoration(color:Colors.red),
// )
ItemWidget(myitem)
).toList(),
)
]
),
);
}
}
子选项内容 info/item_widget.dat
import 'package:flutter/material.dart';
import 'package:goodhouse/pages/home/info/data.dart';
import 'package:goodhouse/widgets/common_image.dart';
class ItemWidget extends StatelessWidget {
final InfoItem data;
const ItemWidget(this.data,{Key key}) : super(key: key);//记住this.data一定要在前面,并且和后面对象独立
@override
Widget build(BuildContext context) {
return Container(
height: 100.0,
padding: EdgeInsets.only(left: 10.0, right: 10.0, bottom: 10.0),
child: Row(
children: <Widget>[
CommonImage(
src: data.imageUrl,
width: 120.0,
height: 90.0,
),
Padding(
padding: EdgeInsets.only(left: 10.0),
),
Expanded(
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[
Text(data.title,
style: TextStyle(
fontWeight: FontWeight.w600, color: Colors.black)),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[
Text(data.source, style: TextStyle(color: Colors.black54)),
Text(data.time, style: TextStyle(color: Colors.black54))
],
)
])) //Expanded自动填充组件
],
),
);
}
}
创建 tab_info/index.dart 文件,复用 info 文件内容渲染即可
在 home/index.dart 中将第三个资讯 tabViewList 改为 TabInfo().
数据准备:tab_search/dataList.dart
步骤:
import 'package:flutter/material.dart';
import 'dataList.dart';
class TabSearch extends StatefulWidget {
const TabSearch({Key key}) : super(key: key);
@override
_TabSearchState createState() => _TabSearchState();
}
class _TabSearchState extends State<TabSearch> {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('tabSearch'),),
body: Column(children: <Widget>[
Container(
height: 40.0,
child: Text('filterBar'),),
Expanded(child: ListView(
children: dataList.map((item)=>Container(
height: 200.0,
margin: EdgeInsets.only(bottom: 10.0),
decoration: BoxDecoration(color: Colors.grey),
)).toList(),
),)
],),
);
}
}
然后封装 item 和 tags:
common_tag.dart(中间用工厂构造函数)/room_list_item_widget.dart
工厂函数用法:
import 'package:flutter/material.dart';
class CommonTag extends StatelessWidget {
final String title;//这是外部传递过来的
final Color color;//自己的
final Color backgroundColor;
const CommonTag.origin(this.title,{Key key,this.color=Colors.black,this.backgroundColor}) : super(key: key);//注意外部和自己的写法
// 工厂函数
factory CommonTag(String title){
switch (title) {
case '近地铁':
return CommonTag.origin(
title,//title子不变,其它值都改变
color:Colors.red,
backgroundColor: Colors.red[50],
);
case '集中供暖':
return CommonTag.origin(
title,//title子不变,其它值都改变
color:Colors.blue,
backgroundColor: Colors.blue[50],
);
case '新上':
return CommonTag.origin(
title,//title子不变,其它值都改变
color:Colors.green,
backgroundColor: Colors.green[50],
);
case '随时看房':
return CommonTag.origin(
title,//title子不变,其它值都改变
color:Colors.orange,
backgroundColor: Colors.orange[50],
);
default:
return CommonTag.origin(title);
}
}
@override
Widget build(BuildContext context) {
return Container(
margin: EdgeInsets.only(right:4.0),
padding: EdgeInsets.only(left:4.0,right:4.0,top:4.0,bottom:2.0),
decoration: BoxDecoration(
color:backgroundColor,//工厂里自定义的背景
borderRadius: BorderRadius.circular(8.0)
),
child: Text(
title,//工厂里自定义的文字
style: TextStyle(
fontSize:10.0,
color:color//工厂里自定义的字体颜色
),
),
);
}
}
步骤:
import 'package:flutter/material.dart';
import 'package:goodhouse/widgets/common_image.dart';
class SearchBar extends StatefulWidget {
final bool showLocation; //是否显示位置
final Function goBackCallback; //回退
final String inputValue; //搜索框值
final String defaultInputValue; //默认显示值
final Function onCancel; //取消按钮
final bool showMap; //是否显示地图按钮
final Function onSearch; //点击搜索框触发
final ValueChanged<String> onSearchSubmit; //点击按键回车触发
const SearchBar(
{Key key,
this.showLocation,
this.goBackCallback,
this.inputValue = "",
this.defaultInputValue = "请输入搜索词",
this.onCancel,
this.showMap,
this.onSearch,
this.onSearchSubmit})
: super(key: key);
@override
_SearchBarState createState() => _SearchBarState();
}
class _SearchBarState extends State<SearchBar> {
String _searchWord = '';
TextEditingController _controller; //输入框的控制器
FocusNode _focus; //焦点声明
_onClean() {
_controller.clear(); //清除输入框控制器
setState(() {
_searchWord = '';
});
}
// 初始化控制器
@override
void initState() {
_focus = FocusNode(); //初始化焦点
_controller = TextEditingController(text: widget.inputValue);
super.initState();
}
@override
Widget build(BuildContext context) {
return Container(
child: Row(
children: <Widget>[
if (widget.showLocation != null)
//location判断
Padding(
padding: EdgeInsets.only(right: 10.0),
child: GestureDetector(
//手势组件
onTap: () {},
child: Row(
children: <Widget>[
Icon(Icons.room, color: Colors.green, size: 16.0),
Text('北京',
style: TextStyle(color: Colors.black, fontSize: 14.0))
],
),
),
),
if (widget.goBackCallback != null)
Padding(
padding: EdgeInsets.only(right: 10.0),
child: GestureDetector(
onTap: () {},
child: Row(children: <Widget>[
Icon(Icons.chevron_left, color: Colors.black87)
]))),
Expanded(
//自适应组件
child: Container(
height: 34.0,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(17.0),
color: Colors.grey[200]),
margin: EdgeInsets.only(right: 10.0),
child: TextField(
// 优化start
focusNode: _focus,
onTap: (){
if(widget.onSearchSubmit==null) {
_focus.unfocus();//解决回退失去焦点问题
}
widget.onSearch(); //使用自己定义的变量方法
},
onSubmitted: widget.onSearchSubmit,
textInputAction: TextInputAction.search, //按键变为搜索
controller: _controller, //自己定义的控制器
onChanged: (String value) {
//值改变问题
setState(() {
_searchWord = value;
});
},
// 优化end
decoration: InputDecoration(
hintText: '请输入搜索词',
border: InputBorder.none,
contentPadding: EdgeInsets.only(top: -2.0, left: -10.0),
suffixIcon: GestureDetector(
//触摸控件
onTap: () {
_onClean();
},
child: Icon(
//后置图标
Icons.clear,
size: 18.0,
color: _searchWord == ''
? Colors.grey[200]
: Colors.grey, //去图标的技巧:当空时设置为没颜色
),
),
icon: Padding(
padding: EdgeInsets.only(top: 4.0, left: 8.0),
child: Icon(
Icons.search,
size: 18.0,
color: Colors.grey,
),
)),
),
)),
if (widget.onCancel != null)
Padding(
padding: EdgeInsets.only(right: 10.0),
child: GestureDetector(
onTap: () {},
child: Row(children: <Widget>[
Text('取消',
style: TextStyle(color: Colors.black, fontSize: 14.0))
]))),
if (widget.showMap != null)
CommonImage(src: 'static/icons/widget_search_bar_map.png')
],
),
);
}
}
appBar:AppBar(
title:SearchBar(showLocation: true,showMap: true,onSearch: (){
// Navigator.of(context).pushNamed('search');//跳转到搜索页面
print("跳转到搜索页面测试");
},),
backgroundColor: Colors.white,
),
```
创建tab_profile/index.dart
import 'package:flutter/material.dart';
import 'package:goodhouse/pages/home/tab_profile/header.dart';
class TabProfile extends StatelessWidget {
const TabProfile({Key key}) : super(key: key);
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
elevation:0,//去掉边框
title:Text('我的'),
actions:<Widget>[//右侧组件
IconButton(
onPressed: (){
// Navigator.of(context).pushNamed(routeName);
print("准备跳转到设置页");
},
icon:Icon(Icons.settings),
)
]
),
body:ListView(
children: <Widget>[
Header(),
Text('内容区域')
],
)
);
}
}
import 'package:flutter/material.dart';
var loginFontStyle = TextStyle(fontSize: 20.0, color: Colors.white);
class Header extends StatelessWidget {
const Header({Key key}) : super(key: key);
Widget _noLoginPageHeader(BuildContext context){
return Container(
decoration: BoxDecoration(color: Colors.green),
height: 95.0,
padding: EdgeInsets.only(top: 10.0, left: 20.0, bottom: 20.0),
child: Row(children: <Widget>[
Container(
height: 65.0,
width: 65.0,
margin: EdgeInsets.only(right: 15.0),
child: CircleAvatar(
//圆形头像
backgroundImage: NetworkImage(
"https://tva1.sinaimg.cn/large/006y8mN6ly1g6tbgbqv2nj30i20i2wen.jpg"),
),
),
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Padding(padding: EdgeInsets.only(top: 6.0)),
Row(
children: <Widget>[
GestureDetector(
//手势组件
onTap: () {
Navigator.of(context).pushNamed("login");
},
child: Text('登录', style: loginFontStyle),
),
Text("/", style: loginFontStyle),
GestureDetector(
//手势组件
onTap: () {
print("点击了注册");
Navigator.of(context).pushNamed("rigister");
},
child: Text('注册', style: loginFontStyle),
),
],
),
Text('登录后可以体验更多',style:TextStyle(color:Colors.white))
],
)
]),
);
}
Widget _loginPageHeader(BuildContext context){
String userName="张三";
String userImage="https://tva1.sinaimg.cn/large/006y8mN6ly1g6tbnovh8jj30hr0hrq3l.jpg";
return Container(
decoration: BoxDecoration(color: Colors.green),
height: 95.0,
padding: EdgeInsets.only(top: 10.0, left: 20.0, bottom: 20.0),
child: Row(children: <Widget>[
Container(
height: 65.0,
width: 65.0,
margin: EdgeInsets.only(right: 15.0),
child: CircleAvatar(
//圆形头像
backgroundImage: NetworkImage(userImage),
),
),
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Padding(padding: EdgeInsets.only(top: 6.0)),
Row(
children: <Widget>[
Text(userName, style: loginFontStyle),
],
),
Text('查看编辑个人资料',style:TextStyle(color:Colors.white))
],
)
]),
);
}
@override
Widget build(BuildContext context) {
var isLogin=false;
return isLogin ?_noLoginPageHeader(context):_loginPageHeader(context);
}
}
1.准备数据文件 pages/home/tab_profile/function_button_data.dart
import 'package:flutter/material.dart';
class FunctionButtonItem{
final String imageUrl;
final String title;
final Function onTapHandle;
FunctionButtonItem(this.imageUrl,this.title,this.onTapHandle);
}
final List<FunctionButtonItem> list=[
FunctionButtonItem('static/images/home_profile_record.png', "看房记录", (){}),
FunctionButtonItem('static/images/home_profile_order.png', '我的订单', null),
FunctionButtonItem('static/images/home_profile_favor.png', '我的收藏', null),
FunctionButtonItem('static/images/home_profile_id.png', '身份认证', null),
FunctionButtonItem('static/images/home_profile_message.png', '联系我们', null),
FunctionButtonItem('static/images/home_profile_contract.png', '电子合同', null),
FunctionButtonItem('static/images/home_profile_wallet.png', '钱包', null),
FunctionButtonItem('static/images/home_profile_house.png', "房屋管理", (context){
bool isLogin=true;//假设先设置未登录
if(isLogin){
Navigator.pushNamed(context, 'login');
}
})
];
创建function_button.dart组件
import 'package:flutter/material.dart';
import 'function_button_data.dart';
class FunctionButton extends StatelessWidget {
const FunctionButton({Key key}) : super(key: key);
@override
Widget build(BuildContext context) {
return Container(
child: Wrap(
spacing: 1.0,
runSpacing: 1.0,
children:list.map((item)=>Container(
height:20.0,
width:MediaQuery.of(context).size.width*0.33,
decoration: BoxDecoration(color:Colors.red),
)).toList(),
)
);
}
}
item选项组件封装
import 'package:flutter/material.dart';
import '../../../widgets/common_image.dart';
import 'function_button_data.dart';
class FunctionButtonWidget extends StatelessWidget {
final FunctionButtonItem data;
const FunctionButtonWidget(this.data,{Key key}) : super(key: key);
@override
Widget build(BuildContext context) {
return GestureDetector(
onTap:(){
if(data.onTapHandle!=null){
data.onTapHandle(context);
}
},
child:Container(
margin: EdgeInsets.only(top:30.0),
width: MediaQuery.of(context).size.width*0.33,
child: Column(
children:<Widget>[
CommonImage(src:data.imageUrl),
Text(data.title)
]
),
)
);
}
}
广告组件Advertisement.dart
import 'package:flutter/material.dart';
import '../../../widgets/common_image.dart';
class Advertisement extends StatelessWidget {
const Advertisement({Key key}) : super(key: key);
@override
Widget build(BuildContext context) {
return Container(
padding: EdgeInsets.only(top:30.0,bottom:20.0,left:10.0,right:10.0),
child: CommonImage(src:'https://tva1.sinaimg.cn/large/006y8mN6ly1g6te62n8f4j30j603vgou.jpg')
);
}
}
效果:
分析:
需要 toast 弹窗,使用 fluttertoast
用法:
// 1. 安装依赖
fluttertoast: ^4.0.0
// 2. 引入依赖
import 'package:fluttertoast/fluttertoast.dart';
// 3. 使用
Fluttertoast.showToast(
msg: "This is Center Short Toast",
toastLength: Toast.LENGTH_SHORT,
gravity: ToastGravity.CENTER,
timeInSecForIos: 1,
backgroundColor: Colors.red,
textColor: Colors.white,
fontSize: 16.0
);
步骤:
准备
核心编码
退出登陆
按钮参考flutter官网的appBar:https://flutterchina.club/catalog/samples/tabbed-app-bar/
import 'package:flutter/material.dart';
import 'package:goodhouse/pages/home/tab_search/dataList.dart';
import 'package:goodhouse/widgets/room_list_item_widget.dart';
class RoomManagePage extends StatelessWidget {
const RoomManagePage({Key key}) : super(key: key);
@override
Widget build(BuildContext context) {
return DefaultTabController(
length: 2,//tab个数
initialIndex: 0,//初始索引
child: Scaffold(
appBar: AppBar(
title: Text('房屋管理'),
bottom: TabBar(
tabs: <Widget>[
Tab(text:'空置'),
Tab(text:'已租'),
]
),
),
body: TabBarView(
children: <Widget>[
ListView(
children:dataList.map((item)=>
RoomListItemWidget(item)
).toList()
),
ListView(
children:dataList.map((item)=>
RoomListItemWidget(item)
).toList()
)
]
),
),
);
}
}
同时增加底部悬浮按钮
准备悬浮按钮common_floating_button.dart组件
import 'package:flutter/material.dart';
class CommonFloatingButton extends StatelessWidget {
final String title;
final Function onTap;
const CommonFloatingButton(this.title, this.onTap,{Key key}) : super(key: key);
@override
Widget build(BuildContext context) {
return GestureDetector(
onTap:(){
if(onTap!=null) onTap();
},
child:Container(
margin: EdgeInsets.all(20.0),
height: 40.0,
decoration: BoxDecoration(
borderRadius:BorderRadius.circular(10.0),
color:Colors.green
),
child: Center(
child:Text(title,style:TextStyle(
color:Colors.white,
fontSize:16.0,
fontWeight:FontWeight.w600
))
),
)
);
}
}
在Scaffold组件中加入悬浮按钮
floatingActionButtonLocation: FloatingActionButtonLocation.centerFloat,//悬浮位置
floatingActionButton: CommonFloatingButton('发布房源', (){
Navigator.of(context).pushNamed("login");
}),
里面对各个组件进行了封装
大体代码如下
import 'package:flutter/material.dart';
import 'package:goodhouse/widgets/common_floating_button.dart';
import 'package:goodhouse/widgets/common_form_item.dart';
import 'package:goodhouse/widgets/common_image_pick.dart';
import 'package:goodhouse/widgets/common_radio_form_item.dart';
import 'package:goodhouse/widgets/common_select_form_item.dart';
import 'package:goodhouse/widgets/common_title.dart';
import 'package:goodhouse/widgets/room_Appliance.dart';
class RoomAddPage extends StatefulWidget {
RoomAddPage({Key key}) : super(key: key);
@override
_RoomAddPageState createState() => _RoomAddPageState();
}
class _RoomAddPageState extends State<RoomAddPage> {
int rentType=0;
int rentTypeTwo=0;
int roomType = 0;
@override
Widget build(BuildContext context) {
return Scaffold(
appBar:AppBar(
title:Text('发布房源'),
),
floatingActionButton: CommonFloatingButton('提交', (){
print("点击了提交");
}),
floatingActionButtonLocation: FloatingActionButtonLocation.centerFloat,
body:ListView(
children:<Widget>[
CommonTitle('房源信息'),
CommonFormItemWidget(//自定义小区
label: '小区',
contentBuilder:(context){
return Container(
height: 40.0,
width: 280.0,
child:GestureDetector(
behavior: HitTestBehavior.translucent,//解决点击空白地方无效问题
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[
Text('请选择小区',style:TextStyle(
fontSize:16.0
)),
Icon(Icons.keyboard_arrow_right)
],
),
onTap: (){
print("跳转选择小区页");
},
)
);
}
),
CommonFormItemWidget(
label: '租金',
hitText:'请输入租金',
suffixText: '元/月',
controller: TextEditingController(),
),
CommonFormItemWidget(
label: '大小',
hitText:'请输入房屋大小',
suffixText: '平方米',
controller: TextEditingController(),
),
CommonRadioFormIten(
label: '租赁方式',
options: ['合租', '整租'],
value: rentType,
onChange: (index){
print(index);
setState(() {
rentType=index;
});
},
),
CommonRadioFormIten(
label: '装修',
options: ['精装', '简装'],
value: rentTypeTwo,
onChange: (index) {
setState(() {
rentTypeTwo = index;
});
}),
CommonSelectFormItemWedget(
label: '户型',
value:roomType,
onChange: (val){
setState(() {
roomType=val;
});
},
options: ['一室','二室','三室','四室'],
),
CommonTitle('房屋图像'),
// CommonImagePicker(
// onChange: (List files) {},//删除File,不然会报错
// // onChange: (List files) {},
// ),
CommonTitle('房屋标题'),
CommonTitle('房屋配置'),
RoomAppliance(
onChange: (data) {},
),
CommonTitle('房屋描述'),
]
),
);
}
}
import 'package:flutter/material.dart';
import 'package:goodhouse/pages/room_detail/data.dart';
import 'package:goodhouse/widgets/common_title.dart';
import 'package:share/share.dart';
var bottomButtonTextStyle = TextStyle(fontSize: 20.0, color: Colors.white);
class RoomDetailPage extends StatefulWidget {
final String roomId;
const RoomDetailPage({Key key, this.roomId}) : super(key: key);
@override
_RoomDetailPageState createState() => _RoomDetailPageState();
}
class _RoomDetailPageState extends State<RoomDetailPage> {
RoomDetailData data;
bool isLike = false;
@override
void initState() {
// TODO: implement initState
data = defaultData;
super.initState();
}
@override
Widget build(BuildContext context) {
if (null == data) return Container();
return Scaffold(
appBar: AppBar(
// title: Text('roomId:${widget.roomId}'),
title: Text(data.title),
actions: <Widget>[
IconButton(
icon: Icon(Icons.share),
onPressed: () {
Share.share('https://baidu.com');
}),
],
),
body: Stack(
children: <Widget>[
ListView(children: <Widget>[
CommonTitle('房屋配置'),
CommonTitle('房屋概况'),
CommonTitle('猜你喜欢'),
]),
Positioned(
width: MediaQuery.of(context).size.width,
height: 100.0,
bottom: 0,
child: Container(
color: Colors.grey[200],
padding: EdgeInsets.only(top: 10.0, left: 20.0, bottom: 20.0),
child: Row(
children: <Widget>[
GestureDetector(
onTap: () {
setState(() {
isLike = !isLike;
});
},
child: Container(
height: 50.0,
width: 40.0,
margin: EdgeInsets.only(right: 10.0),
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[
Icon(isLike ? Icons.star : Icons.star_border,
size: 24.0,
color: isLike ? Colors.green : Colors.black),
Text(isLike ? '已收藏' : '收藏',
style: TextStyle(fontSize: 12.0))
],
)),
),
Expanded(
child: GestureDetector(
onTap: () {},
child: Container(
height: 50.0,
margin: EdgeInsets.only(right: 5.0),
decoration: BoxDecoration(
color: Colors.cyan,
borderRadius: BorderRadius.circular(6.0)),
child: Center(
child:
Text('联系房东', style: bottomButtonTextStyle),
),
))),
Expanded(
child: GestureDetector(
onTap: () {},
child: Container(
height: 50.0,
margin: EdgeInsets.only(right: 5.0),
decoration: BoxDecoration(
color: Colors.green,
borderRadius: BorderRadius.circular(6.0)),
child: Center(
child:
Text('预约看房', style: bottomButtonTextStyle),
),
))),
],
),
),
)
],
));
}
}
import 'package:flutter/material.dart';
import 'package:goodhouse/pages/home/tab_search/filter_bar/data.dart';
import 'package:goodhouse/pages/home/tab_search/filter_bar/item.dart';
class FilterBar extends StatefulWidget {
final ValueChanged<FilterBarResult> onChange;
const FilterBar({Key key, this.onChange}) : super(key: key);
@override
_FilterBarState createState() => _FilterBarState();
}
class _FilterBarState extends State<FilterBar> {
@override
Widget build(BuildContext context) {
return Container(
height: 41.0,
decoration: BoxDecoration(
border:Border(bottom:BorderSide(color:Colors.black12,width: 1.0))
),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: <Widget>[
Item(
title: '区域',
isActive:true,
onTap: (context){},
),
Item(
title: '方式',
isActive:false,
onTap: (context){},
),
Item(
title: '租金',
isActive:false,
onTap: (context){},
),
Item(
title: '筛选',
isActive:false,
onTap: (context){},
),
],
),
);
}
}
文件utils/dio_http.dart
核心代码
import 'dart:io';
import 'package:dio/dio.dart';
import 'package:flutter/material.dart';
import '../config.dart';
class DioHttp {
Dio _client;
BuildContext context;
static DioHttp of(BuildContext context) {
return DioHttp.internal(context);
}
DioHttp.internal(BuildContext context) {
if (_client != null || context != this.context) {
this.context = context;
var options = BaseOptions(
baseUrl: Config.BaseUrl,
connectTimeout: 1000 * 10,
receiveTimeout: 1000 * 3,
extra: {'context': context});
var client = Dio(options);
this._client = client;
}
}
Future<Response<Map<String, dynamic>>> get(String path,
[Map<String, dynamic> params, String token]) async {
var options = Options(headers: {'Authorization': token});
return await _client.get(path, queryParameters: params, options: options);
}
Future<Response<Map<String, dynamic>>> post(String path,
[Map<String, dynamic> params, String token]) async {
var options = Options(headers: {'Authorization': token});
return await _client.post(path, data: params, options: options);
}
Future<Response<Map<String, dynamic>>> postFormData(String path,
[Map<String, dynamic> params, String token]) async {
var options = Options(
contentType: ContentType.parse('multipart/form-data'),
headers: {'Authorization': token});
return await _client.post(path, data: params, options: options);
}
}
核心代码:
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:goodhouse/utils/common_toast.dart';
import 'package:goodhouse/utils/dio_http.dart';
import 'package:goodhouse/utils/string_is_null_or_empty.dart';
// import 'package:goodhouse/widgets/page_content.dart';
class RigisterPage extends StatefulWidget {
const RigisterPage({Key key}) : super(key: key);
@override
RigisterPageState createState() {
return new RigisterPageState();
}
}
class RigisterPageState extends State<RigisterPage> {
var usernameController = TextEditingController();
var passwordController = TextEditingController();
var repeatPasswordController = TextEditingController();
// 注册方法
_registerHandler() async {
var username = usernameController.text;
var password = passwordController.text;
var repeatPassword = repeatPasswordController.text;
if (password != repeatPassword) {
CommontToast.showToast('两次输入密码不一致!');
return;
}
if(stringIsNullOrEmpty(username)||stringIsNullOrEmpty(password)) {
CommontToast.showToast("用户名或者密码不能为空");
return;
}
const url="/register";
var params ={'username':username,'password':password};
var res=await DioHttp.of(context).post(url,params);
// var resString=json.decode(res.toString());
// print(resString);//这个转成了字符串
// print(res.data['data']['code']==0);
if(res.data['data']['code']==0){
CommontToast.showToast("注册成功,请登录");
Navigator.of(context).pushReplacementNamed('login');
}
}
// 定义一个bool值
bool showPassword = false;
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text("注册")),
body: SafeArea(
minimum: EdgeInsets.all(30.0),
child: ListView(children: <Widget>[
TextField(
controller: usernameController,
//文本输入框组件
decoration: InputDecoration(labelText: '用户名', hintText: '请输入用户名'),
),
Padding(
padding: EdgeInsets.all(10.0),
),
TextField(
controller: passwordController,
decoration: InputDecoration(
labelText: "密码",
hintText: "请输入密码",
// 注册页面不需要
// suffixIcon: IconButton(
// icon: Icon(showPassword
// ? Icons.visibility_off
// : Icons.visibility),
// onPressed: () {
// setState(() {
// showPassword = !showPassword;
// });
// }) //使用文本框 TextField 自带的属性【后缀图标 suffixIcon】
),
),
Padding(
padding: EdgeInsets.all(10.0),
),
TextField(
controller: repeatPasswordController,
decoration: InputDecoration(
labelText: "确认密码",
hintText: "请再次输入密码",
// 注册页面不需要
// suffixIcon: IconButton(
// icon: Icon(showPassword
// ? Icons.visibility_off
// : Icons.visibility),
// onPressed: () {
// setState(() {
// showPassword = !showPassword;
// });
// }) //使用文本框 TextField 自带的属性【后缀图标 suffixIcon】
),
),
Padding(
padding: EdgeInsets.all(10.0),
),
RaisedButton(
color: Colors.green,
child: Text(
"注册",
style: TextStyle(color: Colors.white),
),
onPressed: () {
// print("注册");
_registerHandler();
},
),
Padding(
padding: EdgeInsets.all(10.0),
),
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text("已有账号,"),
FlatButton(
child: Text(
"去登录",
style: TextStyle(color: Colors.green),
),
onPressed: () {
Navigator.pushReplacementNamed(context, 'login');
// Navigator.pushNamed(context, 'login');
})
],
)
]),
));
}
}
因真实接口失效了,故只用了一个真实线上模拟接口.
配置启动页面路由和设置第一页为启动页.
启动页面核心代码
import 'dart:async';
import 'package:flutter/material.dart';
class LoadingPage extends StatefulWidget {
LoadingPage({Key key}) : super(key: key);
@override
_LoadingPageState createState() => _LoadingPageState();
}
class _LoadingPageState extends State<LoadingPage> {
@override
void initState() {
Timer(Duration(seconds: 3), () {
Navigator.of(context).pushReplacementNamed('/');
});
super.initState();
}
@override
Widget build(BuildContext context) {
return Container(
decoration: BoxDecoration(
image: DecorationImage(
fit: BoxFit.fill,
image: AssetImage('static/images/loading.jpg'))),
);
}
}
步骤:
打包成功后apk文件的位置:build\app\outputs\apk\release\app-release.apk (18.4MB)
安装成功后,打开有闪退问题,在 android/app/build.gradle 文件,最后解决办法:
buildTypes {
release {
signingConfig signingConfigs.debug
//关键代码:关闭混淆,解决闪退问题
minifyEnabled false
shrinkResources false
// 关键代码:解决闪退问题
}
}
我安装后测试能正常运行,本次开发告一段落,但学习不会停止,请期待下一个开发.