前言
很多 App 的广告页都会有一个倒计时控件,倒计时完毕或者用户点击之后就会进入到 App 的主页。最近在搞一个新的 App,正好需求用到,就趁便封装一个,用的是 Flutter 供给的组件 CircularProgressIndicator
,比较简单,需求的同学能够直接拿去用。
运用
import 'dart:async';
import 'package:flutter/material.dart';
class CountdownCircle extends StatefulWidget {
const CountdownCircle({
Key? key,
this.countdownSeconds = 5,
this.ringBackgroundColor = Colors.transparent,
this.ringColor = Colors.deepOrange,
this.ringStrokeWidth = 3.0,
this.textStyle = const TextStyle(color: Colors.black),
this.finished,
}) : assert(countdownSeconds > 0),
assert(ringStrokeWidth > 0),
super(key: key);
/// 倒计时秒数,默以为 5 秒
final int countdownSeconds;
/// 圆环的背景色,ringColor 会逐步填充背景色,默以为通明色
final Color ringBackgroundColor;
/// 圆环逐步填充的颜色,默以为 Colors.deepOrange
final Color ringColor;
/// 圆谎的宽度,默以为3.0
final double ringStrokeWidth;
/// 中心案牍的字体
final TextStyle textStyle;
/// 点击或倒计时完毕后的回调
/// [byUserClick] 为 true,是用户点击,否则是倒计时完毕
final Function({required bool byUserClick})? finished;
@override
State<CountdownCircle> createState() => _CountdownCircleState();
}
class _CountdownCircleState extends State<CountdownCircle> {
Timer? _timer;
final _currentTimer = ValueNotifier<int>(0);
final _isVisible = ValueNotifier<bool>(true);
@override
void initState() {
super.initState();
_timer = Timer.periodic(const Duration(milliseconds: 10), (timer) {
_currentTimer.value += 10;
if (_currentTimer.value >= widget.countdownSeconds * 1000) {
_didFinished(byUserClick: false);
}
});
}
@override
void dispose() {
_timer?.cancel();
super.dispose();
}
@override
Widget build(BuildContext context) {
return ValueListenableBuilder(
builder: (context, bool isVisible, child) {
return Visibility(
visible: isVisible,
child: GestureDetector(
onTap: () => _didFinished(byUserClick: true),
child: Stack(
alignment: Alignment.center,
children: [
ValueListenableBuilder(
builder: ((context, int countdownDuration, child) => CircularProgressIndicator(
strokeWidth: widget.ringStrokeWidth,
color: widget.ringColor,
value: countdownDuration / (widget.countdownSeconds * 1000),
backgroundColor: widget.ringBackgroundColor,
)),
valueListenable: _currentTimer,
),
Text(
'跳过',
style: widget.textStyle,
),
],
),
),
);
},
valueListenable: _isVisible,
);
}
void _didFinished({required bool byUserClick}) {
if (widget.finished != null) {
widget.finished!(byUserClick: byUserClick);
}
_timer?.cancel();
_isVisible.value = false;
}
}
运用方法:
class SplashPage extends StatefulWidget {
const SplashPage({Key? key}) : super(key: key);
@override
State<SplashPage> createState() => _SplashPageState();
}
class _SplashPageState extends State<SplashPage> {
@override
Widget build(BuildContext context) {
return Scaffold(
body: Stack(
children: [
Positioned(
right: 200,
top: 200,
child: CountdownCircle(
finished: (byUserClick) {
print('用户主动点击? $byUserClick');
},
),
),
],
),
);
}
}
作用如下:
回调 finished
中有一个参数 byUserClick
,如果为 true
,说明是用户主动点击了跳过,如果为 false
,说明是倒计时完毕后主动回调的,能够运用它来区分场景,一般埋点时会需求运用。
整个控件运用 Visibility
来包裹住,在倒计时完毕或者用户主动触发完毕后,会将 visible
设置为 false
,躲藏控件。