- 上一節(jié)趋急,我們熟悉了自動(dòng)布局(Row/Column/Stack)與兩種Widget
- 本節(jié)喝峦,我們開始搭建一個(gè)
仿微信界面
的項(xiàng)目
。后續(xù)文章呜达,都圍繞這個(gè)項(xiàng)目
進(jìn)行講述
谣蠢。
- 搭建項(xiàng)目
-
啟動(dòng)頁
、Icon
和本地資源讀取
- 開發(fā)
發(fā)現(xiàn)頁
1. 新建項(xiàng)目wechat_demo
搭建項(xiàng)目
可參考項(xiàng)目創(chuàng)建
- 清空
main.dart
中的文件查近,編寫代碼:
highlightColor
: 去除高光(alpha
設(shè)置0)splashColor
:去除水波紋(alpha
設(shè)置0)
import 'package:flutter/material.dart';
import 'package:wechat_demo/root_page.dart';
void main() => runApp(App());
class App extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Wechat Demo', // 安卓需要眉踱,后臺(tái)切換app時(shí)展示的名稱(iOS中名稱與APP名稱一致)
debugShowCheckedModeBanner: false, // 隱藏debug角標(biāo)
home: RootPage(),
theme: ThemeData(
primaryColor: Colors.blue, // 主題色
highlightColor: Color.fromRGBO(0, 0, 0, 0), // 去除高亮色
splashColor: Color.fromRGBO(0, 0, 0, 0), // 去除水波紋
),
);
}
}
- 新建
root_page.dart
文件,創(chuàng)建根視圖RootPage
(可變部件)霜威,
State
中創(chuàng)建bodys
部件數(shù)組谈喳,存放每個(gè)主欄目部件
,每個(gè)主欄目部件都是StatefulWidget
可變組件,內(nèi)部都是Scaffold
部件戈泼。State
中創(chuàng)建items
部件數(shù)組(固定不變婿禽,使用final
修飾),存放每個(gè)欄目底部
的Item
State
中創(chuàng)建_currentIndex
Int變量矮冬,記錄當(dāng)前選擇的tabbar Index
谈宛。Scaffold
設(shè)置bottomNavigationBar
,type
設(shè)置為BottomNavigationBarType.fixed
才可以顯示樣式胎署。設(shè)置fixedColor
固定顏色為green
吆录,設(shè)置onTap
點(diǎn)擊回調(diào)事件。Scaffold
設(shè)置selectedFontSize
為12琼牧,是因?yàn)?code>默認(rèn)未選中大小是12恢筝,這樣可以去掉字體變大
動(dòng)畫)
import 'package:flutter/material.dart';
import 'package:wechat_demo/chat_page.dart';
import 'package:wechat_demo/discover_page.dart';
import 'package:wechat_demo/friends_page.dart';
import 'package:wechat_demo/mine_page.dart';
class RootPage extends StatefulWidget {
@override
_RootPageState createState() => _RootPageState();
}
class _RootPageState extends State<RootPage> {
Widget onTap(int index) {
setState(() {
_currentIndex = index;
});
}
// 每個(gè)欄目的主頁面
List<Widget> bodys = [ChatPage(), FriendsPage(), DiscoverPage(), MinePage()];
// 每個(gè)欄目的底部Item
final List<BottomNavigationBarItem> items = [BottomNavigationBarItem(icon: Icon(Icons.chat), label: "聊天"),
BottomNavigationBarItem(icon: Icon(Icons.bookmark), label: "通訊錄"),
BottomNavigationBarItem(icon: Icon(Icons.bookmark), label: "朋友圈"),
BottomNavigationBarItem(icon: Icon(Icons.history), label: "我的")];
// 當(dāng)前選中Index
int _currentIndex = 0;
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.blue,
body: Container(
child: bodys[_currentIndex],
),
bottomNavigationBar: BottomNavigationBar(
type: BottomNavigationBarType.fixed, // 固定大小,避免白色背景
fixedColor: Colors.green, // 固定顏色
currentIndex: _currentIndex, // 選擇的默認(rèn)值
items: items,
onTap: onTap, // 點(diǎn)擊回調(diào)
selectedFontSize: 12, // 選擇字體大小設(shè)置為12(因?yàn)槟J(rèn)大小是12巨坊,這樣可以去掉變大動(dòng)畫)
// selectedLabelStyle: ,
),
);
}
}
- 其中
ChatPage
聊天主頁內(nèi)容為:(其他三個(gè)板塊撬槽,目前只是更改了title
和body
的文字內(nèi)容
)
import 'package:flutter/material.dart';
class ChatPage extends StatefulWidget {
@override
_ChatPageState createState() => _ChatPageState();
}
class _ChatPageState extends State<ChatPage> {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("聊天"),
),
body: Center(child: Text("聊天頁面")),
);
}
}
-
展示樣式
image.png
2. 啟動(dòng)頁
、Icon
和本地資源讀取
跨平臺(tái)項(xiàng)目
中趾撵,APP啟動(dòng)頁
和Icon
的設(shè)置侄柔,都需要原生
進(jìn)行支持
共啃。
本節(jié)
圖片資源
鏈接:https://pan.baidu.com/s/1l9VYCRvBt_3phJL6XlPUfw 密碼: p8wd
2.1 安卓啟動(dòng)頁
- 使用
Android Studio
打開項(xiàng)目,在anroid
->app
->src
->main
->res
文件夾下暂题,存放資源
和配置文件
移剪。 -
安卓
圖片資源,對應(yīng)1倍圖
薪者、1.5倍圖
纵苛、2倍圖
、3倍圖
言津、4倍圖
image.png
2.1.1 設(shè)置Icon圖標(biāo)
- 將
兩倍圖
和三倍圖
分別復(fù)制粘貼到xhdpi
和xxhdpi
圖片文件夾中攻人,都命名為app_icon.png
。 - 修改
配置文件
中的app名稱
和app圖標(biāo)
image.png
2.1.2 設(shè)置啟動(dòng)頁
- 將
啟動(dòng)圖
粘貼到mdpi
文件夾悬槽,修改drawable
文件夾下的lauch_background.xml
文件
image.png
2.1.3 運(yùn)行安卓模擬器
-
開啟
并選擇安卓模擬器
怀吻,debug運(yùn)行
,可以看到Icon圖標(biāo)
和appLauch
都已生效
:
image.png
安卓
導(dǎo)航欄標(biāo)題
默認(rèn)靠左
:
設(shè)置AppBar
的centerTitle
屬性為true
,將標(biāo)題居中
image.png
2.2 iOS啟動(dòng)頁
- 使用
XCode
打開iOS項(xiàng)目
:
image.png
2.2.1 設(shè)置Icon圖標(biāo)
iOS
的Icon
尺寸要求多陷谱,我們可以借助IconKit
工具(工具下載地址)一鍵生成烙博。
-
生成各尺寸圖
image.png
image.png -
在Xcode工程中,
Assets.xcassets
文件夾AppIcon
設(shè)置各尺寸圖標(biāo):
image.png
2.2.2 設(shè)置啟動(dòng)頁
- 將
lauch_image.jpeg
圖片拖入與LaunchScreen.storyboard
文件相同目錄
下(保證每次加載
都會(huì)及時(shí)更新
)烟逊,在LaunchScreen.storyboard
中渣窜,指定啟動(dòng)圖片
為lauch_image.jpeg
:
2.2.3 運(yùn)行iPhone模擬器
-
選中
并運(yùn)行模擬器
,啟動(dòng)頁
和icon
圖標(biāo)都已生效:
image.png
至此,
iOS
和安卓
的啟動(dòng)圖
和Icon
都已設(shè)置完畢
- 下面宪躯,使用
Android Studio
編碼乔宿,加載iOS
和安卓
共用的本地圖片
2.3 Android Studio 本地圖片
Flutter
跨端使用本地圖片
步驟:
圖片
加入images
文件夾- 聲明
圖片位置
AssetImage
使用圖片
2.3.1 圖片加入images文件夾
- 將
images
圖片復(fù)制粘貼
到項(xiàng)目根目錄
下,在pubspec.yaml
配置文件中访雪,放開assets
注釋详瑞,將所有使用到的image
圖片路徑進(jìn)行聲明
:
image.png
2.3.2 聲明圖片位置
-
聲明圖片位置
:
image.png
2.3.3 AssetImage使用圖片
- 我們將
BottomNavigationBarItem
的圖片修改為我們的本地圖片
import 'package:flutter/material.dart';
import 'package:wechat_demo/chat_page.dart';
import 'package:wechat_demo/discover_page.dart';
import 'package:wechat_demo/friends_page.dart';
import 'package:wechat_demo/mine_page.dart';
class RootPage extends StatefulWidget {
@override
_RootPageState createState() => _RootPageState();
}
class _RootPageState extends State<RootPage> {
Widget onTap(int index) {
setState(() {
_currentIndex = index;
});
}
// 每個(gè)欄目的主頁面
List<Widget> bodys = [ChatPage(), FriendsPage(), DiscoverPage(), MinePage()];
// 每個(gè)欄目的底部Item(使用AssetImage加載本地圖片)
final List<BottomNavigationBarItem> items = [
BottomNavigationBarItem(
icon: Image(image: AssetImage('images/tabbar_chat.png'), width: 20),
activeIcon:
Image(image: AssetImage('images/tabbar_chat_hl.png'), width: 20),
label: "聊天"),
BottomNavigationBarItem(
icon: Image(image: AssetImage('images/tabbar_friends.png'), width: 20),
activeIcon:
Image(image: AssetImage('images/tabbar_friends_hl.png'), width: 20),
label: "通訊錄"),
BottomNavigationBarItem(
icon: Image(image: AssetImage('images/tabbar_discover.png'), width: 20),
activeIcon: Image(
image: AssetImage('images/tabbar_discover_hl.png'), width: 20),
label: "朋友圈"),
BottomNavigationBarItem(
icon: Image(image: AssetImage('images/tabbar_mine.png'), width: 20),
activeIcon:
Image(image: AssetImage('images/tabbar_mine_hl.png'), width: 20),
label: "我的")
];
// 當(dāng)前選中Index
int _currentIndex = 0;
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.blue,
body: Container(
child: bodys[_currentIndex],
),
bottomNavigationBar: BottomNavigationBar(
type: BottomNavigationBarType.fixed,
// 固定大小,避免白色背景
fixedColor: Colors.green,
// 固定顏色
currentIndex: _currentIndex,
// 選擇的默認(rèn)值
items: items,
onTap: onTap,
// 點(diǎn)擊回調(diào)
selectedFontSize: 12, // 選擇字體大小設(shè)置為12(因?yàn)槟J(rèn)大小是12臣缀,這樣可以去掉變大動(dòng)畫)
// selectedLabelStyle: ,
),
);
}
}
-
展示樣式:
image.png
至此坝橡,我們已掌握
跨端
本地資源
的加載
?? 本地圖片的加載,也可以將配置文件直接寫成images/
精置,Flutter
會(huì)自動(dòng)
通過名稱來尋找圖片
3. 開發(fā)發(fā)現(xiàn)頁
- 發(fā)現(xiàn)頁比較簡單计寇,部件是
ListView
,配合Cell
展示脂倦。
- UI開發(fā)
-
添加手勢事件
image.png
3.1 UI開發(fā)
-
創(chuàng)建
pages
文件夾番宁,將頁面
都放在這里。新建discover_cell.dart
文件赖阻,
image.png 其中
discover_page
代碼為:
- 創(chuàng)建變量
_themeColor
記錄主題背景色
;appBar
導(dǎo)航欄設(shè)置背景色
蝶押,centerTitle
標(biāo)題居中
(安卓有效),elevation
設(shè)置為0.0
,去除分割線
child
和children
區(qū)別:
child
表示一個(gè)部件
火欧,children
表示多個(gè)部件
- 使用
ListView
布局頁面棋电,分割線使用左白 右灰
兩個(gè)部件構(gòu)成
import 'package:flutter/material.dart';
import 'package:wechat_demo/pages/discover_cell.dart';
class DiscoverPage extends StatefulWidget {
Color _themeColor = Color.fromRGBO(220, 220, 220, 1.0);
@override
_DiscoverPageState createState() => _DiscoverPageState();
}
class _DiscoverPageState extends State<DiscoverPage> {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
backgroundColor: widget._themeColor,
centerTitle: true, // 安卓的導(dǎo)航欄標(biāo)題未居中茎截,可以設(shè)置居中
title: Text(
"朋友圈",
style: TextStyle(color: Colors.black),
),
elevation: 0.0 // 去除分割線
),
body: Container(
// child: 表示一個(gè)部件
// children: 表示一堆部件
color: widget._themeColor,
child: ListView(children: <Widget>[
DiscoverCell(title: "朋友圈", imageName: "images/朋友圈.png",),
SizedBox(height: 8),
DiscoverCell(title: "掃一掃", imageName: "images/掃一掃2.png",),
Container(height: 1, child: Row(children: [Container(width: 40, color: Colors.white), Container(color: widget._themeColor)]),),
DiscoverCell(title: "搖一搖", imageName: "images/搖一搖.png",),
SizedBox(height: 8),
DiscoverCell(title: "看一看", imageName: "images/看一看icon.png",),
Container(height: 1, child: Row(children: [Container(width: 40, color: Colors.white), Container(color: widget._themeColor)]),),
DiscoverCell(title: "搜一搜", imageName: "images/搜一搜3.png",),
SizedBox(height: 8),
DiscoverCell(title: "附近的人", imageName: "images/附近的人icon.png",),
SizedBox(height: 8),
DiscoverCell(title: "購物", imageName: "images/購物.png", subImageName: "images/badge.png", subTitle: "618限時(shí)特惠",),
Container(height: 1, child: Row(children: [Container(width: 40, color: Colors.white), Container(color: widget._themeColor)]),),
DiscoverCell(title: "游戲", imageName: "images/游戲2.png",),
SizedBox(height: 8),
DiscoverCell(title: "小程序", imageName: "images/小程序.png",)
]),
),
);
}
}
使用圖片資源時(shí),一定注意先導(dǎo)入
images
文件夾离陶,再在pubspec.yaml
配置文件中設(shè)置圖片路徑
稼虎,最后再使用圖片
。
image.png
- 其中
discover_cell.dart
代碼為:
- 入?yún)⒂?code>圖片名稱、
標(biāo)題
、子標(biāo)題
剑刑、紅點(diǎn)圖片名稱
四個(gè)哮洽,?可以將光標(biāo)
停留在參數(shù)處
,按住option
+enter
鍵杉适,自動(dòng)
生成構(gòu)造方法
谎倔。
其中可使用@ required
聲明必傳參數(shù)
,使用assert
斷言做錯(cuò)誤提示
猿推。mainAxisAlignment
主軸的對齊方式設(shè)置為spaceBetween
片习,等分中間剩余空間。- 使用
三目運(yùn)算符
判斷是否展示
部件蹬叭。
import 'package:flutter/material.dart';
class DiscoverCell extends StatelessWidget {
final String imageName; // 圖片名稱
final String title; // 標(biāo)題
final String subTitle; // 子標(biāo)題
final String subImageName; //紅點(diǎn)圖片名稱
const DiscoverCell(
{Key key,
@required this.imageName, // @required 必傳
@required this.title, // @required 必傳
this.subTitle,
this.subImageName})
: assert(imageName != null, 'imageName為空'),
assert(title != null, 'title為空'),
super(key: key);
@override
Widget build(BuildContext context) {
return Container(
color: Colors.white,
height: 54,
padding: EdgeInsets.all(10),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween, // 等分中間剩余的空間
children: [
Container(
child: Row(
children: [
Image(image: AssetImage(imageName), width: 20, height: 20), // 圖片
SizedBox(width: 15), // 間距
Text(title), // 標(biāo)題
],
)),
Container(
child: Row(
children: [
Text(subTitle != null ? subTitle : "", style: TextStyle(color: Colors.grey),), // 副標(biāo)題
subImageName != null ? Container(child: Image(image: AssetImage(subImageName), width: 14, height: 14), margin: EdgeInsets.only(left: 8,right: 8)) : Container(), // 紅點(diǎn)
Image(image: AssetImage('images/icon_right.png'), width: 14, height: 14) // 箭頭
],
)),
],
),
);
}
}
- 成功
完成
上面預(yù)期UI
效果藕咏,但缺少點(diǎn)擊事件
和效果
(cell觸摸
時(shí)灰色
,常規(guī)
和放開
都是白色
)秽五。
3.2 添加手勢事件
- 添加手勢事件孽查,需要記錄變更狀態(tài),所以需要將
StatelessWidget
不可變部件改為StatefulWidget
可變部件:
不可變部件
轉(zhuǎn)可變部件
有三步
:
stful
快捷鍵創(chuàng)建可變部件
坦喘,完成命名- 將原不可變組件
build
直接拷貝給State
的build
盲再,build
內(nèi)屬性調(diào)用修改為widget.屬性名
進(jìn)行調(diào)用- 刪除原
不可變組件
即可。
- 在添加
手勢部件
前瓣铣,先準(zhǔn)備一個(gè)簡單的discover_child_page.dart
詳情頁
(后面點(diǎn)擊cell答朋,跳轉(zhuǎn)詳情頁)
import 'package:flutter/material.dart';
class DiscoverChildPage extends StatelessWidget {
// 接受入?yún)itle,必傳參數(shù)( 構(gòu)造函數(shù)中@required 聲明)
final String title;
const DiscoverChildPage ({Key key, @required this.title}) : assert(title != null, '缺少標(biāo)題'), super(key: key);
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(title), // 展示導(dǎo)航欄標(biāo)題
),
body: Center(
child: Text(title), // 文本居中展示
),
);
}
}
- 給部件
添加手勢
,只需要用GestureDetector手勢部件
包裹原部件
即可:
- 添加
onTap
點(diǎn)擊棠笑、onTapCancel
取消點(diǎn)擊梦碗、onTapDown
按下三個(gè)事件,創(chuàng)建獨(dú)立的響應(yīng)函數(shù)
腐晾。
點(diǎn)擊
和取消點(diǎn)擊
時(shí)叉弦,cell背景為白色,按下
時(shí)藻糖,cell背景為灰色淹冰。onTap
點(diǎn)擊新增了路由跳轉(zhuǎn)
,使用context
當(dāng)前上下文的Navigator
導(dǎo)航器巨柒,push入棧Material
頁面樱拴,返回新頁面build
的部件
柠衍。
import 'package:flutter/material.dart';
import 'discover_child_page.dart';
class DiscoverCell extends StatefulWidget {
final String imageName;
final String title;
final String subTitle;
final String subImageName;
const DiscoverCell(
{Key key,
@required this.imageName, // @required 必傳
@required this.title, // @required 必傳
this.subTitle,
this.subImageName})
: assert(imageName != null, 'imageName為空'),
assert(title != null, 'title為空'),
super(key: key);
@override
_DiscoverCellState createState() => _DiscoverCellState();
}
class _DiscoverCellState extends State<DiscoverCell> {
// 私有cell顏色屬性
Color _cellColor = Colors.white;
// 點(diǎn)擊(跳轉(zhuǎn)頁面,恢復(fù)白色)
void onTap() {
// 路由跳轉(zhuǎn)
Navigator.of(context).push(
// MaterialPageRoute 頁面路由晶乔,返回build的部件
MaterialPageRoute(builder: (BuildContext context ) => DiscoverChildPage(title: widget.title))
);
setState(() => _cellColor = Colors.white );
}
// 點(diǎn)擊取消(白色)
void onTapCancel() {
setState(() => _cellColor = Colors.white );
}
// 點(diǎn)擊按下(灰色)
void onTapDown(TapDownDetails details) {
setState(() => _cellColor = Color.fromRGBO(220, 220, 220, 1.0));
}
@override
Widget build(BuildContext context) {
return GestureDetector(
child: Container(
color: _cellColor,
height: 54,
padding: EdgeInsets.all(10),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Container(
child: Row(
children: [
Image(image: AssetImage(widget.imageName), width: 20, height: 20), // 圖片
SizedBox(width: 15), // 間距
Text(widget.title), // 標(biāo)題
],
)),
Container(
child: Row(
children: [
Text(widget.subTitle != null ? widget.subTitle : "", style: TextStyle(color: Colors.grey),), // 副標(biāo)題
widget.subImageName != null ? Container(child: Image(image: AssetImage(widget.subImageName), width: 14, height: 14), margin: EdgeInsets.only(left: 8,right: 8)) : Container(), // 紅點(diǎn)
Image(image: AssetImage('images/icon_right.png'), width: 14, height: 14) // 箭頭
],
)),
],
),
),
onTap: onTap, // 點(diǎn)擊事件
onTapCancel: onTapCancel, //點(diǎn)擊取消
onTapDown: onTapDown, // 點(diǎn)擊按下
);
}
}
至此珍坊,完成
發(fā)現(xiàn)頁面
的簡單開發(fā)
【快捷方式】
Android Studio
的批量修改:command
+F
搜索內(nèi)容,選中Select All Occurrences
image.png
本節(jié)正罢,我們熟悉了框架搭建
阵漏,啟動(dòng)頁
、Icon
和資源的加載翻具,最后完成 發(fā)現(xiàn)
頁面的開發(fā)履怯。
下一節(jié),我們完成個(gè)人中心
和通訊錄
頁面的開發(fā)裆泳。