前言

很多 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');
              },
            ),
          ),
        ],
      ),
    );
  }
}

作用如下:

Flutter - 封装广告页的倒计时圆圈控件

回调 finished 中有一个参数 byUserClick,如果为 true,说明是用户主动点击了跳过,如果为 false,说明是倒计时完毕后主动回调的,能够运用它来区分场景,一般埋点时会需求运用。

整个控件运用 Visibility 来包裹住,在倒计时完毕或者用户主动触发完毕后,会将 visible 设置为 false,躲藏控件。