flutter模仿boss直聘客户端效果
效果图:
前言:
各位同学大家好 boss直聘APP相信大家都用过吧, 今天给大家带来一个开源项目模仿了boss直聘中的一些经典页面的实现 希望能帮助到各位flutter的学习。原生安卓 iOS 实现都比较的简单我这里就不展开讲,今天主要讲解flutter版本如何实现 废话不多说我正式开始 。
准备工作:
安装flutter环境 如果只是跑安卓设备 win系统就行了 要是同时运行安卓和iOS 就需要mac电脑了 配置环境变量这边就不展开讲了大家可以看我之前的文章
需要用到的三方库
dio: ^3.0.9 网络请求库
toast: ^0.1.5 toast 库
flutter_swiper: ^1.1.6 轮播图库
具体代码实现
1欢迎页面就做了一个3秒的倒计时然后跳转到首页
import 'dart:async';
import 'package:flutter/material.dart';
import 'routes/Routes.dart';
/**
* 创建人:xuqing
* 创建时间:2020年3月28日20:34:48
*
*
*/
void main() => runApp(new App());
class App extends StatelessWidget {
@override
Widget build(BuildContext context) {
return new MaterialApp(
onGenerateRoute:onGenerateRoute,
theme: new ThemeData(
primaryIconTheme: const IconThemeData(color: Colors.white),
brightness: Brightness.light,
primaryColor: new Color.fromARGB(255, 0, 215, 198),
accentColor: Colors.cyan[300],
),
home: Scaffold(
body: MainPage(),
)
// home: new MainPage(title: 'Boss直聘'),
);
}
}
class MainPage extends StatefulWidget {
MainPage({Key key}) : super(key: key);
@override
_MainPageState createState() {
return _MainPageState();
}
}
class _MainPageState extends State<MainPage> {
@override
void initState() {
super.initState();
int count = 0;
const period = const Duration(seconds: 1);
print('currentTime='+DateTime.now().toString());
Timer.periodic(period, (timer) {
//到时回调
print('afterTimer='+DateTime.now().toString());
count++;
if (count >= 3) {
//取消定时器,避免无限回调
timer.cancel();
timer = null;
toLoing();
}
});
}
@override
void dispose() {
super.dispose();
}
void toLoing()async{
//Navigator.pushNamed(context, "/my");
Navigator.of(context).pushNamedAndRemoveUntil(
"/my", ModalRoute.withName("/my"));
}
@override
Widget build(BuildContext context) {
// TODO: implement build
return Container(
color: Colors.green,
child: Center(
child: Text("BOSS 直聘",style: TextStyle(
fontSize: 30,
color: Colors.white
),
),
),
);
}
}
这里的跳转我们要用
Navigator.of(context).pushNamedAndRemoveUntil(
"/my", ModalRoute.withName("/my"));
这种路由跳转来清除所有的 widget 这样我们在回退到主页再次点击返回键就能退出正app
###然后是主页的代码的实现
import 'package:flutter/material.dart';
import 'pages/chat_page.dart';
import 'pages/company_page.dart';
import 'pages/mine_page.dart';
import 'pages/job_page.dart';
import 'layout_type.dart';
class My extends StatefulWidget {
My({Key key, this.title}) : super(key: key);
final String title;
@override
_MyState createState() => new _MyState();
}
class _MyState extends State<My> {
LayoutType _layoutSelection = LayoutType.job;
Color _colorTabMatching({LayoutType layoutSelection}) {
return _layoutSelection == layoutSelection ? Colors.cyan[300] : Colors.grey;
}
BottomNavigationBarItem _buildItem(
{String icon, LayoutType layoutSelection}) {
String text = layoutName(layoutSelection);
return BottomNavigationBarItem(
icon: new Image.asset(
icon,
width: 35.0,
height: 35.0,
),
title: Text(
text,
style: TextStyle(
color: _colorTabMatching(layoutSelection: layoutSelection),
),
),
);
}
Widget _buildButtonNavBar() {
return BottomNavigationBar(
type: BottomNavigationBarType.fixed,
items: [
_buildItem(
icon: _layoutSelection == LayoutType.job
? "images/ic_main_tab_find_pre.png"
: "images/ic_main_tab_find_nor.png",
layoutSelection: LayoutType.job),
_buildItem(
icon: _layoutSelection == LayoutType.company
? "images/ic_main_tab_company_pre.png"
: "images/ic_main_tab_company_nor.png",
layoutSelection: LayoutType.company),
_buildItem(
icon: _layoutSelection == LayoutType.chat
? "images/ic_main_tab_contacts_pre.png"
: "images/ic_main_tab_contacts_nor.png",
layoutSelection: LayoutType.chat),
_buildItem(
icon: _layoutSelection == LayoutType.mine
? "images/ic_main_tab_my_pre.png"
: "images/ic_main_tab_my_nor.png",
layoutSelection: LayoutType.mine),
],
onTap: _onSelectTab,
);
}
void _onLayoutSelected(LayoutType selection) {
setState(() {
_layoutSelection = selection;
});
}
void _onSelectTab(int index) {
switch (index) {
case 0:
_onLayoutSelected(LayoutType.job);
break;
case 1:
_onLayoutSelected(LayoutType.company);
break;
case 2:
_onLayoutSelected(LayoutType.chat);
break;
case 3:
_onLayoutSelected(LayoutType.mine);
break;
}
}
Widget _buildBody() {
LayoutType layoutSelection = _layoutSelection;
switch (layoutSelection) {
case LayoutType.job:
return JobPage();
case LayoutType.company:
return CompanyPage();
case LayoutType.chat:
return ChatPage();
case LayoutType.mine:
return MinePage();
}
}
@override
Widget build(BuildContext context) {
return new Scaffold(
body: _buildBody(),
bottomNavigationBar: _buildButtonNavBar(),
);
}
}
我们通过 BottomNavigationBar 来做底部的tab的切换 图片资源存放在assets 下面的images 目录下(没有此目录需要自己创建)
在body组件这里来添加对应的要显示的widget (包括首页 职位 公司 消息 我的 )
Widget _buildBody() {
LayoutType layoutSelection = _layoutSelection;
switch (layoutSelection) {
case LayoutType.job:
return JobPage();
case LayoutType.company:
return CompanyPage();
case LayoutType.chat:
return ChatPage();
case LayoutType.mine:
return MinePage();
}
}
@override
Widget build(BuildContext context) {
return new Scaffold(
body: _buildBody(),
bottomNavigationBar: _buildButtonNavBar(),
);
}
}
到此就实现了主要的tab切换 和widget 页面的加载
然后我们对各个页面你的widget 的UI布局和数据获取的的实现进行讲解说明
职位模块 具体代码实现
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import '../bean/positioninfo.dart';
import '../network/servicepath.dart';
import '../utils/ToastUtil.dart';
/***
*
* 创建人:xuqing
* 创建时间:2020年3月9日00:40:10
* 类说明:职位
*
*/
class JobPage extends StatefulWidget {
JobPage({Key key}) : super(key: key);
@override
_JobPageState createState() {
return _JobPageState();
}
}
class _JobPageState extends State<JobPage> {
List _getdata=[];
Data data=null ;
@override
void initState() {
super.initState();
getInfo();
}
@override
void dispose() {
super.dispose();
}
void getInfo() async {
getPositionInfo().then((value){
setState(() {
PositionInfo positionInfo=value;
String msg=positionInfo.msg;
int code =positionInfo.code;
if(code==200){
_getdata=positionInfo.data;
print(_getdata);
ToastUtil.showinfo(context, msg);
}else{
ToastUtil.showinfo(context, msg);
}
});
});
}
@override
Widget build(BuildContext context) {
// TODO: implement build
return Scaffold(
appBar: AppBar(
title: Text("职位",style:
TextStyle(
color: Colors.white,
fontSize: 20
),),
centerTitle: true,
),
body: Container(
child: new ListView.builder(
itemCount: (_getdata == null) ? 0 : _getdata.length,
itemBuilder: (BuildContext context , int position){
/* data=_getdata[position];
return JobList(data);*/
return getItem(position);
//getItem(position);
}),
),
);
}
Widget getItem(int index){
data=_getdata[index];
return new Padding(padding: EdgeInsets.only(
top: 3.0,
left: 5.0,
right: 5.0,
bottom: 3.0
),
child: new SizedBox(
child: Card(
elevation: 0.0,
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
new Expanded(
child: new Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
children: <Widget>[
new Row(
children: <Widget>[
new Padding(padding: EdgeInsets.only(
top: 10.0,
left: 10.0,
bottom: 5.0
),
child: new Text(data.name),
),
new Expanded(
child:Column(
crossAxisAlignment: CrossAxisAlignment.end,
children: <Widget>[
new Padding(padding: EdgeInsets.only(
right: 10.0
),
child: Text(
data.salary,
style: TextStyle(
color: Colors.red
),
),
)
],
)
),
],
),
new Container(
child: new Text(data.name+data.size,
textAlign: TextAlign.left,
style: new TextStyle(
fontSize: 20.0,
color: Colors.grey
),
),
margin: EdgeInsets.only(
top: 5.0,
left: 10.0,
bottom: 5.0
),
),
new Divider(),
new Row(
children: <Widget>[
new Padding(padding: EdgeInsets.only(
top: 5.0,
left: 10.0,
right: 5.0,
bottom: 15.0,
),
child: new Text(
data.username+"|"+data.title,
style: TextStyle(
color: new Color.fromARGB(255, 0, 215, 198)
),
),
)
],
)
],
)
),
],
),
),
),
);
}
}
主要实现就是listview列表组件加载item item里面是嵌套了Card 组件在在外层里面分别人在用Row组件嵌套
Column 组件来实现这个item的布局
网络请求的封装
import 'package:bosszhiping_tab/bean/messageinfo.dart';
import 'package:dio/dio.dart';
import '../config/api.dart';
import '../bean/positioninfo.dart';
import '../bean/companyinfo.dart';
import '../bean/companydetails.dart';
import '../bean/back_info.dart';
/**
* 获取主页数据
*
*
*/
Future getPositionInfo()async{
try{
Dio dio=new Dio();
Response res= await dio.get(Api.POSITIONINFO);
PositionInfo positionInfo=PositionInfo.fromJson(res.data);
print(res.data.toString());
return positionInfo;
}catch(e){
return print(e);
}
}
/**
* 获取消息数据
*
*
*/
Future getmessageinfo() async{
try{
Dio dio=new Dio();
Response response=await dio.get(Api.MESSAGEINFO);
MessageInfo messageInfo=MessageInfo.fromJson(response.data);
print(response.data.toString());
return messageInfo;
}catch(e){
return print(e);
}
}
/**
*
* 获取公司信息数据
*
*/
Future getcompanyInfo()async{
try{
Dio dio=new Dio();
Response response=await dio.get(Api.COMPANY_INFO);
Company companyInfo=Company.fromJson(response.data);
print(response.data.toString());
return companyInfo;
}catch(e){
return print(e);
}
}
/***
*
* 公司详细信息
*
*
*/
Future getcomanydetails(var data)async{
try{
Dio dio=new Dio();
Response response=await dio.get(Api.COMPAN_DETAILS,queryParameters: data);
print("comdetails ----- > "+response.data.toString());
Comdetails comdetails=Comdetails.fromJson(response.data);
return comdetails;
}catch(e){
print(e);
}
}
/**
*
*
* 提交职位信息
*
*/
Future addPositionInfo(var data)async{
try{
Dio dio=new Dio();
Response response=await dio.post(Api.ADD_POSITION_INFO,queryParameters: data);
print(response.data);
BackInfo backInfo=BackInfo.fromJson(response.data);
return backInfo;
}catch(e){
print(e);
}
}
网络请求方法的调用
void initState() {
super.initState();
getInfo();
}
@override
void dispose() {
super.dispose();
}
void getInfo() async {
getPositionInfo().then((value){
setState(() {
PositionInfo positionInfo=value;
String msg=positionInfo.msg;
int code =positionInfo.code;
if(code==200){
_getdata=positionInfo.data;
print(_getdata);
ToastUtil.showinfo(context, msg);
}else{
ToastUtil.showinfo(context, msg);
}
});
});
}
然后我们刷新数据 显示在listview的item组件里面的就OK了
剩下的一些界面我就把代码贴出来给大家看看篇幅是太长了
公司模块 具体代码实现
import 'package:flutter/material.dart';
import '../bean/companyinfo.dart';
import '../network/servicepath.dart';
import '../utils/ToastUtil.dart';
import 'company/company_details.dart';
/**
*
* 创建人:xuqing
* 创建时间:
* 类说明:公司信息
*
*
*
*/
class CompanyPage extends StatefulWidget {
CompanyPage({Key key}) : super(key: key);
@override
_CompanyPageState createState() {
return _CompanyPageState();
}
}
class _CompanyPageState extends State<CompanyPage> {
List getdata;
Data _data=null;
@override
void initState() {
super.initState();
getcompanyinfo();
}
void getcompanyinfo(){
getcompanyInfo().then((value){
setState(() {
Company company=value;
String msg=company.msg;
int code=company.code;
if(code==200){
getdata=company.data;
ToastUtil.showinfo(context, msg);
}else{
ToastUtil.showinfo(context, msg);
}
});
});
}
@override
void dispose() {
super.dispose();
}
@override
Widget build(BuildContext context) {
// TODO: implement build
return Scaffold(
appBar: AppBar(
title: Text("公司",style:
TextStyle(
color: Colors.white,
fontSize: 20
),),
centerTitle: true,
),
body: Container(
child: ListView.builder(
//itemCount: 3,
itemCount: (getdata==null)?0:getdata.length,
itemBuilder: (BuildContext context, int position){
return getItem(position);
}),
),
);
}
//公司信息 item 布局
Widget getItem(int index){
_data =getdata[index];
print(_data.hot);
return GestureDetector(
child: new Padding(
padding: const EdgeInsets.only(
top: 3.0,
left: 5.0,
right: 5.0,
bottom: 3.0
),
child: SizedBox(
child: new Card(
elevation: 0.0,
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
new Padding(padding: const EdgeInsets.only(
top: 10.0,
left: 15.0,
right: 15.0,
bottom: 0.0
),
child: Image.network(_data.logo,
width: 50.0,
height: 100.0,
),
),
Expanded(
child:Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
children: <Widget>[
new Container(
child: Text(_data.name,
textAlign: TextAlign.left,
style: TextStyle(
fontSize: 15.0,
),
),
margin: const EdgeInsets.only(top: 10.0,bottom: 5.0),
),
new Padding(padding: const EdgeInsets.only(
top: 5.0,
left: 0.0,
right: 5.0,
bottom: 5.0,
),
child: Text(_data.location,
style: TextStyle(
fontSize: 13.0,
color: Colors.grey
),
),),
new Padding(padding: const EdgeInsets.only(
top: 5.0,
left: 0.0,
right: 5.0,
bottom: 5.0,
),
child: Text(
_data.type+"|"+_data.size+"|"+_data.employee,
style: TextStyle(
fontSize: 13.0,
color: Colors.grey
),
),),
new Divider(
height: 2.0,
color: Colors.black54,
),
Row(
children: <Widget>[
new Padding(padding: const EdgeInsets.only(
top: 5.0,
left: 0.0,
right: 5.0,
bottom: 15.0
),
child: Text("热招"+_data.hot+"等"+_data.count+"个职位",
style: TextStyle(
fontSize: 13.0,
color: Colors.grey
),
),
),
new Expanded(child: Column(
crossAxisAlignment: CrossAxisAlignment.end,
children: <Widget>[
new Padding(padding: const EdgeInsets.only(
bottom: 8.0
),
child: const Icon(
Icons.keyboard_arrow_right,
color: Colors.grey,
),
)
],
)
)
],
)
],
)),
],
),
),
),
),
onTap: (){
setState(() {
onItemclick(index);
});
},
);
}
void onItemclick(int index){
_data =getdata[index];
print("获取到的头ID"+_data.id.toString());
Navigator.of(context).pushNamed("company_details" ,arguments: {"id":_data.id});
}
}
消息模块 具体代码实现
import 'package:flutter/material.dart';
import '../network/servicepath.dart';
import '../bean/messageinfo.dart';
import '../utils/ToastUtil.dart';
/**
*
* 创建人:xuqing
* 创建时间:2020年3月15日22:55:51
* 类说明:消息模块
*
*
*/
class ChatPage extends StatefulWidget {
ChatPage({Key key}) : super(key: key);
@override
_ChatPageState createState() {
return _ChatPageState();
}
}
class _ChatPageState extends State<ChatPage> {
List getdata=[];
Data _data=null;
@override
void initState() {
super.initState();
getMessageInfo();
}
void getMessageInfo()async{
getmessageinfo().then((value){
setState(() {
MessageInfo messageInfo=value;
String msg=messageInfo.msg;
int code=messageInfo.code;
if(code==200){
getdata=messageInfo.data;
ToastUtil.showinfo(context, msg);
}else{
ToastUtil.showinfo(context, msg);
}
});
});
}
@override
void dispose() {
super.dispose();
}
@override
Widget build(BuildContext context) {
// TODO: implement build
return Scaffold(
appBar: AppBar(
title: Text("消息",style:
TextStyle(
color: Colors.white,
fontSize: 20
),),
centerTitle: true,
),
body: Container(
child:new ListView.builder(
itemCount:(getdata == null)?0:getdata.length,
itemBuilder: (BuildContext context , int position){
return getItem(position);
}),
),
);
}
Widget getItem(int index){
_data=getdata[index];
return
SizedBox(
child: Card(
elevation: 0.0,
child: Column(
children: <Widget>[
Row(
children: <Widget>[
Container(
padding: EdgeInsets.only(left: 20),
child: ClipOval(
child: Image.network(
_data.headportraiturl,
height: 40,
width: 40,
fit: BoxFit.cover,
),
),
),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
new Container(
child: new Text(_data.nickname,
textAlign: TextAlign.left,
style: new TextStyle(
fontSize: 18.0,
color: Colors.black
),
),
margin: EdgeInsets.only(
top: 2.0,
left: 10.0,
bottom: 2.0
),
),
new Container(
child: new Text(_data.companyname+"|"+_data.jobname,
textAlign: TextAlign.left,
style: new TextStyle(
fontSize: 15.0,
color: Colors.grey
),
),
margin: EdgeInsets.only(
top: 2.0,
left: 10.0,
bottom: 2.0
),
),
new Container(
child: new Text(_data.msg,
textAlign: TextAlign.left,
style: new TextStyle(
fontSize: 12.0,
color: Colors.grey
),
),
margin: EdgeInsets.only(
top: 2.0,
left: 10.0,
bottom: 2.0
),
),
],
),
)
],
),
new Padding(padding: const EdgeInsets.only(left: 5.0,right: 5.0),
child: new Divider(
height: 2.0,
color: Colors.black54,
),
)
],
),
),
);
Container(
child: new Padding(padding: EdgeInsets.only(
left: 2.0,
right: 2.0,
),
child: SizedBox(
child: Column(
children: <Widget>[
Row(
children: <Widget>[
Container(
padding: EdgeInsets.only(left: 20),
child: ClipOval(
child: Image.network(
_data.headportraiturl,
height: 40,
width: 40,
fit: BoxFit.cover,
),
),
),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
new Container(
child: new Text(_data.nickname,
textAlign: TextAlign.left,
style: new TextStyle(
fontSize: 18.0,
color: Colors.black
),
),
margin: EdgeInsets.only(
top: 2.0,
left: 10.0,
bottom: 2.0
),
),
new Container(
child: new Text(_data.companyname+"|"+_data.jobname,
textAlign: TextAlign.left,
style: new TextStyle(
fontSize: 15.0,
color: Colors.grey
),
),
margin: EdgeInsets.only(
top: 2.0,
left: 10.0,
bottom: 2.0
),
),
new Container(
child: new Text(_data.msg,
textAlign: TextAlign.left,
style: new TextStyle(
fontSize: 12.0,
color: Colors.grey
),
),
margin: EdgeInsets.only(
top: 2.0,
left: 10.0,
bottom: 2.0
),
),
],
),
)
],
),
new Padding(padding: const EdgeInsets.only(left: 5.0,right: 5.0),
child: new Divider(
height: 2.0,
color: Colors.black54,
),
)
],
),
/* child: Card(
elevation: 0.0,
),*/
),
)
);
}
}
还有公司详情页面 代码具体看源代码
最后总结:
整个学习的项目采用了mvc 架构模式去编写,后台接口返回的json数据通过格式化转成数据模型类来处理
对于各个分区模块也做了适当的代码分离使得嵌套多层的布局代码得到简化。我也是一个正在学习flutter框的码农希望我的项目能帮助到各位同学 以后我还会贡献更多有用的代码分享给大家。各位同学如果觉得文章还不错 ,麻烦给关注和star,小弟在这里谢过啦 , 也可以加我个人QQ/微信(1693891473)
项目地址 (flutter ):https://github.com/xq19930522/flutter_bosstap
服务端地址(springboot): https://github.com/xq19930522/bosstabservice
QQ 交流群:
- 点赞
- 收藏
- 关注作者
评论(0)