哈喽大家好,一个月没发文,那是因为我沉浸在学习的海洋中,没错,今天要说的就是关于flutter的学习总结,这一篇总结来自一名Android开发的视角。
转载注明出处,CSDN第六篇
大部分的基础我都是通过技术胖的文章和视频来学习,上手较快,当然flutter中文网也是个不错的选择。
我是一名Android小开发,在一家外包工作四年之后,跳到这家大公司,但疫情之下即使大公司也举步维艰,所以不得不多学习充实自己,这样才避免未来被淘汰。这里的经理还是一位很有见地的人,各种安排都是为了同事的未来发展,经过他的指点我上个月的计划都安排给了flutter。
好了话不多说,开始正文。
flutter之初印象
由于我是Android开发,使用Android studio可以更快速的搭建flutter的环境,我直接下载SDK,配置环境变量,然后在studio里安装插件,就没问题直接开始撸代码了。
写过小程序的我感觉看flutter代码毫无压力,完全一样的格式和调用关系,唯一不同的就是组件之间的嵌套,一层又一层,我也试过用in()的方式来解决嵌套,奈何网上没有好用的第三方,作为一个菜鸟只能老老实实的嵌套。
flutter基础理解
其实什么语言不重要,重要的是对于原理的理解,比如界面如何计算每个组件的位置,每个组件自身的属性,如何改变某些变量,和服务器或者用户交互等等。
首先最基础的一句就是,万物都是widget!,就代表任何页面都可以相互嵌套调用。
1、flutter文件
flutter的项目中主要有三个文件夹,android,ios,lib。而flutter的代码都是在lib里面的。
2、flutter代码
flutter的代码风格仿佛是小程序代码和js的结合体。
创建变量使用var相当于Android的Object,当然也可以固定类型int,String,List等等。
当变量发生改变的时候,就可以嵌套setState来刷新页面,但是最好先判断mounted状态,防止页面未打开而刷新造成报错。
当耗时操作的时候需要进行异步操作,方法后添加async,耗时操作前加await
3、原生对接
fulutter提供了MethodChannel来对接原生的方法,其实大部分第三方库也都是对接安卓和苹果的原生代码实现,原理就是定义同样的方法名,我猜应该是通过反射去调用。
在安卓的原生里需要注册一下GeneratedPluginRegistrant,同时继承FlutterActivity
flutter布局理解
1、入口
flutter打开app的入口是main方法,在该方法返回一个MaterialApp的类,相当于Android 的Application,其中的属性有:
title:应用的名称
theme:应用的主题
home:应用的启动页面,返回一个Widget
routes:应用的路由,相当于Android的AndroidManifest中的配置,其实就是给每个页面起一个别名,方便跳转的时候调用。
键值对形式,如:’/home’: (BuildContext context) => HomePage()
若没有配置路由则使用 Navigator.push()来跳转, Navigator.pop()来返回上一页
2、基础组件
最基础的就是StatelessWidget和StatefullWidget这两种组件,就像是窗户的骨架,StatelessWidget是固定不动的静态的那扇,StatefullWidget是左右推动的动态的那扇,我作为简单粗暴的小开发,我直接无脑使用StatefulWidget永远可以推动的动态窗户。
而继承StatefulWidget之后,就是必须实现的方法createState(),在该方法中必须返回一个继承State的类出来。
State中除了包含常规的构造方法和初始化方法,也有自己一套生命周期,还有一个必须实现的方法build。
构造方法用来传参,初始化用来初始化数据和控制器,生命周期控制页面状态,而要搭建我们的页面,就要在build方法中返回一个widget。
3、组件
Scaffold:若是一个新页面,多半是要用这个组件开始,它代表的占满整个屏幕的一个完整页面,包含标题栏,主体和底部导航。
appBar:标题栏,系统自动计算高度,直接使用AppBar这个组件即可
body:主体页面,若是首页可使用TabBarView,可以对应导航的点击事件去切换子页面。
bottomNavigationBar:底部导航栏,我这里直接使用TabBar这个组件,可以和TabBarView绑定同一个controller,这样实现一个项目的首页太简单了。
TabBarView:相当于Android的viewpager,但是和tabber绑定特别好用,除了绑定TabBar的controller外,在children中返回一个Widget的数组即可。
TabBar:导航类的组件,可定义选中和未选中的颜色,在children中返回一个Tab的数组。
Tab:包含图标icon和文字text两个属性。
Container:这个组件Android中没发现类似,反而比较像是Html中的div标签,需要一些外边距margin,内边距padding,居中alignment,边框decoration和背景颜色color的时候就使用这个组件,反正有事没事就加一层Container,但是要注意边框decoration和背景颜色color不能同时使用。
Row:横向布局,mainAxisAlignment和crossAxisAlignment来控制子组件的居中
Column:纵向布局,mainAxisAlignment和crossAxisAlignment来控制子组件的居中。这里将横向和纵向合起来说一下,因为在Android中是LinearLayout,其他并没有什么区别,若要占满宽度或者高度,只需要在children中的使用Expanded即可。
Stack:相对布局,相当于Android的FrameLayout
这些都是布局类的组件,控件类的组件就比较容易了,图片Image,文字Text,输入框TextField
InkWell:点击组件,实现onTap方法即可
RefreshIndicator:上拉刷新,下拉加载。onRefresh可实现刷新,controller的监听判断在滑动底部实现加载。
ListView.builder:为什么不是ListView呢,因为flutter的ListView就和Column没什么区别,只是可以滚动了。而用这个组件,可以实现Andorid的ListView的功能,设置控制器controller,条目数量itemCount,条目布局itemBuilder
SingleChildScrollView:相当于Android的ScrollView
4、配置
pubspec.yaml是flutter的app级别的配置文件,除了配置版本信息之外,还包括第三方库的引用,本地资源的引用等等
1、第三方
大部分都是直接在网上寻找第三方,非常感谢网上那么多开路的大神,为flutter提供了如此多的代码库,以下列出我使用到的库:
dio: ^3.0.9 //网络请求
shared_preferences: ^0.5.3+1 //数据保存
flutter_swiper: ^1.0.6 //轮播图
video_player: ^0.10.1+6
chewie: ^0.9.8+1 //视频播放器
audioplayers: ^0.13.1 //音频播放器
flutter_local_notifications: ^0.9.1+2 //通知
2、本地资源
我这里仅仅用到了本地图标,每一张图片都必须配置,可以说十分不方便,我是在
lib同级的地方创建了一个images的文件夹来使用,配置一下信息
- images/557222.png
- images/557249.png
//下面是使用演示
Image.asset( 'images/557222.png',
width: 25,
height: 25,
)
4、功能
这里会写出我这一个月的时间到底用flutter实现了什么功能,用什么方法实现的,也算是写出一点学(百)习(度)经验。
1、沉浸式
在main方法中添加一下代码:
void main() {
runApp(MyApp());
if (Platform.isAndroid) {
// 以下两行 设置android状态栏为透明的沉浸。
// 写在组件渲染之后,是为了在渲染后进行set赋值,覆盖状态栏,
// 写在渲染之前MaterialApp组件会覆盖掉这个值。
SystemUiOverlayStyle systemUiOverlayStyle =
SystemUiOverlayStyle(statusBarColor: Colors.transparent);
SystemChrome.setSystemUIOverlayStyle(systemUiOverlayStyle);
}
}
这样的话只需要设置当前页面的背景颜色就可以实现沉浸式,但是还需要控制状态栏的字体颜色,在每个页面的根布局Scaffold的外层嵌套:
AnnotatedRegion<SystemUiOverlayStyle>(
value: isdark ? SystemUiOverlayStyle.light : SystemUiOverlayStyle.dark,
child: Scaffold(
)
)
2、网络请求
flutter中的网络请求一般在initState中去初始化,当然根据项目要求灵活调整,使用多中代码库后,发现还是dio比较好用。
这里要特别说明的一点是,赋值的时候使用setState嵌套刷新页面,外面要判断页面是否打开mounted,。
void getList() async {
try {
shows.clear();
var url =
'https://api.yunxuekeji.cn/yunxue_app_api/content/getColumn/1/73334';
var response = await Dio().get(url);
if (response.statusCode == HttpStatus.OK) {
var data = response.data;
if (data['code'] == 200) {
var json = data['body'];
if (mounted) {
setState(() {
list.addAll(new List.from(json['result']));
});
} else {
Toast.toast(context, msg: data['message']);
}
} else {
Toast.toast(context, msg: "服务器异常");
}
} on Exception catch (e) {
Toast.toast(context, msg: "当前网络质量不佳");
}
}
}
3、Toast和弹窗
flutter中的OverlayEntry可以将布局插入到当前页面之上,所以可以用来实现Toast和弹窗功能。实现的方法一样,唯一的区别是Toast要定时去关闭,而弹窗等待用户的反馈去关闭。
//这里创建一个OverlayEntry
OverlayEntry overlay = OverlayEntry(
builder: (BuildContext context) => Positioned(
//top值,默认在屏幕下方
top: MediaQuery.of(context).size.height * 3 / 4,
child: Material(),
));
//插入context所在的页面
Overlay.of(context).insert(overlay);
//重绘方法,达到刷新的效果
overlay.markNeedsBuild();
//关闭方法
overlay.remove();
3、音频播放器
音频播放器是在第三方 audioplayers: ^0.13.1和弹窗的结合实现的,这里直接展示相关代码:
import 'package:audioplayers/audioplayers.dart';
AudioPlayer player;
AudioUtils(Function function) {
player = new AudioPlayer();
player.onNotificationPlayerStateChanged.listen(function);
}
//下面是相关操作,result=1是成功
// int result = await player.play(url);
// int result = await player.pause();
//int result = await player.resume();
//int result = await player.stop();
// int result = await player.release();
//int result = await player.seek(new Duration(milliseconds: mill));
//使用示例,要使用异步
play(context, url) async {
int result = await player.play(url);
if (result == 1) {
AudioUi.play(context);//刷新ui到播放状态
isPlay = true;
print('播放成功');
} else {
isPlay = false;
print('播放失败');
}
AudioUi.refresh();//根据isPlay刷新ui
}
想必有人好奇AudioUi中怎么写的:
class AudioUi {
static BuildContext context;
static OverlayEntry overlay;
static play(BuildContext c) {
context = c;
if (overlay == null) {
overlay = OverlayEntry(
builder: (BuildContext context) => Positioned(
//top值,默认在屏幕下方
top: MediaQuery.of(context).size.height * 3 / 4,
child: Material(
color: Colors.transparent,
child: Container(
width: MediaQuery.of(context).size.width - 40,
color: Color(0x11000000),
child: Padding(
padding: EdgeInsets.fromLTRB(10, 0, 10, 0),
child: InkWell(
onTap: () {
if (MyHomePage.audioUtils != null) {
MyHomePage.audioUtils.release();
}
},
child: Container(
padding: EdgeInsets.all(10.0),
child: Image.asset(
"images/557249.png",
width: 30,
height: 30,
),
),
),
),
),
),
),
));
Overlay.of(context).insert(overlay);
} else {
overlay.markNeedsBuild();
}
static refresh() {
if (overlay != null) {
overlay.markNeedsBuild();
}
}
static close() {
if (overlay != null) {
overlay.remove();
overlay = null;
}
}
}
4、数据保存
flutter的sharedPreferences和Android略有不同,就是flutter的sharedPreferences获取数据的时候竟然是异步的,这样就不在一个线程了,获取结果很麻烦。而经过我的改进,用静态的方式改为和Android获取一样的效果。
注意:需要在刚开始初始化一下
class SharedpUtil {
static SharedPreferences sharedPreferences;
static void init() async {
if (sharedPreferences == null) {
sharedPreferences = await SharedPreferences.getInstance();
}
}
static void putString(String key, String value) async {
if (sharedPreferences == null) {
return;
}
sharedPreferences.setString(key, value);
}
static String getString(String key) {
if (sharedPreferences == null) {
return "";
}
String get = sharedPreferences.getString(key);
return get;
} }
3、其他功能
虽然我还实现了轮播图,播放器和通知栏,但是其中的技术含量还是很低的,没有经过自己的改进,所以就不贴出来了,各位去找一下类似的文章即可,我也是从那些文章里搬过来用的。
flutter个人总结
为期一个月,略微实现了一个小demo,流畅度也不错,但是打包没有成功,需要翻墙;也没有试过IOS适配如何。
虽说flutter的发展越来越好,但2020年一直跟着别人后面走的小开发来说,还是不值得投入太多,毕竟咱不是领头的大牛,现在可用的好用的第三方还是有些少,比如我这个demo中关于直播的即时通讯的相关几乎没有。
我的观点就是,能学会尽早学会,语法也不是很难,当flutter能一统前端的时候,就可以直接出山了。