Flutter——一個跨平臺的開發的框架。類React Native原理。使用Dart語言開發。資料齊備,易學。對于移動開發非常實用。
每個APP都有啟動頁面。啟動頁看似簡單,卻也是“麻雀雖小,五臟俱全”,開啟使用Flutter開發的第一步。本啟動頁面也包含了 頁面布局、 頁面路由,廣告網路請求、加載網絡圖片,加載本地圖片,使用動畫,Canvas繪制界面,數據刷新界面功能。
先看效果:
image
一、頁面功能,整個頁面一個包含一個大屏的廣告頁面,一個本地圖片展示,和一個倒計時功能。
1.界面整體布局。之前是主要做Android原生開發。所以安卓開發作對比。原生Android寫界面,很簡單使用RelativeLayout。相對布局設置約束的位置。在Flutter中使用Stack,Row, Cloumn組合來實現相對布局。
看代碼:
@override
Widget build(BuildContext context) {
return new Stack(
alignment: Alignment.bottomCenter,
children: <Widget>[
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<SkipDownTimeProgress>
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: <Widget>[
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<String, WidgetBuilder> getRoutes() {
var route = {
HOME_PAGE: (BuildContext context) {
return MyHomePage(title: 'demo app');
}
};
return route;
}
}
//跳轉方法
_goHomePage() {
Navigator.of(context).pushNamedAndRemoveUntil(
PageConstance.HOME_PAGE, (Route<dynamic> route) => false);
}
詳細工程請參見:https://github.com/bowen919446264/demowelcome/tree/master