Flutter——一个跨平台的开发的框架。类React Native原理。使用Dart语言开发。资料齐备,易学。对于移动开发非常实用。
每个APP都有启动页面。启动页看似简单,却也是“麻雀虽小,五脏俱全”,开启使用Flutter开发的第一步。本启动页面也包含了 页面布局、 页面路由,广告网路请求、加载网络图片,加载本地图片,使用动画,Canvas绘制界面,数据刷新界面功能。
先看效果:
一、页面功能,整个页面一个包含一个大屏的广告页面,一个本地图片展示,和一个倒计时功能。
1.界面整体布局。之前是主要做Android原生开发。所以安卓开发作对比。原生Android写界面,很简单使用RelativeLayout。相对布局设置约束的位置。在Flutter中使用Stack,Row, Cloumn组合来实现相对布局。
看代码:
@override
Widget build(BuildContext context) {
return new Stack(
alignment: Alignment.bottomCenter,
children: [
new Container(
color: Colors.white,
child: new Image.network(
welcomeImageUrl,
fit: BoxFit.cover,
),
constraints: new BoxConstraints.expand(),
),
new Image.asset(
'pic/images/wenshan_wel_logon.jpg',
fit: BoxFit.fitWidth,
),
new Container(
child: Align(
alignment: Alignment.topRight,
child: new Container(
padding: const EdgeInsets.only(top: 30.0, right: 20.0),
child: new SkipDownTimeProgress(
Colors.red,
22.0,
new Duration(seconds: 5),
new Size(25.0, 25.0),
skipText: "跳过",
clickListener: this,
),
),
),
),
],
);
}
2.局部界面,自定义 “跳过” Widget——SkipDownTimeProgress;主要功能,显示文字,倒计时进度,点击监听。
倒计时我们使用动画实现,例如:需要倒计时5秒,就等于显示5秒的动画。
详见skip_down_time.dart代码。
import 'package:flutter/material.dart';
import 'package:flutter/widgets.dart';
import 'dart:math' as math;
class _DrawProgress extends CustomPainter {
final Color color;
final double radius;
double angle;
AnimationController animation;
Paint circleFillPaint;
Paint progressPaint;
Rect rect;
_DrawProgress(this.color, this.radius,
{double this.angle, AnimationController this.animation}) {
circleFillPaint = new Paint();
circleFillPaint.color = Colors.white;
circleFillPaint.style = PaintingStyle.fill;
progressPaint = new Paint();
progressPaint.color = color;
progressPaint.style = PaintingStyle.stroke;
progressPaint.strokeCap = StrokeCap.round;
progressPaint.strokeWidth = 4.0;
if (animation != null && !animation.isAnimating) {
animation.forward();
}
}
@override
void paint(Canvas canvas, Size size) {
double x = size.width / 2;
double y = size.height / 2;
Offset center = new Offset(x, y);
canvas.drawCircle(center, radius - 2, circleFillPaint);
rect = Rect.fromCircle(center: center, radius: radius);
angle = angle * (-1);
double startAngle = -math.pi / 2;
double sweepAngle = math.pi * angle / 180;
print("draw paint-------------------= $startAngle, $sweepAngle");
// canvas.drawArc(rect, startAngle, sweepAngle, false, progressPaint);
//1.0.0之后换种绘制圆弧的方式:
Path path = new Path();
path.arcTo(rect, startAngle, sweepAngle, true);
canvas.drawPath(path, progressPaint);
}
@override
bool shouldRepaint(CustomPainter oldDelegate) {
return oldDelegate != this;
}
}
class SkipDownTimeProgress extends StatefulWidget {
final Color color;
final double radius;
final Duration duration;
final Size size;
String skipText;
OnSkipClickListener clickListener;
SkipDownTimeProgress(
this.color,
this.radius,
this.duration,
this.size, {
Key key,
String this.skipText = "跳过",
OnSkipClickListener this.clickListener,
}) : super(key: key);
@override
_SkipDownTimeProgressState createState() {
return new _SkipDownTimeProgressState();
}
}
class _SkipDownTimeProgressState extends State
with TickerProviderStateMixin {
AnimationController animationController;
double curAngle = 360.0;
@override
void initState() {
super.initState();
print('initState----------------------');
animationController =
new AnimationController(vsync: this, duration: widget.duration);
animationController.addListener(_change);
_doAnimation();
}
@override
void didUpdateWidget(SkipDownTimeProgress oldWidget) {
super.didUpdateWidget(oldWidget);
print('didUpdateWidget----------------------');
}
@override
void dispose() {
super.dispose();
print('dispose----------------------');
animationController.dispose();
}
void _onSkipClick() {
if (widget.clickListener != null) {
print('skip onclick ---------------');
widget.clickListener.onSkipClick();
}
}
void _doAnimation() async {
Future.delayed(new Duration(milliseconds: 50), () {
if(mounted) {
animationController.forward().orCancel;
}else {
_doAnimation();
}
});
}
void _change() {
print('ange == $animationController.value');
double ange =
double.parse(((animationController.value * 360) ~/ 1).toString());
setState(() {
curAngle = (360.0 - ange);
});
}
@override
Widget build(BuildContext context) {
return new GestureDetector(
onTap: _onSkipClick,
child: new Stack(
alignment: Alignment.center,
children: [
new CustomPaint(
painter:
new _DrawProgress(widget.color, widget.radius, angle: curAngle),
size: widget.size,
),
Text(
widget.skipText,
style: TextStyle(
color: widget.color,
fontSize: 13.5,
decoration: TextDecoration.none),
),
],
),
);
}
}
abstract class OnSkipClickListener {
void onSkipClick();
}
代码解释:
1.定义私有的类 _DrawProgress 实现绘制进度界面,
绘制圆和绘制圆弧。根据传入的angle值来绘制圆弧的大小
(这里需要注意 canvas.drawArc方法的二和第三参数不是0-360度的值而是 使用弧度用PI 来计算,
正负数控制是逆时针,还是顺时针)
2. 动画使用AnimationController, 他们有start方法,使用forward()方法开启动画。
3. 刷新界面的方式, 类似于Android原生的ListView
使用Adapter更新界面的原理,即:UI层通过数据来绘制界面,
数据改变之后,通知适配器根据新数据重新绘制UI。所以在写代码的时候要牢记 通过修改数据来修改界面。
setState(() {
curAngle = (360.0 - ange);
});
setSate方法就类似于通知适配器重绘的功能
4.使用接口OnSkipClickListener回调点击事件
5. 注意:在使用 Flutter的Text Widget 时,设置TextStyle 的属性
decoration: TextDecoration.none 消除下划线. 不然会有“奇怪”的下划线显示
3.界面跳转——路由。 在Flutter中没有Activity的概念 和Intent。 使用Routes 和 Navigator 来实现页面跳转。Routes 来注册页面, Navigator指定跳转。
@override
Widget build(BuildContext context) {
return new MaterialApp(
title: 'Flutter Demo as wenshancms',
theme: new ThemeData(
primarySwatch: Colors.blue,
),
home: WelcomePage(),
routes: PageConstance.getRoutes(),
);
}
------------------------------------------------//辅助类来管理页面
import 'package:demonewsapp/page/home_page.dart';
import 'package:flutter/material.dart';
class PageConstance {
static String WELCOME_PAGE = '/';
static String HOME_PAGE = '/home';
static Map getRoutes() {
var route = {
HOME_PAGE: (BuildContext context) {
return MyHomePage(title: 'demo app');
}
};
return route;
}
}
//跳转方法
_goHomePage() {
Navigator.of(context).pushNamedAndRemoveUntil(
PageConstance.HOME_PAGE, (Route route) => false);
}
详细工程请参见:https://github.com/bowen919446264/demowelcome/tree/master