疫情刚好静下心来回归过去的工作,博主要开始学习flutter,博主会边学习边实战项目连续连贯实现,大约一周会出一篇,源码地址,开源不易,麻烦动手点星,谢谢,本开源不做商业使用,里面涉及用到api接口资源等只供学习,项目开发会分博客文章,请点击链接到对应的文章中浏览,建议其实万变不离其中,说到底还是离不开原生,学习之前往补充原生知识,本片主要讲的是引导页,其中涉及到技术点层叠布局、相对定位、容器、定时器、异步数据更新,再次感谢大家能耐心观看,谢谢!
@[TOC](Flutter 开始征途)
1 布局详解
1.1 Stack层叠布局
在flutter中层叠布局就是叠加效果,如果做过安卓的话,那么就知道FrameLayout,其实就依次往上一层view层叠效果
ios 最原始addSubview,其实就是一个层叠布局效果。
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
UIView * view1 = [[UIView alloc]initWithFrame:CGRectMake(20, 40, 200, 200)];
view1.backgroundColor=[UIColor redColor];
UIView * view2 = [[UIView alloc]initWithFrame:CGRectMake(10, 10, 100, 100)];
view2.backgroundColor=[UIColor greenColor];
[view1 addSubview:view2];
[self.view addSubview:view1];
}
下面是flutter的实现,children包含子组件数组,依次视图叠加效果,而Positioned是用来做绝对位置的,左右头底,四个点需要自己计算适配哦。
1.2 Align 相对布局详解
1.2.1 安卓相对布局
在安卓中大家应该很熟悉RelativeLayout,这可是我们安卓最常用最无敌布局神器,再复杂的业务都是浮云。
布局 | 方向 |
---|---|
layout_centerHorizontal | 水平居中 |
layout_alignParentRight | 右对齐 |
layout_alignParentLeft | 左对齐 |
layout_alignParentTop | 顶部对齐 |
layout_alignBottom | 居底部对齐 |
layout_centerInParent | 居中 |
layout_centerVertical | 竖向对齐 |
1.2.2 ios相对布局
ios我们开发的时候,我们用的Autolayout布局,解析如下:
UIView *subView = [[UIView alloc]init];
subView.backgroundColor = [UIColor greenColor];
[self.view addSubview:subView];
subView.translatesAutoresizingMaskIntoConstraints = NO;
[self.view addConstraint:[NSLayoutConstraint constraintWithItem:subView attribute:NSLayoutAttributeTop relatedBy:NSLayoutRelationEqual toItem:self.view attribute:NSLayoutAttributeTop multiplier:1. constant:150]];
[self.view addConstraint:[NSLayoutConstraint constraintWithItem:subView attribute:NSLayoutAttributeHeight relatedBy:NSLayoutRelationEqual toItem:self.view attribute:NSLayoutAttributeHeight multiplier:0.3 constant:0]];
[self.view addConstraint:[NSLayoutConstraint constraintWithItem:subView attribute:NSLayoutAttributeWidth relatedBy:NSLayoutRelationEqual toItem:self.view attribute:NSLayoutAttributeWidth multiplier:0.3 constant:0]];
[self.view addConstraint:[NSLayoutConstraint constraintWithItem:subView attribute:NSLayoutAttributeLeft relatedBy:NSLayoutRelationEqual toItem:self.view attribute:NSLayoutAttributeLeft multiplier:1. constant:100]];
上面方法参数说明:
第一个参数:指定约束左边的视图view1
第二个参数:指定view1的属性attr1
第三个参数:指定左右两边的视图的关系relation
第四个参数:指定约束右边的视图view2
第五个参数:指定view2的属性attr2
第六个参数:指定一个与view2属性相乘的乘数multiplier
第七个参数:指定一个与view2属性相加的浮点数constant
依据的公式是:view1.attr1 = view2.attr2*multiplier +constant
布局 | 方向 |
---|---|
NSLayoutAttributeLeft | 视图的左边 |
NSLayoutAttributeRight | 视图的右边 |
NSLayoutAttributeTop | 视图的上边 |
NSLayoutAttributeBottom | 视图的下边 |
NSLayoutAttributeLeading | 视图的前边 |
NSLayoutAttributeTrailing | 视图的后边 |
NSLayoutAttributeWidth | 视图的宽度 |
NSLayoutAttributeHeight | 视图的高度 |
NSLayoutAttributeCenterX | 视图的中点的X值 |
NSLayoutAttributeCenterY | 视图中点的Y值 |
NSLayoutAttributeBaseline | 视图的基准线 |
NSLayoutAttributeNotAnAttribute | 无属性 |
NSLayoutRelation的类型:
NSLayoutRelationLessThanOrEqual 关系小于或等于
NSLayoutRelationEqual 视图关系等于
NSLayoutRelationGreaterThanOrEqual 视图关系大于或等于
这里要说明一下,设置约束之前必须要求确保子视图添加到了父视图上了(如:[self.view addSubview:subView]),并且被约束的视图的translatesAutoresizingMaskIntoConstraints = NO,不然就会发生程序crash。
1.2.3 flutter Align相对布局
Align 组件可以调整子组件的位置,并且可以根据子组件的宽高来确定自身的的宽高,定义如下:
首先你要在外部套一个容器,或者像安卓要套一个RelativeLayout一样,区别目前么发现如何自适应,必须设置区域大小。
布局 | 方向 |
---|---|
Alignment.topLeft | 头部左对齐 |
Alignment.topRight | 头部右对齐 |
Alignment.topCenter | 头部居中 |
Alignment.centerLeft | 居中左对齐 |
Alignment.centerRight | 居中右对齐 |
Alignment.center | 居中对齐 |
Alignment.bottomLeft | 底部左对齐 |
Alignment.bottomCenter | 底部居中 |
Alignment.bottomRight | 底部右对齐 |
new Positioned(
child: Container(
child: Align(
child: Stack(
alignment: Alignment.center,
children: <Widget>[
Image.asset('images/splash_confirm.png'),
Text(
"立即开启",
style: TextStyle(
fontSize: 12,
color: Colors.white,
fontFamily: 'Raleway',
//2.不继承默认样式
decorationStyle: TextDecorationStyle.dashed),
)
],
),
alignment: Alignment.bottomCenter,
),
margin: EdgeInsets.only(bottom: 100),
),
height: pageTotalSize > 0 && (pageViewIndex == pageTotalSize - 1)
? height
: 0,
width: width)
alignment : 需要一个AlignmentGeometry类型的值,表示子组件在父组件中的起始位置。
AlignmentGeometry 是一个抽象类,它有两个常用的子类:Alignment和 FractionalOffset,我们将在下面的示例中详细介绍。
widthFactor和heightFactor是用于确定Align 组件本身宽高的属性;
它们是两个缩放因子,会分别乘以子元素的宽、高,最终的结果就是Align 组件的宽高。如果值为null,则组件的宽高将会占用尽可能多的空间。
2 定时器
启动页会有定时器,定时几秒后操作,下面会举例说明安卓、ios、flutter实现不同。
2.1 安卓定时器
第一种方式
Timer timer = new Timer(); //创建一个定时器对象
TimerTask task = new TimerTask()
timer.schedule(task,0,10000); //启动定时器
第二种方式
//参数1:计时总时间,参数2:每次扣除时间数
CountDownTimer cdt = new CountDownTimer(10000, 100)
{
@Override
public void onTick(long millisUntilFinished)
{
}
@Override
public void onFinish() {
}
};
cdt.start();
其它方式 alarmService、thread、Handler。
2.2 ios定时器
第一种
NSTimer *timer = [NSTimer timerWithTimeInterval:2 target:self selector:@selector(test) userInfo:nil repeats:YES];
// 将定时器添加到runloop中,否则定时器不会启动
[[NSRunLoop mainRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
// 停止定时器
[timer invalidate];
第二种
// 创建displayLink
CADisplayLink *displayLink = [CADisplayLink displayLinkWithTarget:self selector:@selector(test:)];
// 将创建的displaylink添加到runloop中,否则定时器不会执行
[displayLink addToRunLoop:[NSRunLoop mainRunLoop] forMode:NSDefaultRunLoopMode];
// 停止定时器
[displayLink invalidate];
displayLink = nil;
2.3 flutter 定时器
Timer定时器记得一定要在initState,这个函数就是初始化状态,你就可以理解为安卓的onCreate()生命周期、ios的initWithCoder状态一样,build就是一个构建widget,其实就可以理解为安卓xml,但是这里有很大区别,就是更新ui、初始化、initState()、didUpdateWidget()、setState()、didChangeDependencies()都会调用哦,当然布局上更新的数据怎么做,一定是在build构建写对应数据变量,其实思想就跟mvvm,数据驱动刷新ui一样。
@override
void initState() {
// TODO: implement initState
super.initState();
splashBuilder = fetchPost();
initCountDown();
WidgetsBinding.instance.addObserver(this);
}
initCountDown() {
// 只在倒计时结束时回调
Timer.periodic(new Duration(seconds: 1), (timer) {
if (timer.tick == 5) {
setState(() {
nextStr = '跳过';
});
timer.cancel();
print(nextStr);
} else {
setState(() {
nextStr = '${countTime--}s跳过';
});
print(nextStr);
}
});
}
new Positioned(
child: new GestureDetector(
child: new Container(
padding: const EdgeInsets.only(
left: 10, top: 2, right: 10, bottom: 2),
decoration: new ShapeDecoration(
color: !isClicking1 ? Colors.white : Color(0xff898989),
shape: StadiumBorder(
side: BorderSide(
color: Color(0xff898989),
style: BorderStyle.solid,
width: 1))),
child: Text(nextStr),
),
onTap: () {
print('onTap 跳过');
},
onTapUp: (TapUpDetails) {
print('onTapUp 跳过');
upDataButtonState(false);
},
onTapDown: (TapUpDetails) {
print('onTapDown 跳过');
upDataButtonState(true);
},
onTapCancel: () {
print('onTapCancel 跳过');
upDataButtonState(false);
},
),
top: 10 + statebar_height,
right: 10)
3 网络异步更新
3.1 网络使用详解
目前在flutter使用最多就是Dio库,在安卓都是用的是Reftrofit,ios AFNetWork,好像git有个flutter的reftroft对应插件。dio链接
首先引用dio插件,我们要做一个启动页就要有网络申请,直接上dio代码,get获取图片集合,然后网络解析加载。
Future<SplashEntityEntity> fetchPost() async {
var dio = Dio();
Response response;
response = await dio
.get(WanAndroidApi.OtherCategory, queryParameters: {"type": 1});
// print(JsonConvert.fromJsonAsT<SplashEntityEntity>(response.data)
// .result
// .elementAt(0)
// .album10001000);
return JsonConvert.fromJsonAsT<SplashEntityEntity>(response.data);
}
3.2 解析数据详解
3.2.1 官方推荐解析
首先,打开JSON to Dart,JSON to Dar
如下图所示
Map splashMap = JSON.decode(json);
var splash = new Splash.fromJson(userMap);
print('Howdy, ${splash.code}!');
print('We sent the verification link to ${splash.message}.');
3.2.2 FlutterJsonBeanFactory插件
搜索安卓插件,然后重启工具,这下可以愉快玩耍哦。
右键会有一个插件,然后让你一件生产对应的dart解析文件哦。
解析就是一行代码
JsonConvert.fromJsonAsT<SplashEntityEntity>(response.data)
4 异步
当我们访问接口的时候,那么操作肯定需要异步,一边访问请求一边给个进度条,这样体验会更好,异步操作很重要,在flutter中异步很简单,就是靠FutureBuilder和future配合,原生我们都是异步线程,然后刷新ui会切主线程刷新。
Future<SplashEntityEntity> fetchPost() async {
var dio = Dio();
Response response;
response = await dio
.get(WanAndroidApi.OtherCategory, queryParameters: {"type": 1});
// print(JsonConvert.fromJsonAsT<SplashEntityEntity>(response.data)
// .result
// .elementAt(0)
// .album10001000);
return JsonConvert.fromJsonAsT<SplashEntityEntity>(response.data);
}
FutureBuilder<SplashEntityEntity>(
future: splashBuilder,
builder: (BuildContext content, AsyncSnapshot async) {
if (async.connectionState == ConnectionState.done) {
print("success");
return getPageView(async.data);
} else {
print("loading===");
return Container(
height: 100,
width: 100,
alignment: Alignment.center,
child: CircularProgressIndicator(
backgroundColor: Colors.blue,
valueColor: AlwaysStoppedAnimation(Colors.blue),
),
);
return CircularProgressIndicator(strokeWidth: 1);
}
},
)
- future:FutureBuilder依赖的Future,通常是一个异步耗时任务。
- initialData:初始数据,用户设置默认数据。
- builder:这里对应异步操作的时候对应ui组件,其实都是wiget,注意网络申请要放在初始化中操作,如果有定时器的话,setState更新状态话,就会一直走build.
5 事件讲解
首先这里要讲的事件,在flutter简直无法理解,不知道创造这个人是怎么想的,万物都是Widget,事件怎么能是widget,这真是无限地狱嵌套,代码如下:
5.1 Listener事件
Listener(
child: Container(
alignment: Alignment.center,
color: Colors.blue,
width: 300.0,
height: 150.0,
child: Text(_event?.toString()??"",style: TextStyle(color: Colors.white)),
),
onPointerDown: (PointerDownEvent event) => setState(()=>_event=event),
onPointerMove: (PointerMoveEvent event) => setState(()=>_event=event),
onPointerUp: (PointerUpEvent event) => setState(()=>_event=event),
)
事件 | 介绍 |
---|---|
onPointerDown | 按下 |
onPointerUp | 抬起时触发 |
onPointerCancel | 取消触摸时触发 |
onPointerMove | 移动时触发 |
5.2 手势事件
手势这个都是外面包一个,其实这种涉及模式还能理解,需要就外面扩展,不需要就不用写,但是点击事件listener完全不理解呕吐。
GestureDetector(
child: CircleAvatar(child: Text("A")),
//垂直方向拖动事件
onVerticalDragUpdate: (DragUpdateDetails details) {
setState(() {
_top += details.delta.dy;
});
}
)
事件 | 介绍 |
---|---|
onTapUp | 点击抬起 |
onTapDown | 点击按下 |
onTapCancel | 取消触摸时触发 |
onTap | 点击回调 |
-------- | ----- |
onDoubleTap | 双击 |
-------- | ----- |
onPanDown | 指针已接触屏幕并可能开始移动 |
onPanStart | 指针已经接触屏幕并开始移动 |
onPanUpdate | 与屏幕接触并移动的指针再次移动 |
onPanEnd | 先前与屏幕接触并移动的指针不再与屏幕接触,并且当它停止接触屏幕时以特定速度移动 |
onPanCancel | 先前触发 onPanDown 的指针未完成 |