创建仓库链接
创建接口:
mock.js语法链接;
返回接口内容示例:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-sCDD82WX-1583233191829)(https://upload-images.jianshu.io/upload_images/6266734-348eda659132dc2a.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)]
!(img-h95FEqh4-1583233191830)(https://upload-images.jianshu.io/upload_images/6266734-5d6333b4d07fb546.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/620)]
聊天界面导航栏右上角有一个+更多按钮,点击这个按钮的时候弹出一个弹窗:
appBar: AppBar(
centerTitle: true,
title: Text('微信'),
actions: [
Container(
margin: EdgeInsets.only(right: 15),
child: PopupMenuButton(
offset: Offset(0, 60),
child: Image(image: AssetImage('images/圆加.png'),width: 25,),
itemBuilder: _PopMenuItemBuild,
),
)
],
),
//itemBuilder单独写的方法,返回值是List >
List > _PopMenuItemBuild(BuildContext context){
return >[
_CreatPopMenuBuildItem('images/发起群聊.png', '发起群聊'),
_CreatPopMenuBuildItem('images/添加朋友.png', '添加朋友'),
_CreatPopMenuBuildItem('images/扫一扫1.png', '扫一扫'),
_CreatPopMenuBuildItem('images/收付款.png', '收付款'),
];
}
//创建每一个item分出来的公共方法,传入一个imageName和一个title返回一个PopupMenuItem
PopupMenuItem _CreatPopMenuBuildItem(String imageName, String title){
return PopupMenuItem(child: Row(
children: [
Image(image: AssetImage(imageName),width: 25,),
SizedBox(width: 20),
Text(title,style: TextStyle(color: Colors.white),),
],
),);
}
在flutter中,对于右上角的按钮,以及点击弹出的弹窗,系统提供了一个按钮:
/// See also:
///
/// * [PopupMenuItem], a popup menu entry for a single value.
/// * [PopupMenuDivider], a popup menu entry that is just a horizontal line.
/// * [CheckedPopupMenuItem], a popup menu item with a checkmark.
/// * [showMenu], a method to dynamically show a popup menu at a given location.
class PopupMenuButton extends StatefulWidget {
/// Creates a button that shows a popup menu.
///
/// The [itemBuilder] argument must not be null.
const PopupMenuButton({
Key key,
@required this.itemBuilder,
this.initialValue,
this.onSelected,
this.onCanceled,
this.tooltip,
this.elevation,
this.padding = const EdgeInsets.all(8.0),
this.child,
this.icon,
this.offset = Offset.zero,
this.enabled = true,
this.shape,
this.color,
this.captureInheritedThemes = true,
}) : assert(itemBuilder != null),
通过创建的网络假数据接口可以请求到网络数据,网络请求到的数据的可是是Json格式的,需要转换成Map,然后转换成对应的模型.用于ListView的创建;
class _WeChatPageState extends State with AutomaticKeepAliveClientMixin {
List _datas = [];
bool _cancelConnect = false;
@override
// TODO: implement wantKeepAlive
bool get wantKeepAlive => true;
@override
void initState() {
// TODO: implement initState
super.initState();
getDatas().then((List datas) {
if(!_cancelConnect){
setState(() {
_datas = datas;
});
}
}).catchError((error) {
print(error);
}).whenComplete(() {
print("完毕");
}).timeout(Duration(seconds: 10)).catchError(
(timeOutError){
_cancelConnect = true;
print('${timeOutError}');
}
);
// print('来了');
}
Future> getDatas() async {
_cancelConnect = false;
final response = await http
.get('http://rap2.taobao.org:38080/app/mock/245766/api/chat/list');
if (response.statusCode == 200) {
var respones_Body = json.decode(response.body);
List chat_list = respones_Body['chat_list']
.map((item) => Chat.formJson(item))
.toList();
return chat_list;
} else {
throw Exception('stateCode=${response.statusCode}');
}
print(response.statusCode);
}
@override
Widget build(BuildContext context) {
super.build(context);
return Scaffold(
appBar: AppBar(
centerTitle: true,
title: Text('微信'),
actions: [
Container(
margin: EdgeInsets.only(right: 15),
child: PopupMenuButton(
offset: Offset(0, 60),
child: Image(
image: AssetImage('images/圆加.png'),
width: 25,
),
itemBuilder: _PopMenuItemBuild,
),
)
],
),
body: Container(
child: _datas.length == 0
? Center(
child: Text("loading"),
)
: ListView.builder(
itemCount: _datas.length,
itemBuilder: (BuildContext context, int index) {
return ListTile(
leading: Container(
height: 45,
width: 45,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(8),
image: DecorationImage(
fit: BoxFit.fitHeight,
image: NetworkImage(_datas[index].imageUrl))),
),
title: Text(_datas[index].name),
subtitle: Container(child: Text(_datas[index].message),height: 20,),
);
}),
));
}
}
里面有用到一个factory工厂方法来实现jsonMap转模型,在外部可以直接调用这个工厂方法传入有一个Map返回一个Chat模型.
class Chat {
final String name;
final String message;
final String imageUrl;
Chat({this.name, this.message,this.imageUrl});
factory Chat.formJson(Map json) {
return Chat(
name: json['user_name'],
message: json['message'],
imageUrl: json['image_url'],
);
}
}
首先需要导入头文件;
示例代码如下:
import 'dart:convert';
_MapAndJson() {
var TestMap = {
'name': 'Jack',
'message': 'today is nice',
};
//map转json使用encode
var mapJson = json.encode(TestMap);
print(mapJson);
//json转map使用decode
var jsonMap = json.decode(mapJson);
print(jsonMap);
}
为了不每次进这个页面都刷新页面的状态,需要保持这个页面的状态,这时候需要用到dart中的混合MixIn,类似于iOS中的分类,就是可以用其他class的方法,需要添加如下操作:
class _WeChatPageState extends State with AutomaticKeepAliveClientMixin {
@override
// TODO: implement wantKeepAlive
bool get wantKeepAlive => true;
@override
Widget build(BuildContext context) {
super.build(context);
但是在page.dart中需改了以后,发现点击还是会去跟新,原因是在rootpage中,body返回的是每个创建的page,其他的page会被销毁,会重新渲染,所以保持部件的状态没有效果,所以在rootPage中需要更改控制body的方式,使用PageViewController专门来控制tabbarpage的切换.
class PageController extends ScrollController {
/// Creates a page controller.
///
/// The [initialPage], [keepPage], and [viewportFraction] arguments must not be null.
PageController({
this.initialPage = 0,
this.keepPage = true,
this.viewportFraction = 1.0,
}) : assert(initialPage != null),
assert(keepPage != null),
assert(viewportFraction != null),
assert(viewportFraction > 0.0);
/// The page to show when first creating the [PageView].
final int initialPage;
/// Save the current [page] with [PageStorage] and restore it if
/// this controller's scrollable is recreated.
///
/// If this property is set to false, the current [page] is never saved
/// and [initialPage] is always used to initialize the scroll offset.
/// If true (the default), the initial page is used the first time the
/// controller's scrollable is created, since there's isn't a page to
/// restore yet. Subsequently the saved page is restored and
/// [initialPage] is ignored.
///
/// See also:
///
/// * [PageStorageKey], which should be used when more than one
/// scrollable appears in the same route, to distinguish the [PageStorage]
/// locations used to save scroll offsets.
final bool keepPage;
PageViewController的使用需要先创建一个实例对象,initialPage=0是选中的第0个page;然后在root_Page中body传入PageView,PageView的controller传入之前创建好的PageViewController对象;
class _RootPageState extends State {
int _cuttentIndex = 0;
final PageController _pageController = PageController(
initialPage: 0,
);
@override
Widget build(BuildContext context) {
return Container(
child: Scaffold(
bottomNavigationBar: BottomNavigationBar(
type: BottomNavigationBarType.fixed,
fixedColor: Colors.greenAccent,
currentIndex: _cuttentIndex,
selectedFontSize: 12.0,
items: [
BottomNavigationBarItem(
icon: Image(
height: 20,
width: 20,
image: AssetImage('images/tabbar_chat.png')),
activeIcon: Image(
height: 20,
width: 20,
image: AssetImage('images/tabbar_chat_hl.png')),
title: Text('微信')),
BottomNavigationBarItem(
icon: Image(
height: 20,
width: 20,
image: AssetImage('images/tabbar_friends.png')),
activeIcon: Image(
height: 20,
width: 20,
image: AssetImage('images/tabbar_friends_hl.png')),
title: Text('通讯录')),
BottomNavigationBarItem(
icon: Image(
height: 20,
width: 20,
image: AssetImage('images/tabbar_discover.png')),
activeIcon: Image(
height: 20,
width: 20,
image: AssetImage('images/tabbar_discover_hl.png')),
title: Text('发现')),
BottomNavigationBarItem(
icon: Image(
height: 20,
width: 20,
image: AssetImage('images/tabbar_mine.png')),
activeIcon: Image(
height: 20,
width: 20,
image: AssetImage('images/tabbar_mine_hl.png')),
title: Text('我的')),
],
onTap: (int index) {
_cuttentIndex = index;
setState(() {});
_pageController.jumpToPage(index);
},
),
body: PageView(
controller: _pageController,
onPageChanged: (int index){//tabbar页面滚动回调,更改index刷新tabbar选中的item
_cuttentIndex = index;
setState(() {});
},
// physics: NeverScrollableScrollPhysics(),//禁止tabbar页面滚动
children: [
WeChatPage(),
FriendsPage(),
FindPage(),
MinePage()
],
),
),
);
}
}
和PageViewController配合使用的有一个PageView,
效果:这次的界面搭建出来的实现效果比较粗糙,主要是为了学习网络请求的逻辑流程,Json和模型的转换,理解弄清楚了这些界面的搭建细节可以修改.
网络请求是学会Flutter很关键的一点,熟练的运用网络请求和处理数据,那么项目困难已经解决了的一大半.
原文链接