Skip to content

Instantly share code, notes, and snippets.

@oravecz
Last active January 14, 2023 03:13
Show Gist options
  • Save oravecz/59aefb48f8a94199fa674e036cf62bd9 to your computer and use it in GitHub Desktop.
Save oravecz/59aefb48f8a94199fa674e036cf62bd9 to your computer and use it in GitHub Desktop.
Bloc w/ Animated Items

Bloc w/ Animated Items

Created with <3 with dartpad.dev.

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