1. 依賴
dependencies:
flutter_bloc: ^2.1.1
equatable: ^1.0.1
wave: ^0.0.8
2. Ticker
Ticker 用于產(chǎn)生定時器的數(shù)據(jù)流塑荒。
/// 定時器數(shù)據(jù)源
class Ticker {
/// 定時器數(shù)據(jù)源
/// @param ticks 時間
Stream<int> tick({int ticks}){
return Stream.periodic(Duration(seconds: 1), (x) => ticks - x - 1).take(ticks);
}
}
3. TimerBloc
創(chuàng)建 TimerBloc 用于消費Ticker, 我們需要創(chuàng)建定時器狀態(tài)铣缠,定時器事件兩個輔助類。其中定時器的狀態(tài)有
- Ready(準備從指定的持續(xù)時間開始倒計時)
- Running(從指定持續(xù)時間開始遞減計數(shù))
- Paused(在剩余的持續(xù)時間內(nèi)暫停)
- Finished已完成弧圆,剩余持續(xù)時間為0
import 'package:equatable/equatable.dart';
import 'package:meta/meta.dart';
/// 定時器狀態(tài)
@immutable
abstract class TimerState extends Equatable{
/// 時間
final int duration;
/// 構(gòu)造方法
const TimerState(this.duration);
@override
List<Object> get props => [this.duration];
}
/// 準備狀態(tài)
class Ready extends TimerState {
const Ready(int duration) : super(duration);
@override
String toString() => 'Ready { duration: $duration }';
}
/// 暫停狀態(tài)
class Paused extends TimerState {
const Paused(int duration) : super(duration);
@override
String toString() => 'Paused { duration: $duration }';
}
/// 運行狀態(tài)
class Running extends TimerState {
const Running(int duration) : super(duration);
@override
String toString() => 'Running { duration: $duration }';
}
/// 完成狀態(tài)
class Finished extends TimerState{
const Finished() : super(0);
}
所有的State都繼承自抽象類TimerState俏让,因為不論在哪個狀態(tài),我們都需要知道剩余時間鳍徽。
4. TimerEvent
我們需要處理的事件有
- Start?(通知TimerBloc定時器應(yīng)該開始)
- Pause?(通知TimerBloc計時器應(yīng)該暫停)
- Resume(通知TimerBloc應(yīng)該恢復(fù)計時器)
- Reset?(通知TimerBloc定時器應(yīng)重置為原始狀態(tài))
- 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 {}
/// 恢復(fù)狀態(tài)
class Resumed extends TimerEvent {}
/// 重置狀態(tài)
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 實現(xiàn)
- 初始化狀態(tài)
Ready(_duration)
- 創(chuàng)建Ticker對象, 用戶獲取數(shù)據(jù)流
- 實現(xiàn)mapEventToState方法
- 當event為Start時, 需要開啟數(shù)據(jù)流
- 創(chuàng)建StreamSubscription, 處理流的不同狀態(tài), 并在bloc的close方法中關(guān)閉它
- 當event為Tick時, 需要處理數(shù)據(jù)的更新
- 當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;
/// 定時器數(shù)據(jù)流
final Ticker _ticker;
// 流訂閱
StreamSubscription<int> _tickerSubscription;
TimerBloc({@required Ticker ticker})
: assert(ticker != null),
_ticker = ticker;
/// 初始化狀態(tài)
@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* {
// 運行狀態(tài)
yield Running(start.duration);
// 取消訂閱
_tickerSubscription?.cancel();
// 創(chuàng)建訂閱
_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);
}
}
/// 處理恢復(fù)狀態(tài)
Stream<TimerState> _mapResumeToState(Resume resume) async* {
if (state is Paused) {
_tickerSubscription?.resume();
yield Running(state.duration);
}
}
/// 處理重置狀態(tài)
Stream<TimerState> _mapResetToState(Reset reset) async* {
_tickerSubscription?.cancel();
yield Ready(_duration);
}
}
6. 界面實現(xiàn)
- 實現(xiàn)定時器顯示
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');
// 秒數(shù)格式化
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(
// ... 省略內(nèi)容
)
],
),
);
}
}
- 添加定時器動作
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)),
);
}
/// 創(chuàng)建動作按鈕
/// @param timerBloc 定時器Bloc
List<Widget> _mapStateToActionButtons({TimerBloc timerBloc}) {
// 定時器當前狀態(tài)
final TimerState currentState = timerBloc.state;
// 根據(jù)不同狀態(tài)返回不同視圖
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 [];
}
}
}
- 在界面設(shè)置動作
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(),
)
],
)
],
),
);
}
}
- 效果圖
效果圖.gif