- 上一节,我们熟悉了自动布局(Row/Column/Stack)与两种Widget
- 本节,我们开始搭建一个
仿微信界面
的项目
。后续文章,都围绕这个项目
进行讲述
。
- 搭建项目
-
启动页
、Icon
和本地资源读取
- 开发
发现页
1. 新建项目wechat_demo
搭建项目
可参考项目创建
- 清空
main.dart
中的文件,编写代码:
highlightColor
: 去除高光(alpha
设置0)splashColor
:去除水波纹(alpha
设置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', // 安卓需要,后台切换app时展示的名称(iOS中名称与APP名称一致)
debugShowCheckedModeBanner: false, // 隐藏debug角标
home: RootPage(),
theme: ThemeData(
primaryColor: Colors.blue, // 主题色
highlightColor: Color.fromRGBO(0, 0, 0, 0), // 去除高亮色
splashColor: Color.fromRGBO(0, 0, 0, 0), // 去除水波纹
),
);
}
}
- 新建
root_page.dart
文件,创建根视图RootPage
(可变部件),
State
中创建bodys
部件数组,存放每个主栏目部件
,每个主栏目部件都是StatefulWidget
可变组件,内部都是Scaffold
部件。State
中创建items
部件数组(固定不变,使用final
修饰),存放每个栏目底部
的Item
State
中创建_currentIndex
Int变量,记录当前选择的tabbar Index
。Scaffold
设置bottomNavigationBar
,type
设置为BottomNavigationBarType.fixed
才可以显示样式。设置fixedColor
固定颜色为green
,设置onTap
点击回调事件。Scaffold
设置selectedFontSize
为12,是因为默认未选中大小
是12,这样可以去掉字体变大
动画)
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;
});
}
// 每个栏目的主页面
List<Widget> bodys = [ChatPage(), FriendsPage(), DiscoverPage(), MinePage()];
// 每个栏目的底部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: "我的")];
// 当前选中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, // 选择的默认值
items: items,
onTap: onTap, // 点击回调
selectedFontSize: 12, // 选择字体大小设置为12(因为默认大小是12,这样可以去掉变大动画)
// selectedLabelStyle: ,
),
);
}
}
- 其中
ChatPage
聊天主页内容为:(其他三个板块,目前只是更改了title
和body
的文字内容
)
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("聊天页面")),
);
}
}
-
展示样式
2. 启动页
、Icon
和本地资源读取
跨平台项目
中,APP启动页
和Icon
的设置,都需要原生
进行支持
。
本节
图片资源
链接:https://pan.baidu.com/s/1l9VYCRvBt_3phJL6XlPUfw 密码: p8wd
2.1 安卓启动页
- 使用
Android Studio
打开项目,在anroid
->app
->src
->main
->res
文件夹下,存放资源
和配置文件
。 -
安卓
图片资源,对应1倍图
、1.5倍图
、2倍图
、3倍图
、4倍图
2.1.1 设置Icon图标
- 将
两倍图
和三倍图
分别复制粘贴到xhdpi
和xxhdpi
图片文件夹中,都命名为app_icon.png
。 - 修改
配置文件
中的app名称
和app图标
2.1.2 设置启动页
- 将
启动图
粘贴到mdpi
文件夹,修改drawable
文件夹下的lauch_background.xml
文件
2.1.3 运行安卓模拟器
-
开启
并选择安卓模拟器
,debug运行
,可以看到Icon图标
和appLauch
都已生效
:
安卓
导航栏标题
默认靠左
:
设置AppBar
的centerTitle
属性为true
,将标题居中
2.2 iOS启动页
- 使用
XCode
打开iOS项目
:
2.2.1 设置Icon图标
iOS
的Icon
尺寸要求多,我们可以借助IconKit
工具(工具下载地址)一键生成。
-
生成各尺寸图
-
在Xcode工程中,
Assets.xcassets
文件夹AppIcon
设置各尺寸图标:
2.2.2 设置启动页
- 将
lauch_image.jpeg
图片拖入与LaunchScreen.storyboard
文件相同目录
下(保证每次加载
都会及时更新
),在LaunchScreen.storyboard
中,指定启动图片
为lauch_image.jpeg
:
2.2.3 运行iPhone模拟器
-
选中
并运行模拟器
,启动页
和icon
图标都已生效:
至此,
iOS
和安卓
的启动图
和Icon
都已设置完毕
- 下面,使用
Android Studio
编码,加载iOS
和安卓
共用的本地图片
2.3 Android Studio 本地图片
Flutter
跨端使用本地图片
步骤:
图片
加入images
文件夹- 声明
图片位置
AssetImage
使用图片
2.3.1 图片加入images文件夹
- 将
images
图片复制粘贴
到项目根目录
下,在pubspec.yaml
配置文件中,放开assets
注释,将所有使用到的image
图片路径进行声明
:
2.3.2 声明图片位置
-
声明图片位置
:
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;
});
}
// 每个栏目的主页面
List<Widget> bodys = [ChatPage(), FriendsPage(), DiscoverPage(), MinePage()];
// 每个栏目的底部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: "我的")
];
// 当前选中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,
// 选择的默认值
items: items,
onTap: onTap,
// 点击回调
selectedFontSize: 12, // 选择字体大小设置为12(因为默认大小是12,这样可以去掉变大动画)
// selectedLabelStyle: ,
),
);
}
}
-
展示样式:
至此,我们已掌握
跨端
本地资源
的加载
🌹 本地图片的加载,也可以将配置文件直接写成images/
,Flutter
会自动
通过名称来寻找图片
3. 开发发现页
- 发现页比较简单,部件是
ListView
,配合Cell
展示。
- UI开发
-
添加手势事件
3.1 UI开发
-
创建
pages
文件夹,将页面
都放在这里。新建discover_cell.dart
文件,
其中
discover_page
代码为:
- 创建变量
_themeColor
记录主题背景色
;appBar
导航栏设置背景色
,centerTitle
标题居中
(安卓有效),elevation
设置为0.0
,去除分割线
child
和children
区别:
child
表示一个部件
,children
表示多个部件
- 使用
ListView
布局页面,分割线使用左白 右灰
两个部件构成
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, // 安卓的导航栏标题未居中,可以设置居中
title: Text(
"朋友圈",
style: TextStyle(color: Colors.black),
),
elevation: 0.0 // 去除分割线
),
body: Container(
// child: 表示一个部件
// 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限时特惠",),
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",)
]),
),
);
}
}
使用图片资源时,一定注意先导入
images
文件夹,再在pubspec.yaml
配置文件中设置图片路径
,最后再使用图片
。
- 其中
discover_cell.dart
代码为:
- 入参有
图片名称
、标题
、子标题
、红点图片名称
四个,�可以将光标
停留在参数处
,按住option
+enter
键,自动
生成构造方法
。
其中可使用@ required
声明必传参数
,使用assert
断言做错误提示
。mainAxisAlignment
主轴的对齐方式设置为spaceBetween
,等分中间剩余空间。- 使用
三目运算符
判断是否展示
部件。
import 'package:flutter/material.dart';
class DiscoverCell extends StatelessWidget {
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
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), // 标题
],
)),
Container(
child: Row(
children: [
Text(subTitle != null ? subTitle : "", style: TextStyle(color: Colors.grey),), // 副标题
subImageName != null ? Container(child: Image(image: AssetImage(subImageName), width: 14, height: 14), margin: EdgeInsets.only(left: 8,right: 8)) : Container(), // 红点
Image(image: AssetImage('images/icon_right.png'), width: 14, height: 14) // 箭头
],
)),
],
),
);
}
}
- 成功
完成
上面预期UI
效果,但缺少点击事件
和效果
(cell触摸
时灰色
,常规
和放开
都是白色
)。
3.2 添加手势事件
- 添加手势事件,需要记录变更状态,所以需要将
StatelessWidget
不可变部件改为StatefulWidget
可变部件:
不可变部件
转可变部件
有三步
:
stful
快捷键创建可变部件
,完成命名- 将原不可变组件
build
直接拷贝给State
的build
,build
内属性调用修改为widget.属性名
进行调用- 删除原
不可变组件
即可。
- 在添加
手势部件
前,先准备一个简单的discover_child_page.dart
详情页
(后面点击cell,跳转详情页)
import 'package:flutter/material.dart';
class DiscoverChildPage extends StatelessWidget {
// 接受入参title,必传参数( 构造函数中@required 声明)
final String title;
const DiscoverChildPage ({Key key, @required this.title}) : assert(title != null, '缺少标题'), super(key: key);
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(title), // 展示导航栏标题
),
body: Center(
child: Text(title), // 文本居中展示
),
);
}
}
- 给部件
添加手势
,只需要用GestureDetector手势部件
包裹原部件
即可:
- 添加
onTap
点击、onTapCancel
取消点击、onTapDown
按下三个事件,创建独立的响应函数
。
点击
和取消点击
时,cell背景为白色,按下
时,cell背景为灰色。onTap
点击新增了路由跳转
,使用context
当前上下文的Navigator
导航器,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;
// 点击(跳转页面,恢复白色)
void onTap() {
// 路由跳转
Navigator.of(context).push(
// MaterialPageRoute 页面路由,返回build的部件
MaterialPageRoute(builder: (BuildContext context ) => DiscoverChildPage(title: widget.title))
);
setState(() => _cellColor = Colors.white );
}
// 点击取消(白色)
void onTapCancel() {
setState(() => _cellColor = Colors.white );
}
// 点击按下(灰色)
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), // 标题
],
)),
Container(
child: Row(
children: [
Text(widget.subTitle != null ? widget.subTitle : "", style: TextStyle(color: Colors.grey),), // 副标题
widget.subImageName != null ? Container(child: Image(image: AssetImage(widget.subImageName), width: 14, height: 14), margin: EdgeInsets.only(left: 8,right: 8)) : Container(), // 红点
Image(image: AssetImage('images/icon_right.png'), width: 14, height: 14) // 箭头
],
)),
],
),
),
onTap: onTap, // 点击事件
onTapCancel: onTapCancel, //点击取消
onTapDown: onTapDown, // 点击按下
);
}
}
至此,完成
发现页面
的简单开发
【快捷方式】
Android Studio
的批量修改:command
+F
搜索内容,选中Select All Occurrences
本节,我们熟悉了框架搭建
,启动页
、Icon
和资源的加载,最后完成 发现
页面的开发。
下一节,我们完成个人中心
和通讯录
页面的开发。