携手创造,一起成长!这是我参与「日新计划 8 月更文挑战」的第19天,点击查看活动详情
本文首要运用bloc 形式创立一个倒计时,包括暂停重置等状况
1. 分析
首先就是如下图所示,是一个倒计时,供给了开端,暂停,继续,重置几种状况,一起还有一个完结状况当,倒计时完毕后到达重置效果。
一起有一个计时器,咱们能够订阅它,之后发送咱们的秒数。这个时分能够运用stream
流就行履行。关于UI方面,咱们会依据状况展现不同按钮。
2. periodic
咱们运用Stream
初始化一个流,每秒履行1次,履行次数经过办法传进来,之后转化下剩下秒数。
class TimeTicker{
const TimeTicker();
Stream<int> tick({required int ticks}){
return Stream.periodic(const Duration(seconds: 1),(count) => ticks - count -1).take(ticks);
}
}
这儿关于必填的参数咱们运用required
修饰,这样明确语意
3. State
咱们之前分析了计时器的几种状况,因而咱们能够设置几种状况
-
CalculateInitial
:准备从指定的时刻开端倒计时,用户能够进行倒计时。 -
TimerRunInProgress
:正在倒计时中,暂停和重置计时器而且能够看到剩下的时刻。 -
TimerRunPause
:暂停,恢复倒计时和重置计时器。 -
TimerRunComplete
:完毕,重置计时器。
part of 'calculate_bloc.dart';
abstract class CalculateState extends Equatable {
final int duration;
const CalculateState(this.duration);
@override
List<Object> get props => [duration];
}
class CalculateInitial extends CalculateState {
const CalculateInitial(super.duration);
}
class TimerRunPause extends CalculateState {
const TimerRunPause(super.duration);
}
class TimerRunInProgress extends CalculateState {
const TimerRunInProgress(super.duration);
}
class TimerRunComplete extends CalculateState {
const TimerRunComplete() : super(0);
}
注意一切的states
都承继自抽象基类CalculateState
,它有一个duration特点。这是由于不论TimerBloc
在哪里,咱们都想知道还剩下多少时刻。另外CalculateState
还承继了Equatable
用于确保假如有相同状况不会再次触发重建。
4. Event
关于咱们Event事情驱动,咱们对应state也有相对应的event
-
TimerStarted
:告诉Bloc开端计时。 -
TimerPaused
:告诉Bloc暂停。 -
TimerResumed
:告诉Bloc恢复计时。 -
TimerReset
:告诉Bloc重置计时器到原来的状况。 -
TimerTicked
:告诉Bloc一个tick现已产生,需求更新它对应的状况。
part of 'calculate_bloc.dart';
abstract class CalculateEvent extends Equatable {
const CalculateEvent();
@override
List<Object> get props => [];
}
class TimerStarted extends CalculateEvent {
final int duration;
const TimerStarted({required this.duration });
}
class TimerTicked extends CalculateEvent {
const TimerTicked({required this.duration});
final int duration;
@override
List<Object> get props => [duration];
}
class TimerPaused extends CalculateEvent {
const TimerPaused();
}
class TimerResumed extends CalculateEvent {
const TimerResumed();
}
class TimerReset extends CalculateEvent {
const TimerReset();
5. Bloc
咱们这儿实现逻辑,针对event,处理逻辑发送对应的state
5.1 初始化
part 'calculate_event.dart';
part 'calculate_state.dart';
class CalculateBloc extends Bloc<CalculateEvent, CalculateState> {
static const int _duration = 60;
final TimeTicker _ticker;
StreamSubscription<int>? _tickerStreamSubscription;
CalculateBloc({required TimeTicker ticker}) :_ticker = ticker , super(const CalculateInitial(_duration)) {
咱们界说倒计时和计时器,一起界说一个订阅者用于订阅Stream,从而控制咱们的Stream是否暂停,继续以及毁掉。
咱们封闭的时分需求,封闭订阅
/// 封闭的时分 封闭订阅
@override
Future<void> close() async {
_tickerStreamSubscription?.cancel();
return super.close();
}
5.2 TimerStarted
假如CalculateBloc
收到TimerStarted
事情,它会发送一个带有开端时刻的TimerRunInProgress
状况。此外,假如现已打开了_tickerSubscription
咱们需求撤销它释放内存。咱们也需求在TimerBloc
中重载close
办法,当TimerBloc
被封闭的时分能撤销_tickerSubscription
。最终咱们监听_ticker.tick
流而且在每个触发时刻咱们增加一个包括剩下时刻的TimerTicked
事情。
/// 开端:1。发送一个带有开端时刻的状况
void _onStarted(TimerStarted event, Emitter<CalculateState> emit) {
emit(TimerRunInProgress(event.duration));
_tickerStreamSubscription?.cancel();
_tickerStreamSubscription = _ticker
.tick(ticks: event.duration)
.listen((event) => add(TimerTicked(duration: event)));
}
5.3 TimerTicked
每次接收到TimerTicked
事情,假如剩下时刻大于0,咱们需求发送一个带有新的剩下时刻的TimerRunInProgress
事情来更新状况。否则,假如剩下时刻等于0,那么倒计时现已完毕,咱们需求发送TimerRunComplete
状况。
/// 计时中,判别是否剩下时刻
void _onTicked(TimerTicked event, Emitter<CalculateState> emit){
emit(event.duration>0 ? TimerRunInProgress(event.duration): const TimerRunComplete());
}
5.4 TimerPaused
在_onPaused
中假如咱们TimerBloc
中的状况是TimerRunInProgress
,咱们能够暂停_tickerSubscription
而且发送一个带有其时时刻的TimerRunPause
状况。
/// 暂停,判别其时是否state为TimerRunInprogress类型,之后暂停订阅,发送暂停state
void _onPaused(TimerPaused event, Emitter<CalculateState> emit) {
if( state is TimerRunInProgress) {
_tickerStreamSubscription?.pause();
emit(TimerRunPause(state.duration));
}
}
5.5 TimerResumed
TimerResumed
事情处理和TimerPaused
事情的处理十分相似。假如CalculateBloc
的state
是TimerRunPause
而且它接收到一个TimerResumed
事情,它恢复_tickerSubscription
而且发送一个带有其时时刻的TimerRunInProgress
状况。
/// 恢复,咱们判别其时state为暂停状况后,进行恢复jiant
void _onResumed(TimerResumed event, Emitter<CalculateState> emit){
if( state is TimerRunPause) {
_tickerStreamSubscription?.resume();
emit(TimerRunInProgress(state.duration));
}
}
5.6 TimerReset
假如CalculateBloc
接收到一个TimerReset
事情,它需求撤销其时的_tickerSubscription
这样它就不会被计时器告诉,而且发送一个带有初始时刻的CalculateInitial
状况。
/// 重置,初始化
void _onReset(TimerReset event ,Emitter<CalculateState> emit) {
_tickerStreamSubscription?.cancel();
emit(const CalculateInitial(_duration));
}
6. UI
咱们经过BlocProvider相关咱们的页面和bloc
class TimerPage extends StatelessWidget {
const TimerPage({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return BlocProvider(
create: (_) => CalculateBloc(ticker: const TimeTicker()),
child: const TimerView(),
);
}
}
6.1 TimerPage
依据布局咱们运用Stack,方便咱们增加背景什么的。
class TimerView extends StatelessWidget {
const TimerView({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('倒计时')),
body: Stack(
children: [
Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: const <Widget>[
Padding(
padding: EdgeInsets.symmetric(vertical: 100.0),
child: Center(child: TimerText()),
),
Actions(),
],
),
],
),
);
}
}
6.2 TimerText
经过context获取Bloc,之后展现在Text中
class TimerText extends StatelessWidget {
const TimerText({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
final duration = context.select((CalculateBloc bloc) => bloc.state.duration);
final minutesStr =
((duration / 60) % 60).floor().toString().padLeft(2, '0');
final secondsStr = (duration % 60).floor().toString().padLeft(2, '0');
return Text(
'$minutesStr:$secondsStr',
style: Theme.of(context).textTheme.headline1,
);
}
}
6.3 Actions
class Actions extends StatelessWidget {
const Actions({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return BlocBuilder<CalculateBloc, CalculateState>(
buildWhen: (prev, state) => prev.runtimeType != state.runtimeType,
builder: (context, state) {
return Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
if (state is CalculateInitial) ...[
FloatingActionButton(
heroTag: null,
child: const Icon(Icons.play_arrow),
onPressed: () => context
.read<CalculateBloc>()
.add(TimerStarted(duration: state.duration)),
),
],
if (state is TimerRunInProgress) ...[
FloatingActionButton(
heroTag: null,
child: const Icon(Icons.pause),
onPressed: () => context.read<CalculateBloc>().add(const TimerPaused()),
),
FloatingActionButton(
heroTag: null,
child: const Icon(Icons.replay),
onPressed: () => context.read<CalculateBloc>().add(const TimerReset()),
),
],
if (state is TimerRunPause) ...[
FloatingActionButton(
heroTag: null,
child: const Icon(Icons.play_arrow),
onPressed: () => context.read<CalculateBloc>().add(const TimerResumed()),
),
FloatingActionButton(
heroTag: null,
child: const Icon(Icons.replay),
onPressed: () => context.read<CalculateBloc>().add(const TimerReset()),
),
],
if (state is TimerRunComplete) ...[
FloatingActionButton(
heroTag: null,
child: const Icon(Icons.replay),
onPressed: () => context.read<CalculateBloc>().add(const TimerReset()),
),
]
],
);
},
);
}
}
Actions
小部件只是另一个StatelessWidget
,每逢咱们获取到一个新的TimerState时,它运用BlocBuilder
来重建UI。Actions
运用context.read<TimerBloc>()
拜访TimerBloc
实例而且依据其时TimerBloc
状况回来不同的FloatingActionButtons
。每个FloatingActionButtons
的onPressed
回调中都增加一个事情告诉CalculateBloc
。
假如你想纤细的控制,当builder
办法被调用的时分你能够供给一个可选的buildWhen
到BlocBuilder
。buildWhen
带着前一个bloc状况和其时的bloc状况,而且回来一个boolean
值。假如buildWhen
回来true
,将调用带有state
的builder
而且重建组件。假如buildWhen
回来false
,带有state
的builder
将不会被调用而且不会被重建。
这种情况下,咱们不想每次都重新构建Actions
组件,这样功率很低。咱们只想在TimeState
的runtimeType
改动的时分(TimerInitial => TimerRunInProgress, TimerRunInProgress => TimerRunPause, 等…)重建Actions
。
7. 小结
关于一些多状况的场景,咱们能够承继抽象类
,每种状况代表一种情况,都持有抽象类参数duration
。与之对应的是event
,经过event
发送不同state刷新界面。一起咱们经过stream
能够监听,依据传递状况做出对应操作,最终做到了 event-state
一一对应。