1. 依赖
dependencies:
flutter_bloc: ^2.1.1
equatable: ^1.0.1
wave: ^0.0.8
2. Ticker
Ticker 用于产生定时器的数据流。
/// 定时器数据源
class Ticker {
/// 定时器数据源
/// @param ticks 时间
Stream<int> tick({int ticks}){
return Stream.periodic(Duration(seconds: 1), (x) => ticks - x - 1).take(ticks);
}
}
3. TimerBloc
创建 TimerBloc 用于消费Ticker, 我们需要创建定时器状态,定时器事件两个辅助类。其中定时器的状态有
- Ready(准备从指定的持续时间开始倒计时)
- Running(从指定持续时间开始递减计数)
- Paused(在剩余的持续时间内暂停)
- Finished已完成,剩余持续时间为0
import 'package:equatable/equatable.dart';
import 'package:meta/meta.dart';
/// 定时器状态
@immutable
abstract class TimerState extends Equatable{
/// 时间
final int duration;
/// 构造方法
const TimerState(this.duration);
@override
List<Object> get props => [this.duration];
}
/// 准备状态
class Ready extends TimerState {
const Ready(int duration) : super(duration);
@override
String toString() => 'Ready { duration: $duration }';
}
/// 暂停状态
class Paused extends TimerState {
const Paused(int duration) : super(duration);
@override
String toString() => 'Paused { duration: $duration }';
}
/// 运行状态
class Running extends TimerState {
const Running(int duration) : super(duration);
@override
String toString() => 'Running { duration: $duration }';
}
/// 完成状态
class Finished extends TimerState{
const Finished() : super(0);
}
所有的State都继承自抽象类TimerState,因为不论在哪个状态,我们都需要知道剩余时间。
4. TimerEvent
我们需要处理的事件有
- Start (通知TimerBloc定时器应该开始)
- Pause (通知TimerBloc计时器应该暂停)
- Resume(通知TimerBloc应该恢复计时器)
- Reset (通知TimerBloc定时器应重置为原始状态)
- Tick (通知TimerBloc需要更新剩余时间)
import 'package:equatable/equatable.dart';
import 'package:meta/meta.dart';
/// 定时器事件
@immutable
abstract class TimerEvent extends Equatable{
const TimerEvent();
@override
List<Object> get props => [];
}
/// 开始时间
class Start extends TimerEvent {
/// 定时器时间
final int duration;
const Start({@required this.duration});
@override
String toString() => 'Start { duration: $duration }';
}
/// 暂停事件
class Paused extends TimerEvent {}
/// 恢复状态
class Resumed extends TimerEvent {}
/// 重置状态
class Reset extends TimerEvent {}
/// 定时器事件
class Tick extends TimerEvent {
/// 当前时间
final int duration;
const Tick({@required this.duration});
@override
List<Object> get props => [this.duration];
@override
String toString() => 'Tick { duration: $duration }';
}
5. TimerBloc 实现
- 初始化状态
Ready(_duration)
- 创建Ticker对象, 用户获取数据流
- 实现mapEventToState方法
- 当event为Start时, 需要开启数据流
- 创建StreamSubscription, 处理流的不同状态, 并在bloc的close方法中关闭它
- 当event为Tick时, 需要处理数据的更新
- 当event为Pause时, 需要停止定时器
- 当event为Resume时, 需要重新启动定时器
- 当event为reset时, 需要重置定时器
import 'dart:async';
import 'package:bloc/bloc.dart';
import 'package:flutter/material.dart';
import 'package:state_manage/timer/ticker.dart';
import './bloc.dart';
/// 定时器Bloc
class TimerBloc extends Bloc<TimerEvent, TimerState> {
/// 定时器时间
final int _duration = 60;
/// 定时器数据流
final Ticker _ticker;
// 流订阅
StreamSubscription<int> _tickerSubscription;
TimerBloc({@required Ticker ticker})
: assert(ticker != null),
_ticker = ticker;
/// 初始化状态
@override
TimerState get initialState => Ready(_duration);
@override
Stream<TimerState> mapEventToState(
TimerEvent event,
) async* {
print('$event');
if (event is Start) {
yield* _mapStartToState(event);
} else if (event is Tick) {
yield* _mapTickToState(event);
} else if (event is Pause) {
yield* _mapPauseToState(event);
} else if (event is Resume) {
yield* _mapResumeToState(event);
} else if (event is Reset) {
yield* _mapResetToState(event);
}
}
@override
Future<void> close() {
_tickerSubscription?.cancel();
return super.close();
}
/// 处理开始事件
Stream<TimerState> _mapStartToState(Start start) async* {
// 运行状态
yield Running(start.duration);
// 取消订阅
_tickerSubscription?.cancel();
// 创建订阅
_tickerSubscription =
_ticker.tick(ticks: start.duration).listen((duration) {
add(Tick(duration: duration));
});
}
/// 处理定时器事件
Stream<TimerState> _mapTickToState(Tick tick) async* {
yield tick.duration > 0 ? Running(tick.duration) : Finished();
}
/// 处理暂停事件
Stream<TimerState> _mapPauseToState(Pause pause) async* {
if (state is Running) {
_tickerSubscription?.pause();
yield Paused(state.duration);
}
}
/// 处理恢复状态
Stream<TimerState> _mapResumeToState(Resume resume) async* {
if (state is Paused) {
_tickerSubscription?.resume();
yield Running(state.duration);
}
}
/// 处理重置状态
Stream<TimerState> _mapResetToState(Reset reset) async* {
_tickerSubscription?.cancel();
yield Ready(_duration);
}
}
6. 界面实现
- 实现定时器显示
timer_test.dart
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:state_manage/timer/bloc/bloc.dart';
import 'package:state_manage/timer/ticker.dart';
/// 定时器
class TimerTest extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
theme: ThemeData(
primaryColor: Color.fromRGBO(109, 234, 255, 1),
accentColor: Color.fromRGBO(72, 74, 126, 1),
brightness: Brightness.dark,
),
title: 'Flutter Timer',
home: BlocProvider(
create: (ctx) => TimerBloc(ticker: Ticker()),
child: Timer(),
),
);
}
}
/// 定时器页面
class Timer extends StatelessWidget{
/// 字体样式
static const TextStyle timerTextStyle = TextStyle(
fontSize: 60,
fontWeight: FontWeight.bold
);
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('Flutter Time')),
body: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
Padding(
padding: EdgeInsets.symmetric(vertical: 100.0),
child: Center(
child: BlocBuilder<TimerBloc, TimerState>(
builder: (ctx, state) {
// 分钟格式化
final String minuteStr = ((state.duration / 60) % 60).floor().toString().padLeft(2, '0');
// 秒数格式化
final String secondStr = (state.duration % 60).floor().toString().padLeft(2, '0');
return Text(
'$minuteStr : $secondStr',
style: Timer.timerTextStyle,
);
},
),
),
)
],
),
);
}
}
- 添加背景
timer_background.dart
import 'package:flutter/material.dart';
import 'package:wave/config.dart';
import 'package:wave/wave.dart';
/// 定时器背景
class Background extends StatelessWidget {
@override
Widget build(BuildContext context) {
return WaveWidget(
config: CustomConfig(
gradients: [
[
Color.fromRGBO(72, 74, 126, 1),
Color.fromRGBO(125, 170, 206, 1),
Color.fromRGBO(184, 189, 245, 0.7)
],
[
Color.fromRGBO(72, 74, 126, 1),
Color.fromRGBO(125, 170, 206, 1),
Color.fromRGBO(172, 182, 219, 0.7)
],
[
Color.fromRGBO(72, 73, 126, 1),
Color.fromRGBO(125, 170, 206, 1),
Color.fromRGBO(190, 238, 246, 0.7)
]
],
durations: [19440, 10800, 6000],
heightPercentages: [0.03, 0.01, 0.02],
gradientBegin: Alignment.bottomCenter,
gradientEnd: Alignment.topCenter
),
size: Size(double.infinity, double.infinity),
waveAmplitude: 25,
backgroundColor: Colors.blue[50],
);
}
}
timer_test.dart
/// 定时器页面
class Timer extends StatelessWidget {
/// 字体样式
static const TextStyle timerTextStyle =
TextStyle(fontSize: 60, fontWeight: FontWeight.bold);
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('Flutter Time')),
body: Stack(
children: <Widget>[
Background(),
Column(
// ... 省略内容
)
],
),
);
}
}
- 添加定时器动作
timer_actions.dart
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:state_manage/timer/bloc/bloc.dart';
/// 动作
class TimerActions extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: _mapStateToActionButtons(timerBloc: BlocProvider.of<TimerBloc>(context)),
);
}
/// 创建动作按钮
/// @param timerBloc 定时器Bloc
List<Widget> _mapStateToActionButtons({TimerBloc timerBloc}) {
// 定时器当前状态
final TimerState currentState = timerBloc.state;
// 根据不同状态返回不同视图
if (currentState is Ready) {
return [FloatingActionButton(
child: Icon(Icons.play_arrow),
onPressed: () => timerBloc.add(Start(duration: currentState.duration)),
)];
} else if (currentState is Running) {
return [
FloatingActionButton(
child: Icon(Icons.pause),
onPressed: () => timerBloc.add(Pause()),
),
FloatingActionButton(
child: Icon(Icons.replay),
onPressed: () => timerBloc.add(Reset()),
)
];
} else if (currentState is Paused) {
return [
FloatingActionButton(
child: Icon(Icons.play_arrow),
onPressed: () => timerBloc.add(Resume()),
),
FloatingActionButton(
child: Icon(Icons.replay),
onPressed: () => timerBloc.add(Reset()),
)
];
} else if (currentState is Finished) {
return [
FloatingActionButton(
child: Icon(Icons.replay),
onPressed: () => timerBloc.add(Reset()),
)
];
} else {
return [];
}
}
}
- 在界面设置动作
timer_test.dart
/// 定时器页面
class Timer extends StatelessWidget {
/// 字体样式
static const TextStyle timerTextStyle =
TextStyle(fontSize: 60, fontWeight: FontWeight.bold);
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('Flutter Timer')),
body: Stack(
children: <Widget>[
Background(),
Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
Padding(
// ...
),
BlocBuilder<TimerBloc, TimerState>(
condition: (previousState, currentState) => currentState.runtimeType != previousState.runtimeType,
builder: (ctx, state) => TimerActions(),
)
],
)
],
),
);
}
}
- 效果图