登錄頭像處理:
接着上一次https://www.cnblogs.com/webor2006/p/13028104.html的功能繼續往下擼,在上一次中已經處理了登錄的接口回調了,如下:
接下來則需要請求用戶信息接口了,瞅一下官網:
先來定義一下URL:
接下來調用一下:
下面請求看一下能否成功?
沒問題,接下來則來處理結果,並顯示頭像和用戶名:
好,接下來則來做一下界面刷新,一個是用戶頭像,另一個是用戶名稱,這塊邏輯就比較簡單了,加判斷如下:
@override Widget build(BuildContext context) { return ListView.separated( itemBuilder: (context, index) { if (index == 0) { //用戶頭像 return Container( color: Color(AppColors.APP_THEME), height: 150.0, child: Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: <Widget>[ GestureDetector( child: userAvatar != null ? Container(//網絡加載圖像 width: 60.0, height: 60.0, decoration: BoxDecoration( shape: BoxShape.circle, border: Border.all( color: Colors.white, width: 2.0, ), image: DecorationImage( image: NetworkImage(userAvatar), fit: BoxFit.cover, )), ) : Container(//默認加載圖像 width: 60.0, height: 60.0, decoration: BoxDecoration( shape: BoxShape.circle, border: Border.all( color: Colors.white, width: 2.0, ), image: DecorationImage( image: AssetImage( 'assets/images/ic_avatar_default.png'), fit: BoxFit.cover, )), ), onTap: () { _login(); }, ), SizedBox( //加間距 height: 10.0, ), Text( '點擊頭像登錄', style: TextStyle(color: Colors.white), ) ], ), ), ); } index -= 1; return ListTile( leading: Icon(menuIcons[index]), //左圖標 title: Text(menuTitles[index]), //中間標題 trailing: Icon(Icons.arrow_forward_ios), ); }, separatorBuilder: (context, index) { return Divider(); }, //分隔線 itemCount: menuTitles.length + 1); }
此時運行看一下:
不過這里有一個代碼有問題,就是關於默認頭像這塊,什么問題呢?先來看一下默認的頭像長啥樣?
本身就是帶白邊框的,但是我們代碼中又畫蛇添足了一下:
所以咱們可以簡化一下,直接用AssetImage來顯示既可:
好,接下來再來處理用戶名,比較簡單,改一句代碼既可:
復習一下:https://www.cnblogs.com/webor2006/p/11975291.html,
運行:
接下來再來完善一下代碼,進這個界面時則需要主動查一下是否已經有當前用戶的信息了,有則需要顯示到界面上,如下:
// 用戶基本信息 class User { String gender; String name; String location; int id; String avatar; String email; String url; User( {this.gender, this.name, this.location, this.id, this.avatar, this.email, this.url}); }
接下來看一下整體的效果:
登出處理:
接下來再做登出處理,簡單弄一個頁面既可,如下:
import 'package:flutter/material.dart'; import 'package:flutter_osc_client/common/event_bus.dart'; import 'package:flutter_osc_client/constants/constants.dart' show AppColors; import 'package:flutter_osc_client/utils/data_utils.dart'; class SettingsPage extends StatefulWidget { @override _SettingsPageState createState() => _SettingsPageState(); } class _SettingsPageState extends State<SettingsPage> { @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( elevation: 0.0, title: Text( '設置', style: TextStyle(color: Color(AppColors.APPBAR)), ), iconTheme: IconThemeData(color: Color(AppColors.APPBAR)), ), body: Center( child: FlatButton( onPressed: () { //退出登錄 DataUtils.clearLoginInfo().then((_) { eventBus.fire(LogoutEvent());//發出退出消息 Navigator.of(context).pop(); }); }, child: Text( '退出登錄', style: TextStyle(fontSize: 25.0), )), ), ); } }
其實也就是這個點設置跳轉的頁面做的退出登錄處理:
此時再來處理一下登出的消息,很簡單,直接再刷新一下界面既可:
此時運行瞅一下效果:
我的詳情:
接下來則來處理點擊頭像來查看用戶的詳細信息,這里點擊肯定得要判斷登錄狀態了,如下:
接下來則新建用戶詳情頁面:
import 'package:flutter/material.dart'; import 'package:flutter_osc_client/constants/constants.dart'; class ProfileDetailPage extends StatefulWidget { @override _ProfileDetailPageState createState() => _ProfileDetailPageState(); } class _ProfileDetailPageState extends State<ProfileDetailPage> { @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text( '用戶詳情', style: TextStyle(color: Color(AppColors.APPBAR)), ), iconTheme: IconThemeData(color: Color(AppColors.APPBAR)), ), body: Column(), ); } }
然后鏈上這個頁面:
運行看一下:
接下來則來集中實現一下用戶詳情頁面,先來看一下它長啥樣:
下面則來一行行構建布局,首先構建個頭像,這里會用到一個新的Widget:
import 'package:flutter/material.dart'; import 'package:flutter_osc_client/constants/constants.dart'; class ProfileDetailPage extends StatefulWidget { @override _ProfileDetailPageState createState() => _ProfileDetailPageState(); } class _ProfileDetailPageState extends State<ProfileDetailPage> { @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text( '用戶詳情', style: TextStyle(color: Color(AppColors.APPBAR)), ), iconTheme: IconThemeData(color: Color(AppColors.APPBAR)), ), body: Column( children: <Widget>[ InkWell( onTap: () { //TODO }, child: Container( margin: const EdgeInsets.only(left: 20.0), padding: const EdgeInsets.only(top: 10.0, bottom: 10.0, right: 20.0), child: Row( crossAxisAlignment: CrossAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.spaceBetween, children: <Widget>[ Text( '頭像', style: TextStyle(fontSize: 20.0), ), Container( width: 60.0, height: 60.0, decoration: BoxDecoration( shape: BoxShape.circle, border: Border.all( color: Colors.white, width: 2.0, ), image: DecorationImage( image: NetworkImage(""), fit: BoxFit.cover, ), ), ) ], ), ), ), ], ), ); } }
其中用戶頭像這里空着是因為得要請求一個新的接口,這里先來預覽一下效果:
其中可以看到是典型的material風格的點擊效果,其中就是使用了這個新的Widget:
接下來則來顯示一下頭像,剛才也提到了需要再請求一個接口,如下:
下面來請求一下,先來准備一下API:
然后新建一個實體,用來接收服務器所下發的信息:
// 用戶詳細信息 class UserInfo { int uid; String name; int gender; String province; String city; List<dynamic> platforms; List<dynamic> expertise; String joinTime; String lastLoginTime; String portrait; int fansCount; int favoriteCount; int followersCount; Map<String, dynamic> notice; UserInfo( {this.uid, this.name, this.gender, this.province, this.city, this.platforms, this.expertise, this.joinTime, this.lastLoginTime, this.portrait, this.fansCount, this.favoriteCount, this.followersCount, this.notice}); }
接下來則開始請求處理:
import 'dart:convert'; import 'dart:io'; import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:flutter_osc_client/constants/constants.dart'; import 'package:flutter_osc_client/models/user_info.dart'; import 'package:flutter_osc_client/utils/data_utils.dart'; import 'package:flutter_osc_client/utils/net_utils.dart'; class ProfileDetailPage extends StatefulWidget { @override _ProfileDetailPageState createState() => _ProfileDetailPageState(); } class _ProfileDetailPageState extends State<ProfileDetailPage> { UserInfo _userInfo; _getDetailInfo() { DataUtils.getAccessToken().then((accessToken) { //拼裝請求 Map<String, dynamic> params = Map<String, dynamic>(); params['dataType'] = 'json'; params['access_token'] = accessToken; NetUtils.get(AppUrls.MY_INFORMATION, params).then((data) { print('MY_INFORMATION: $data'); if (data != null && data.isNotEmpty) { Map<String, dynamic> map = json.decode(data); UserInfo userInfo = UserInfo(); userInfo.uid = map['uid']; userInfo.name = map['name']; userInfo.gender = map['gender']; userInfo.province = map['province']; userInfo.city = map['city']; userInfo.platforms = map['platforms']; userInfo.expertise = map['expertise']; userInfo.joinTime = map['joinTime']; userInfo.lastLoginTime = map['lastLoginTime']; userInfo.portrait = map['portrait']; userInfo.fansCount = map['fansCount']; userInfo.favoriteCount = map['favoriteCount']; userInfo.followersCount = map['followersCount']; userInfo.notice = map['notice']; setState(() { _userInfo = userInfo; }); } }); }); } @override void initState() { super.initState(); _getDetailInfo(); } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text( '用戶詳情', style: TextStyle(color: Color(AppColors.APPBAR)), ), iconTheme: IconThemeData(color: Color(AppColors.APPBAR)), ), body: _userInfo == null ? Center( child: CupertinoActivityIndicator(), ) : Column( children: <Widget>[ InkWell( onTap: () { //TODO }, child: Container( margin: const EdgeInsets.only(left: 20.0), padding: const EdgeInsets.only( top: 10.0, bottom: 10.0, right: 20.0), child: Row( crossAxisAlignment: CrossAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.spaceBetween, children: <Widget>[ Text( '頭像', style: TextStyle(fontSize: 20.0), ), Container( width: 60.0, height: 60.0, decoration: BoxDecoration( shape: BoxShape.circle, border: Border.all( color: Colors.white, width: 2.0, ), image: DecorationImage( image: NetworkImage(_userInfo.portrait), fit: BoxFit.cover, ), ), ) ], ), ), ), ], ), ); } }
再運行:
ok,至於其它信息條目的顯示基本寫法差不多,就把代碼貼出來了,不一一細說了,比較多,但都比較容易理解:
@override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text( '用戶詳情', style: TextStyle(color: Color(AppColors.APPBAR)), ), iconTheme: IconThemeData(color: Color(AppColors.APPBAR)), ), body: _userInfo == null ? Center( child: CupertinoActivityIndicator(), ) : Column( children: <Widget>[ InkWell( onTap: () { //TODO }, child: Container( margin: const EdgeInsets.only(left: 20.0), padding: const EdgeInsets.only( top: 10.0, bottom: 10.0, right: 20.0), child: Row( crossAxisAlignment: CrossAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.spaceBetween, children: <Widget>[ Text( '頭像', style: TextStyle(fontSize: 20.0), ), Container( width: 60.0, height: 60.0, decoration: BoxDecoration( shape: BoxShape.circle, border: Border.all( color: Colors.white, width: 2.0, ), image: DecorationImage( image: NetworkImage(_userInfo.portrait), fit: BoxFit.cover, ), ), ) ], ), ), ), Divider(), InkWell( onTap: () { //TODO }, child: Container( margin: const EdgeInsets.only(left: 20.0), padding: const EdgeInsets.only( top: 10.0, bottom: 10.0, right: 20.0), child: Row( crossAxisAlignment: CrossAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.spaceBetween, children: <Widget>[ Text( '昵稱', style: TextStyle(fontSize: 20.0), ), Text( _userInfo.name, style: TextStyle(fontSize: 20.0), ), ], ), ), ), Divider(), Container( margin: const EdgeInsets.only(left: 20.0), padding: const EdgeInsets.only( top: 10.0, bottom: 10.0, right: 20.0), child: Row( crossAxisAlignment: CrossAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.spaceBetween, children: <Widget>[ Text( '加入時間', style: TextStyle(fontSize: 20.0), ), Text( _userInfo.joinTime, // _userInfo.joinTime.split(' ')[0], style: TextStyle(fontSize: 20.0), ), ], ), ), Divider(), InkWell( onTap: () { //TODO }, child: Container( margin: const EdgeInsets.only(left: 20.0), padding: const EdgeInsets.only( top: 10.0, bottom: 10.0, right: 20.0), child: Row( crossAxisAlignment: CrossAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.spaceBetween, children: <Widget>[ Text( '所在地區', style: TextStyle(fontSize: 20.0), ), Text( _userInfo.city, style: TextStyle(fontSize: 20.0), ), ], ), ), ), Divider(), InkWell( onTap: () { //TODO }, child: Container( margin: const EdgeInsets.only(left: 20.0), padding: const EdgeInsets.only( top: 10.0, bottom: 10.0, right: 20.0), child: Row( crossAxisAlignment: CrossAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.spaceBetween, children: <Widget>[ Padding( padding: const EdgeInsets.only(right: 20.0), child: Text( '開發平台', style: TextStyle(fontSize: 20.0), ), ), Expanded( child: Text( // 'Android,C/C++,J2ME/K-Java,Python,.NET/C#', _userInfo.platforms.toString(), style: TextStyle(fontSize: 20.0), textAlign: TextAlign.right, ), ), ], ), ), ), Divider(), InkWell( onTap: () { //TODO }, child: Container( margin: const EdgeInsets.only(left: 20.0), padding: const EdgeInsets.only( top: 10.0, bottom: 10.0, right: 20.0), child: Row( crossAxisAlignment: CrossAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.spaceBetween, children: <Widget>[ Padding( padding: const EdgeInsets.only(right: 20.0), child: Text( '專長領域', style: TextStyle(fontSize: 20.0), ), ), Expanded( child: Text( // '手機軟件開發,服務器開發,軟件開發管理', _userInfo.expertise.toString(), style: TextStyle(fontSize: 20.0), textAlign: TextAlign.right, ), ), ], ), ), ), Divider(), Container( margin: const EdgeInsets.only(left: 20.0), padding: const EdgeInsets.only( top: 10.0, bottom: 10.0, right: 20.0), child: Row( crossAxisAlignment: CrossAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.spaceBetween, children: <Widget>[ Text( '粉絲數', style: TextStyle(fontSize: 20.0), ), Text( _userInfo.fansCount.toString(), style: TextStyle(fontSize: 20.0), overflow: TextOverflow.ellipsis, ), ], ), ), Divider(), Container( margin: const EdgeInsets.only(left: 20.0), padding: const EdgeInsets.only( top: 10.0, bottom: 10.0, right: 20.0), child: Row( crossAxisAlignment: CrossAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.spaceBetween, children: <Widget>[ Text( '收藏數', style: TextStyle(fontSize: 20.0), ), Text( _userInfo.favoriteCount.toString(), style: TextStyle(fontSize: 20.0), overflow: TextOverflow.ellipsis, ), ], ), ), Divider(), Container( margin: const EdgeInsets.only(left: 20.0), padding: const EdgeInsets.only( top: 10.0, bottom: 10.0, right: 20.0), child: Row( crossAxisAlignment: CrossAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.spaceBetween, children: <Widget>[ Text( '關注數', style: TextStyle(fontSize: 20.0), ), Text( _userInfo.followersCount.toString(), style: TextStyle(fontSize: 20.0), overflow: TextOverflow.ellipsis, ), ], ), ), Divider(), ], ), ); }
關於上面的代碼細節不用過多糾結,都是熟能生巧的事,練得多了自然而然也就親切了,寫得少當然感覺很懵,重點是理解,當實際自己來實現時只要有思路再到時查閱一下既可,反正我也記不住。
我的消息:
接下來則來處理這塊的點擊事件,不過這里僅以它為例來實現一下:
點擊時需要判斷是否登錄:
然后新建一個頁面:
import 'package:flutter/material.dart'; import 'package:flutter_osc_client/constants/constants.dart'; class MyMessagePage extends StatefulWidget { @override _MyMessagePageState createState() => _MyMessagePageState(); } class _MyMessagePageState extends State<MyMessagePage> { @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( elevation: 0.0, title: Text( '消息中心', style: TextStyle( color: Color(AppColors.APPBAR), ), ), iconTheme: IconThemeData( color: Color(AppColors.APPBAR), ), ), ); } }
運行看一下:
接下來則來構建消息中心頁面,它長啥樣呢?先看看:
關於這種Tab樣式界面的寫法可以參考https://www.cnblogs.com/webor2006/p/12578218.html,下面來構建一下:
import 'package:flutter/material.dart'; import 'package:flutter_osc_client/constants/constants.dart'; class MyMessagePage extends StatefulWidget { @override _MyMessagePageState createState() => _MyMessagePageState(); } class _MyMessagePageState extends State<MyMessagePage> { List<String> _tabTitles = ['@我', '評論', '私信']; @override Widget build(BuildContext context) { return DefaultTabController( length: _tabTitles.length, child: Scaffold( appBar: AppBar( elevation: 0.0, title: Text( '消息中心', style: TextStyle( color: Color(AppColors.APPBAR), ), ), iconTheme: IconThemeData( color: Color(AppColors.APPBAR), ), bottom: TabBar( tabs: _tabTitles .map((title) => Tab( text: title, )) .toList()), ), ), ); } _buildMessageList() {} }
運行:
TAB效果有了,接下來則需要構建一下TAB之后下面頁面的內容:
運行:
這里只以私信為例,前面兩個就占個位得了,這里私信又有一個新接口需要請求:
所以新建一個URL:
然后咱們來請求一下它,並根據數據來構建私信列表顯示:
import 'dart:convert'; import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:flutter_osc_client/constants/constants.dart'; import 'package:flutter_osc_client/utils/data_utils.dart'; import 'package:flutter_osc_client/utils/net_utils.dart'; class MyMessagePage extends StatefulWidget { @override _MyMessagePageState createState() => _MyMessagePageState(); } class _MyMessagePageState extends State<MyMessagePage> { List<String> _tabTitles = ['@我', '評論', '私信']; List messageList; int curPage = 1; @override Widget build(BuildContext context) { return DefaultTabController( length: _tabTitles.length, child: Scaffold( appBar: AppBar( elevation: 0.0, title: Text( '消息中心', style: TextStyle( color: Color(AppColors.APPBAR), ), ), iconTheme: IconThemeData( color: Color(AppColors.APPBAR), ), bottom: TabBar( tabs: _tabTitles .map((title) => Tab( text: title, )) .toList()), ), body: TabBarView(children: [ Center( child: Text('暫無內容'), ), Center( child: Text('暫無內容'), ), _buildMessageList(), ]), ), ); } _buildMessageList() { if (messageList == null) { //獲取私信 _getMessageList(); return Center( child: CupertinoActivityIndicator(), ); } return RefreshIndicator( child: ListView.separated( itemBuilder: (context, index) { return Padding( padding: const EdgeInsets.all(10.0), child: Row( children: <Widget>[ Image.network(messageList[index]['portrait']), SizedBox( width: 10.0, ), Expanded( child: Column( children: <Widget>[ Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: <Widget>[ Text( '${messageList[index]['sendername']}', style: TextStyle( fontSize: 16.0, fontWeight: FontWeight.bold), ), Text( '${messageList[index]['pubDate']}', style: TextStyle( fontSize: 12.0, color: Color(0xffaaaaaa)), ), ], ), Text( '${messageList[index]['content']}', overflow: TextOverflow.ellipsis, maxLines: 1, style: TextStyle(fontSize: 12.0), ), ], ), ), ], ), ); }, separatorBuilder: (context, index) { return Divider(); }, itemCount: messageList.length), onRefresh: _pullToRefresh); } Future<Null> _pullToRefresh() async { curPage = 1; _getMessageList(); return null; } void _getMessageList() { DataUtils.isLogin().then((isLogin) { if (isLogin) { DataUtils.getAccessToken().then((accessToken) { print('accessToken: $accessToken'); //拼裝請求 Map<String, dynamic> params = Map<String, dynamic>(); params['dataType'] = 'json'; params['page'] = curPage; params['pageSize'] = 10; params['access_token'] = accessToken; NetUtils.get(AppUrls.MESSAGE_LIST, params).then((data) { print('MY_INFORMATION: $data'); if (data != null && data.isNotEmpty) { Map<String, dynamic> map = json.decode(data); var _messageList = map['messageList']; setState(() { messageList = _messageList; }); } }); }); } }); } }
上面的代碼比較多,其實都比較好理解,不多說了,直接運行看看效果:
對於界面上這樣的布局:
可以看到在Flutter中寫着還是蠻麻煩的:
而且這種嵌套看着也比較頭暈,只怪自己還是比較菜,慢慢來,先能跟着做出效果來,之后再慢慢熟練,這里有上拉刷新的邏輯,那分頁下拉加載呢?關於這塊可以參考https://www.cnblogs.com/webor2006/p/12748524.html,需要自己手動添加邏輯,這里略過了。