RN想必大家都知道,它的技术底层是沿用了native的技术,渲染就是用原始的渲染的,所以会导致安卓和iOS的兼容性不太好,组件会分平台,不好维护,连忠实的追随者Airbnb也不得不放弃了,改为最初的原生开发了。
Flutter,2015年10月就有了,2018年12月正式版本1.0发布,是谷歌开发的,渲染引擎是自己用C++搞出来的,不再借用原生的。个别地方iOS和安卓的处理方式不一样,但是这个不一样在开发者这层是体现不出来的,代码只需要一套,所以在兼容性上就非常的好。
本篇文章开始慢慢的探究一下Flutter。环境的配置大家去Flutter官网、Flutter中文网探索安装就可以了。
1.初次接触
本篇开始带着大家一起慢慢的学习Flutter,开发工具使用的是Android Studio,当然你用VS Code也可以。cd进入到对应的文件夹然后命令行flutter create boxdemo
创建一个boxdemo的flutter工程,flutter的命令这里也不再赘述,良心话,真正开发的过程中像这种创建工程、运行工程的有可视化的如果还用命令行那就是纯属装逼,哈哈。Android Studio的flutter插件安装过了的话直接就直接可以在界面上点击New Flutter Project
,按照步骤一步一步即可创建flutter工程,创建完后如果发现无法在iOS模拟器或者真机上运行,请用xcode
打开ios目录下的Runner.xcworkspace
进行证书设置,然后就可以正常运行了。
优先来一个Flutter Application工程。用Android Studio运行的过程中如果出现意外中断后,再也运行不起来或者运行的不是最新代码,请在终端执行
rm ~/flutter/bin/cache/lockfile
删除flutter/bin
里面的缓存区就可以了,flutter的路径请按照自己电脑上的路径我这里就是放在~
中。Flutter主要的语言是dart
,在后面的写代码过程中我们会一直用到它,新建完工程后,我们从main.dart入手,这里是flutter工程的起始点,就像是一个iOS工程的AppDelegate
一样,会发现main.dart
有一个引入 import 'package:flutter/material.dart';
,这个引入在以后的日子你会经常看到,类似于iOS中的UIKit
,是谷歌给大家提供的一套UI方案。看着初始工程的main.dart里面代码有点多,全部干掉,来一个简单的代码:
import 'package:flutter/material.dart';
void main() {
runApp(
Center(
child: Text('BoxJing')
)
);
}
这代码足够简单了,就是一个简单的在中间位置显示一个BoxJing
字符串。
在Flutter工程中会有一个Widget
(小部件)我们会频率很高的见到它,这个东西就是我们在iOS里面的控件。Widget
分为下面两种:
-
stateless
: 无状态,死板板的显示,初始化后无法再改变 -
stateful
: 有状态,可以根据需要刷新
那么很明显,我们可以根据自己的需要来封装不同的Widget,那么先来个我们自己封装一个简单的stateless
的Widget
:
import 'package:flutter/material.dart';
void main() {
runApp(
BoxWidgetLess()
);
}
class BoxWidgetLess extends StatelessWidget {
@override
Widget build(BuildContext context) {
// TODO: implement build
return Center(
child: Text('一个阿狸', textDirection: TextDirection.ltr)
);
}
}
如果某个函数里只有一句代码的时候,比如上面main
函数,我们可以直接简化为:void main() => runApp(BoxWidgetLess());
。既然我们用到了Text
这个Widget,下面就仔细的说一下这个Text:
class Text extends StatelessWidget {
/// The [data] parameter must not be null.
const Text(
this.data, {
Key key,
this.style,
this.strutStyle,
this.textAlign,
this.textDirection,
this.locale,
this.softWrap,
this.overflow,
this.textScaleFactor,
this.maxLines,
this.semanticsLabel,
this.textWidthBasis,
}) : assert(
data != null,
'A non-null String must be provided to a Text widget.',
),
textSpan = null,
super(key: key)
final TextStyle style;
...
final TextAlign textAlign;
也就是说第一个参数data必须传值,后面大括号里面的全是可选参数,来个实实在在的例子,文字带大小和颜色最基本的操作:
class BoxWidgetLess extends StatelessWidget {
@override
Widget build(BuildContext context) {
// TODO: implement build
final _boxTxtStyle = TextStyle(
color: Colors.blue,
fontSize: 36.0,
fontWeight: FontWeight.bold
);
return Center(
child: Text('一个阿狸',
textDirection: TextDirection.ltr,
style: _boxTxtStyle
)
);
}
}
还是通俗易懂的,多练习练习就熟练了。下面来个一直想要的界面效果:
void main() => runApp(BoxApp());
class BoxApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
// debugShowCheckedModeBanner: false, //隐藏右上角DEBUG标识
home: Scaffold(
appBar: AppBar(
title: Text('Box Demo'),
),
body: BoxWidgetLess(),
),
theme: ThemeData(
primaryColor: Colors.purple
),
);
}
}
前后对比效果来一个:
这个MaterialApp
其实就是系统帮我们封装了一个可以让我们快速开发的手脚架,点进去源码,解释的也很清楚,Scaffold
就像是iOS开发中的UINavigationController
,实际上比它要弱,可以暂时的这样理解起来,appBar
就是导航条,body
就是导航条下面的空白部分,theme
可以用来设置一些主题颜色,其他很多小东西大家就自己试一试。
2. ListView
我们在开发中用到的最多的
ListView
,那必须来一波操作,先来个简单的代码:
void main() => runApp(BoxHome());
class BoxApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
home: BoxHome(),
theme: ThemeData(
primaryColor: Colors.blue
),
);
}
}
class BoxHome extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Box Demo'),
),
body: ListView.builder(
itemCount: 10,
itemBuilder: _boxCellForRow,
),
);
}
// cellForRow方法
Widget _boxCellForRow(BuildContext context, int index) {
return Text('阿狸');
}
}
效果就是返回10行文字,全是
阿狸
。从代码中可以看到ListView
的构建方法使用ListView.builder
,里面的参数也是非常好理解的,itemCount
就是行数,itemBuilder
就是每个cell,其他的参数直接去看源码注释,Flutter中的ListView
是没有iOS中TableView
的Section
的概念的。简单的把cell的构建摘出去一个单独方法来操作了,这里说一点,在Dart
中带_
的属于内部的变量或者方法,外部不可访问,不带_
属于公开的。这里我们为了达到一个更好的效果,新建一个people.dart
文件来作为一个model,等下我们会用这个model来填充cell:
class People {
// 构造方法
const People({
this.name,
this.avatar
});
// 两个属性 final 不可变的 var 可以更改的
final String name;
final String avatar;
}
// 这里声明一个公开的数据源 后面外部需要使用填充cell
final List<People> boxArr = [
People(name:'阿狸1',avatar: 'http://cache.fotoplace.cc/ap2/190521/0/7B5C54B95100DE281E689EEF24E499D9.png'),
People(name:'阿狸2',avatar: 'http://cache.fotoplace.cc/ap2/190521/0/6478737E09CA39170389191133953111.png'),
People(name:'阿狸3',avatar: 'http://cache.fotoplace.cc/ap2/190521/0/5FD190ECFF4943167F3D5F19A2973D6D.png')
];
然后在main.dart
中引入import 'model/people.dart';
并且改掉cellForRow方法:
// 直接一个图片控件
Widget _boxCellForRow(BuildContext context, int index) {
return Container(
color: Colors.white,
height: 88.0,
margin: EdgeInsets.all(6.0),
child: Image.network(boxArr[index].avatar),
);
}
这里就是放一个单独的控件Image来加载网络图片,在开发中很少用到,一般情况下我们都是多个控件在一个cell中,来几个多控件其他布局,可以放开不同的注释看效果,完整的代码可以在文章底部的github
上下载运行:
// cellForRow方法
//// 横向 Row
// Widget _boxCellForRow(BuildContext context, int index) {
// return Container(
// color: Colors.white,
// height: 88.0,
// margin: EdgeInsets.all(6.0),
// child: Row(
// children: <Widget>[
// Image.network(boxArr[index].avatar),
// Text(boxArr[index].name)
// ],
// ),
// );
// }
//// 竖向 Column
// Widget _boxCellForRow(BuildContext context, int index) {
// return Container(
// color: Colors.white,
// height: 240.0,
// margin: EdgeInsets.all(6.0),
// child: Column(
// children: <Widget>[
// Image.network(boxArr[index].avatar),
// Text(boxArr[index].name)
// ],
// ),
// );
// }
//// 垂直 z轴 Stack
// Widget _boxCellForRow(BuildContext context, int index) {
// return Container(
// color: Colors.white,
// height: 240.0,
// margin: EdgeInsets.all(6.0),
// child: Stack(
// children: <Widget>[
// Image.network(boxArr[index].avatar),
// Text(boxArr[index].name)
// ],
// ),
// );
// }
先来看一下这三种效果:
这三种布局会是我们在整个Flutter开发中使用到,各种各样的布局都是用这三种布局来实现的。一般情况下,我们自定义cell最外层会用一个
Container
来包一层,顾名思义是一个容器,其实就是一个很干净的部件,在单个控件中我们直接是在child
里面放置了一个控件就可以了,多个的时候,一定要有一个布局的方式,所以使用这三个中的其中一个包多个,用了children
,这命名相当的人性化吧。这时候,如果想文字和图片中间有点间距怎么办?使用SizedBox
来进行间隔设置,就是图片和文字之间放了一个宽度为10pt的空白区域,我代码里使用的是SizedBox(width: 10)
,如果是竖排方向间隔那就是SizedBox(height: 10)
,直接上代码:
// 横向 Row
Widget _boxCellForRow(BuildContext context, int index) {
return Container(
color: Colors.white,
height: 88.0,
margin: EdgeInsets.all(6.0),
child: Row(
children: <Widget>[
Image.network(boxArr[index].avatar),
SizedBox(width: 10),
Text(boxArr[index].name)
],
),
);
}
margin
这个东西如果你接触过CSS,应该就很明白是干嘛用的,就是用来设置边距的,再看后面的赋值EdgeInsets
,作为一个iOS开发者很熟悉,经常会用到的设置上下左右的距离,在Flutter中常用的有all
和only
,all就是直接设置四边距离都是同一个值,only的话就需要指明是哪个方向的边距是多少,还有其他的symmetric
、fromWindowPadding
等方法看看注释,试一试就可以了,看一下运行效果:
如果想固定这个图片的大小,那就直接给Image设置
width
和height
来实现,可以直接去掉Container的height了:
// 横向 Row
Widget _boxCellForRow(BuildContext context, int index) {
return Container(
color: Colors.white,
// height: 88.0,
margin: EdgeInsets.all(6.0),
child: Row(
children: <Widget>[
Image.network(boxArr[index].avatar,width: 60.0,height: 60.0),
SizedBox(width: 10),
Text(boxArr[index].name)
],
),
);
}
看一下运行效果:
如期达到了我们想要的效果。有一点要注意,并不是所有的部件都有
width
、height
属性的,如果没有,就把它放进一个Container
,Container
是有width
、height
属性的。
本篇就简单的接触一下Flutter,感受一下Dart语言,所有的代码都可以在Github:BoxJ/Flutter-daydayup中下载,本篇代码的文件夹是
boxdemo_001
,欢迎一起交流!