Created with <3 with dartpad.dev.
Last active
January 14, 2023 03:13
-
-
Save oravecz/59aefb48f8a94199fa674e036cf62bd9 to your computer and use it in GitHub Desktop.
Bloc w/ Animated Items
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import 'package:equatable/equatable.dart'; | |
import 'package:flutter_bloc/flutter_bloc.dart'; | |
import 'package:flutter/material.dart'; | |
typedef Op = MyStateItem Function(MyStateItem stateItem); | |
class MyState extends Equatable { | |
const MyState(this.items); | |
final List<MyStateItem> items; | |
Iterable<MyStateItem> _operateOn( | |
MyStateItem stateItem, | |
Op op, | |
) => | |
items.map( | |
(item) => item == stateItem ? op(item) : item, | |
); | |
Iterable<MyStateItem> toggle(MyStateItem item) => _operateOn( | |
item, | |
(item) => item.copyWith(selected: !item.selected), | |
); | |
@override | |
List<Object?> get props => [items]; | |
} | |
class MyStateItem extends Equatable { | |
const MyStateItem({required this.title, required this.selected}); | |
final String title; | |
final bool selected; | |
MyStateItem copyWith({ | |
String? title, | |
bool? selected, | |
}) { | |
return MyStateItem( | |
title: title ?? this.title, | |
selected: selected ?? this.selected, | |
); | |
} | |
@override | |
List<Object?> get props => [title, selected]; | |
} | |
class MyCubit extends Cubit<MyState> { | |
MyCubit(super.initialState); | |
void toggle(MyStateItem item) { | |
emit(MyState(state.toggle(item).toList())); | |
} | |
} | |
class MyExample extends StatelessWidget { | |
@override | |
Widget build(BuildContext context) { | |
return BlocBuilder<MyCubit, MyState>(builder: (context, state) { | |
return ListView.builder( | |
itemCount: state.items.length, | |
itemBuilder: (context, index) { | |
return MyListItem(index: index); | |
}, | |
); | |
}); | |
} | |
} | |
class MyListItem extends StatefulWidget { | |
const MyListItem({required this.index}); | |
final int index; | |
@override | |
State<MyListItem> createState() { | |
return MyListItemState(); | |
} | |
} | |
// No I can't use ExpansionTile - my actual | |
// use case is more complicated with multiple | |
// expansion areas | |
class MyListItemState extends State<MyListItem> { | |
// Including this to show that I need a StatefulWidget | |
late final TextEditingController _controller; | |
@override | |
Widget build(BuildContext context) { | |
return BlocBuilder<MyCubit, MyState>( | |
builder: (context, state) { | |
final bloc = context.read<MyCubit>(); | |
final item = state.items[widget.index]; | |
return GestureDetector( | |
onTap: () { | |
bloc.toggle(item); | |
}, | |
child: ListTile( | |
title: Text(item.title), | |
subtitle: ColoredBox( | |
color: Colors.lightBlue, | |
child: AnimatedSize( | |
clipBehavior: Clip.hardEdge, | |
duration: const Duration(milliseconds: 750), | |
child: item.selected | |
? Column(children: [ | |
const SizedBox(height: 12), | |
TextField( | |
decoration: const InputDecoration( | |
border: OutlineInputBorder(), | |
filled: true, | |
), | |
controller: _controller, | |
), | |
]) | |
: const SizedBox.shrink(), | |
), | |
), | |
), | |
); | |
}, | |
); | |
} | |
@override | |
void initState() { | |
super.initState(); | |
_controller = TextEditingController(); | |
} | |
@override | |
void dispose() { | |
_controller.dispose(); | |
super.dispose(); | |
} | |
} | |
void main() => runApp(MyApp()); | |
class MyApp extends StatelessWidget { | |
@override | |
Widget build(BuildContext context) { | |
return BlocProvider<MyCubit>( | |
create: (_) => MyCubit(const MyState([ | |
MyStateItem(title: 'A', selected: false), | |
MyStateItem(title: 'B', selected: false), | |
MyStateItem(title: 'C', selected: false), | |
])), | |
child: MaterialApp( | |
title: 'Flutter Demo', | |
debugShowCheckedModeBanner: false, | |
theme: ThemeData( | |
primarySwatch: Colors.blue, | |
), | |
home: const MyHomePage(title: 'Bloc w/ List Item Animation'), | |
), | |
); | |
} | |
} | |
class MyHomePage extends StatelessWidget { | |
const MyHomePage({ | |
super.key, | |
required this.title, | |
}); | |
final String title; | |
@override | |
Widget build(BuildContext context) { | |
return Scaffold( | |
appBar: AppBar( | |
title: Text(title), | |
), | |
body: MyExample(), | |
); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment