从0开始写一个基于Flutter的开源中国客户端(3)——初识Flutter & 常用的Widgets

MaterialApp和Scaffold是Flutter提供的两个Widget,其中:

  • MaterialApp是一个方便的Widget,它封装了应用程序实现Material Design所需要的一些Widget。(参考
  • Scaffold组件是Material Design布局结构的基本实现。此类提供了用于显示drawer、snackbar和底部sheet的API。(参考

基于Flutter的开源中国客户端App中,我也使用到了MaterialApp和Scaffold两个组件,下面是部分代码:

@override
Widget build(BuildContext context) {
initData();
return new MaterialApp(
theme: new ThemeData(
// 设置主题颜色
primaryColor: const Color(0xFF63CA6C)
),
home: new Scaffold(
// 设置App顶部的AppBar
appBar: new AppBar(
// AppBar的标题
title: new Text(appBarTitles[_tabIndex],
// 标题文本的颜色
style: new TextStyle(color: Colors.white)),
// AppBar上的图标的颜色
iconTheme: new IconThemeData(color: Colors.white)
),
body: _body,
// 页面底部的导航栏
bottomNavigationBar: new CupertinoTabBar(
items: [
new BottomNavigationBarItem(
icon: getTabIcon(0),
title: getTabTitle(0)),
new BottomNavigationBarItem(
icon: getTabIcon(1),
title: getTabTitle(1)),
new BottomNavigationBarItem(
icon: getTabIcon(2),
title: getTabTitle(2)),
new BottomNavigationBarItem(
icon: getTabIcon(3),
title: getTabTitle(3)),
],
currentIndex: _tabIndex,
// 底部Tab的点击事件处理
onTap: (index) {
setState((){
_tabIndex = index;
});
},
),
// 侧滑菜单,这里的MyDrawer是自定义的Widget
drawer: new MyDrawer(),
),
);
}

Text组件

Text组件是非常常用的组件,任何需要显示文本的地方基本都会用到。通过查看Text类的源码,可以发现Text是一个无状态的组件,下面的代码演示了如何修改Text组件的字号、颜色,给字体加粗、设置下划线、设置斜体等:

import ‘package:flutter/material.dart’;

void main() => runApp(new MaterialApp(
title: “Text Demo”,
home: new Scaffold(
appBar: new AppBar(
title: new Text(“Text Demo”),
),
body: new Center(
child: new Text(
“Hello Flutter”,
style: new TextStyle(
color: Colors.red, // 或者用这种写法:const Color(0xFF6699FF) 必须使用AARRGGBB
fontSize: 20.0, // 字号
fontWeight: FontWeight.bold, // 字体加粗
fontStyle: FontStyle.italic, // 斜体
decoration: new TextDecoration.combine([TextDecoration.underline]) // 文本加下划线
),
),
),
),
));

注意

  1. MaterialApp的title参数是字符串类型,而AppBar的title参数是一个Text组件类型。
  2. 开发基于Flutter的开源中国客户端时,Flutter还是beta版本,导致在设置中文文本的某些样式时不起作用,比如字体加粗,斜体等。目前的Flutter Preview版本,该问题好像已经修复了。

TextField组件

TextFiled组件用于文本的输入,示例代码如下:

class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return new MaterialApp(
title: “Test”,
home: new Scaffold(
appBar: new AppBar(
title: new Text(“Test”)
),
body: new Padding(
padding: const EdgeInsets.all(8.0),
child: new TextField(
maxLines: 8, // 设置输入框显示的最大行数(不是可输入的最大行数)
maxLength: 30, // 设置输入框中最多可输入的字符数
decoration: new InputDecoration( // 给输入框添加样式
hintText: “Input something…”, // 输入框中placeholder文本
border: new OutlineInputBorder( // 输入框的边框
borderRadius: const BorderRadius.all(Radius.circular(1.0))
)
),
)
)
),
);
}
}

在模拟器中运行界面如下图:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

InkWell和GestureDetector

这两个组件放到一起说,是因为在处理组件的点击事件时,会经常用到它们。 比如某个列表的item的点击事件,某个图标的点击事件等等。Flutter有专门设计MaterialDesign风格的按钮,但是更多时候我们希望自定义按钮样式或者为某个组件添加点击事件,所以在处理点击事件时,最常见的做法是,用InkWell或者GestureDetector将某个组件包起来。

InkWell的使用方法如下:

new InkWell(
child: new Text(“Click me!”),
onTap: () {
// 单击
},
onDoubleTap: () {
// 双击
},
onLongPress: () {
// 长按
}
);

GestureDetector用法与InkWell类似,不过GestureDetector有更多处理手势的方法,这里暂时不做介绍(其实我也用得不多)。

按钮

Flutter提供了几种类型的按钮组件:RaisedButton FloatingActionButton FlatButton IconButton PopupMenuButton,下面用一段代码说明这几种按钮的用法:

import ‘package:flutter/material.dart’;

main() {
runApp(new MyApp());
}

enum WhyFarther { harder, smarter, selfStarter, tradingCharter }

class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return new MaterialApp(
title: ‘Test’,
home: new Scaffold(
appBar: new AppBar(
title: new Text(‘Test’)
),
body: new Column(
children: [
new RaisedButton(
child: new Text(“Raised Button”),
onPressed: (){},
),
new FloatingActionButton(
child: new Icon(Icons.add),
onPressed: (){},
),
new FlatButton(
onPressed: (){},
child: new Text(“Flat Button”)
),
new IconButton(
icon: new Icon(Icons.list),
onPressed: (){}
),
new PopupMenuButton(
onSelected: (WhyFarther result) {},
itemBuilder: (BuildContext context) => <PopupMenuEntry>[
const PopupMenuItem(
value: WhyFarther.harder,
child: const Text(‘Working a lot harder’),
),
const PopupMenuItem(
value: WhyFarther.smarter,
child: const Text(‘Being a lot smarter’),
),
const PopupMenuItem(
value: WhyFarther.selfStarter,
child: const Text(‘Being a self-starter’),
),
const PopupMenuItem(
value: WhyFarther.tradingCharter,
child: const Text(‘Placed in charge of trading charter’),
),
],
)
],
)
)
);
}
}

在模拟器中上面的代码运行效果如下图所示:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传 外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

Dialog组件

Flutter提供了两种类型的对话框:SimpleDialog和AlertDialog。SimpleDialog是一个可以显示附加的提示或操作的简单对话框,AlertDialog则是一个会中断用户操作的对话框,需要用户确认的对话框,下面用代码来说明其用法:

import ‘package:flutter/material.dart’;

main() {
runApp(new MyApp());
}

class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return new MaterialApp(
title: ‘Test’,
home: new Scaffold(
appBar: new AppBar(
title: new Text(‘Test’)
),
// body: new MyAlertDialogView()
body: new MySimpleDialogView(),
),
);
}
}

class MyAlertDialogView extends StatelessWidget {
@override
Widget build(BuildContext context) {
return new RaisedButton(
child: new Text(‘显示AlertDialog’),
onPressed: () {
showDialog(
context: context,
barrierDismissible: false, // 不能点击对话框外关闭对话框,必须点击按钮关闭
builder: (BuildContext context) {
return new AlertDialog(
title: new Text(‘提示’),
content: new Text(‘微软重申Windows 7将在2020年1月到达支持终点,公司希望利用这个机会说服用户在最新更新发布之前升级到Windows 10。’),
actions: [
new FlatButton(
child: new Text(‘明白了’),
onPressed: () {
Navigator.of(context).pop();
},
),
],
);
},
);
},
);
}
}

class MySimpleDialogView extends StatelessWidget {
@override
Widget build(BuildContext context) {
return new RaisedButton(
child: new Text(‘显示SimpleDialog’),
onPressed: () {
showDialog(
context: context,
builder: (BuildContext ctx) {
return new SimpleDialog(
title: new Text(‘这是SimpleDialog’),
children: [
new SimpleDialogOption(
onPressed: () { Navigator.pop(context); },
child: const Text(‘确定’),
),
new SimpleDialogOption(
onPressed: () { Navigator.pop(context); },
child: const Text(‘取消’),
),
],
);
}
);
},
);
}
}

上面的代码分别展示了SimpleDialog和AlertDialog的基本用法。需要注意的是,这里并没有直接将按钮和显示对话框的逻辑写到MyApp类中,而是分两个StatelessWidget来写的,如果你直接将按钮及显示对话框的逻辑写到MyApp的build方法里,是会报错的,具体报错信息为:

Navigator operation requested with a context that does not include a Navigator.

意思是导航操作需要一个不包含Navigator的上下文对象,而如果我们将showDialog的逻辑写到MyApp的build方法中时,使用的是MaterialApp的上下文对象,这个上下文对象是包含Navigator的,所以就会报错。上面的代码在模拟器中运行效果如下图:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传 外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

Image组件

Image组件用于显示一张图片,可以加载本地(项目中或手机存储中)或网络图片。

加载本地图片

使用下面的方法加载一张项目中的图片:

new Image.asset(path, width: 20.0, height: 20.0, fit: BoxFit.cover)

其中path是项目中的图片目录。

加载项目中的图片一定要注意编辑pubspec.yaml文件:

假设当前我们在跟lib/同级的目录下创建了images/目录,在images/目录下存放了若干图片供项目使用,那么一定要记得在项目根目录下(也是跟images/同级的目录)编辑pubspec.yaml文件,打开pubspec.yaml文件,默认情况下assets是被注释了的,这里我们要取消注释assets并添加images/目录下的每个图片的路径,如下图所示:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

在上图中我们配置了文件路径images/ic_nav_news_normal.png,所以可以用下面的代码来加载图片了:

new Image.asset(‘images/ic_nav_news_normal.png’, width: 20.0, height: 20.0, fit: BoxFit.cover)

widthheight是图片长宽,为double类型,如果你传整型20则会报错。 如果要加载手机存储中的图片,使用下面的方法:

new Image.file(path, width: 20.0, height: 20.0, fit: BoxFit.cover)

fit属性指定了图片显示的不同方式,有如下几个值:

  • contain:尽可能大,同时仍然包含图片完全在目标容器内。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

  • cover:尽可能小,同时仍然覆盖整个目标容器。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

  • fill:通过拉伸图片的长宽比填充目标容器。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

  • fitHeight:确保是否显示了图片的完整高度,而不管是否意图片高度溢出了目标容器。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

  • fitWidth:确保是否显示了图片的完整宽度,而不管是否图片高度溢出目标容器。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

  • none:对齐目标容器内的图片(默认情况下居中)并丢弃位于容器外的图片的任何部分。图片原始大小不会被调整。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

  • scaleDown:对齐目标容器内的图片(默认情况下居中),如果必要的话,对图片进行缩放,以确保图片适合容器。这与contain的情况相同,否则它与没有一样。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

加载网络图片

加载网络图片使用下面的方法:

new Image.network(imgUrl, width: 20.0, height: 20.0, fit: BoxFit.cover)

ListView组件

ListView组件用于显示一个列表,在基于Flutter的开源中国客户端App中,新闻列表、动弹列表等都需要用到ListView,一个最简单的ListView可以用如下代码实现:

import ‘package:flutter/material.dart’;

void main() {
List items = new List();
for (var i = 0; i < 20; i++) {
items.add(new Text(“List Item $i”));
}
runApp(new MaterialApp(
title: “Text Demo”,
home: new Scaffold(
appBar: new AppBar(
title: new Text(“Text Demo”),
),
body: new Center(
child: new ListView(children: items)
),
),
));
}

运行上面的代码,结果如下图所示:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

这样的ListView显示不是我们需要的,太难看,每个item没有边距而且没有分割线,所以我们用下面的代码改造一下:

import ‘package:flutter/material.dart’;

void main() {
// 装有ListView中所有item的集合
List items = new List();
for (var i = 0; i < 20; i++) {
var text = new Text(“List Item $i”);
// Padding也是一个Widget,是一个有内边距的容器,可以装其他Widget
items.add(new Padding(
// 内边距设置为15.0,上下左右四边都是15.0
padding: const EdgeInsets.all(15.0),
// Padding容器中装的是Text组件
child: text
));
}
runApp(new MaterialApp(
title: “Text Demo”,
home: new Scaffold(
appBar: new AppBar(
title: new Text(“Text Demo”),
),
body: new Center(
// build是ListView提供的静态方法,用于创建ListView
child: new ListView.builder(
// itemCount是ListView的item个数,这里之所以是items.length * 2是因为将分割线也算进去了
itemCount: items.length * 2,
itemBuilder: (context, index) {
// 如果index为奇数,则返回分割线
if (index.isOdd) {
return new Divider(height: 1.0);
}
// 这里index为偶数,为了根据下标取items中的元素,需要对index做取整
index = index ~/ 2;
return items[index];
},
)
)
),
));
}

此时再次运行上面的代码,UI就好看多了:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

关于ListView的用法,上面的代码中已有相关注释,更详细的用法会在后面的篇幅中介绍,比如ListView中的item实现不同的布局,下拉刷新,加载更多等等。

小结

关于Flutter常用的部分Widget,在上面已有相关示例代码和说明,你还可以在Flutter中文网上查看更多组件及其用法。下一篇中我将记录Flutter中的布局,任何移动开发,甚至Web开发和桌面端应用开发中都不可避免的需要了解布局的知识。

我的开源项目

  1. 基于Google Flutter的开源中国客户端,希望大家给个Star支持一下,源码:
  1. 基于Flutter的俄罗斯方块小游戏,希望大家给个Star支持一下,源码:

深知大多数初中级Android工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则近万的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年Android移动开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。

img

img

img

img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Android开发知识点,真正体系化!

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!

如果你觉得这些内容对你有帮助,可以扫码获取!!(备注:Android)

小福利:

在当下这个碎片化信息环境的时代,很多资源都可以在网络上找到,只取决于你愿不愿意找或是找的方法对不对了

很多朋友不是没有资料,大多都是有几十上百个G,但是杂乱无章,不知道怎么看从哪看起,甚至是看后就忘

如果大家觉得自己在网上找的资料非常杂乱、不成体系的话,我也分享一套给大家,比较系统,我平常自己也会经常研读。

2021大厂最新Android面试真题解析

Android大厂面试真题解析

各个模块学习视频:如数据结构与算法

算法与数据结构资料图

只有系统,有方向的学习,才能在段时间内迅速提高自己的技术。
一线互联网架构师

这份体系学习笔记,适应人群:**第一,**学习知识比较碎片化,没有合理的学习路线与进阶方向。**第二,**开发几年,不知道如何进阶更进一步,比较迷茫。第三,到了合适的年纪,后续不知道该如何发展,转型管理,还是加强技术研究。如果你有需要,我这里恰好有为什么,不来领取!说不定能改变你现在的状态呢!点赞+评论即可获得!

《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!

2021大厂最新Android面试真题解析**

[外链图片转存中…(img-3OV9L8aS-1713505123366)]

各个模块学习视频:如数据结构与算法

[外链图片转存中…(img-YzZPYkCd-1713505123367)]

只有系统,有方向的学习,才能在段时间内迅速提高自己的技术。
[外链图片转存中…(img-MlbnsI2C-1713505123367)]

[外链图片转存中…(img-2AImiYJO-1713505123368)]

这份体系学习笔记,适应人群:**第一,**学习知识比较碎片化,没有合理的学习路线与进阶方向。**第二,**开发几年,不知道如何进阶更进一步,比较迷茫。第三,到了合适的年纪,后续不知道该如何发展,转型管理,还是加强技术研究。如果你有需要,我这里恰好有为什么,不来领取!说不定能改变你现在的状态呢!点赞+评论即可获得!

《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!

  • 15
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值