Reputation: 11
I want to build something like this in flutter.
I have tried using slivers but was not able to get the desired output as the animation goes to the top for tapped widget .
Key ideal features are followings.
1.Behave like list view, but cards should be stacked at the top of screen.
2.The tapped list should be selected and displayed as main card as in the attached image.
import 'dart:math' as math;
class FlashCards extends StatelessWidget {
// This widget is the root of your application.
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(
title: Text("Flash Cards"),
backgroundColor: Colors.black,
),
body: StackedList()),
);
}
}
class StackedList extends StatefulWidget {
@override
_StackedListState createState() => _StackedListState();
}
class _StackedListState extends State<StackedList> {
static const _minHeight = 16.0;
//var _maxHeight = 120.0;
bool _tapped = false;
final List<Color> _colors = Colors.primaries;
@override
Widget build(BuildContext context) => CustomScrollView(
slivers: _colors
.map(
(color) => StackedListChild(
minHeight: _minHeight,
maxHeight: _tapped
? (_colors.indexOf(color) == _colors.length - 1
? MediaQuery.of(context).size.height
: 500)
: (_colors.indexOf(color) == _colors.length - 1
? MediaQuery.of(context).size.height
: 120.0),
pinned: true,
child: GestureDetector(
child: Container(
color: _colors.indexOf(color) == 0
? Colors.black
: _colors[_colors.indexOf(color) - 1],
child: Container(
decoration: BoxDecoration(
borderRadius: BorderRadius.vertical(
top: Radius.circular(_minHeight)),
color: color,
),
),
),
onTap: () => setState(() {
_tapped = !_tapped;
}),
//print(_colors.indexOf(color).toString() + "Tapped"),
),
),
)
.toList(),
);
}
class StackedListChild extends StatelessWidget {
final double minHeight;
final double maxHeight;
final bool pinned;
final bool floating;
final Widget child;
SliverPersistentHeaderDelegate get _delegate => _StackedListDelegate(
minHeight: minHeight, maxHeight: maxHeight, child: child);
const StackedListChild({
Key key,
@required this.minHeight,
@required this.maxHeight,
@required this.child,
this.pinned = false,
this.floating = false,
}) : assert(child != null),
assert(minHeight != null),
assert(maxHeight != null),
assert(pinned != null),
assert(floating != null),
super(key: key);
@override
Widget build(BuildContext context) => SliverPersistentHeader(
key: key, pinned: pinned, floating: floating, delegate: _delegate);
}
class _StackedListDelegate extends SliverPersistentHeaderDelegate {
final double minHeight;
final double maxHeight;
final Widget child;
_StackedListDelegate({
@required this.minHeight,
@required this.maxHeight,
@required this.child,
});
@override
double get minExtent => minHeight;
@override
double get maxExtent => math.max(maxHeight, minHeight);
@override
Widget build(
BuildContext context, double shrinkOffset, bool overlapsContent) {
return new SizedBox.expand(child: child);
}
@override
bool shouldRebuild(_StackedListDelegate oldDelegate) {
return maxHeight != oldDelegate.maxHeight ||
minHeight != oldDelegate.minHeight ||
child != oldDelegate.child;
}
}
As in animation the tapped widget goes to the top and only that get expanded.
Upvotes: 1
Views: 912
Reputation: 16185
To make design you want need to solve several tasks:
How to solve it?
Problem with that is that SliverList is pretty complicated, especially if you are only beginning to learn flutter.
So my solution Is make a bit hacky.
We could use scroll position and get index of top visible card. After that hide it in the sliver list, but show it in stack widget. It would have same visible affect.
import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
void main() => runApp(FlashCards());
class FlashCards extends StatelessWidget {
// This widget is the root of your application.
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(
title: Text("Flash Cards"),
backgroundColor: Colors.black,
),
backgroundColor: Colors.black,
body: StackedList()),
);
}
}
class StackedList extends StatefulWidget {
@override
_StackedListState createState() => _StackedListState();
}
const double closedCardSize = 100;
const double roundRadiusSize = 16;
class _StackedListState extends State<StackedList> {
final List<Color> colors = Colors.primaries;
ScrollController controller = ScrollController();
int firstVisibleCardIndex = 0;
@override
void dispose() {
controller.dispose();
super.dispose();
}
@override
void initState() {
controller.addListener(() {
setState(
() {
firstVisibleCardIndex =
(controller.offset / closedCardSize).floor();
},
);
});
super.initState();
}
@override
Widget build(BuildContext context) {
return Stack(
children: [
Container(
height: closedCardSize + roundRadiusSize,
decoration: BoxDecoration(
borderRadius: BorderRadius.vertical(top: Radius.circular(16)),
color: colors[firstVisibleCardIndex],
),
),
CustomScrollView(
physics: ClampingScrollPhysics(),
controller: controller,
slivers: [
SliverList(
delegate: SliverChildBuilderDelegate(
(context, index) => StackedListChild(
istTransparent: index <= firstVisibleCardIndex,
color: colors[index],
key: ObjectKey(colors[index]),
),
childCount: colors.length - (firstVisibleCardIndex + 1)),
)
],
)
],
);
}
}
class StackedListChild extends StatelessWidget {
const StackedListChild({
Key key,
this.color,
this.istTransparent = false,
}) : super(key: key);
final Color color;
final bool istTransparent;
@override
Widget build(BuildContext context) {
if (istTransparent) {
return SizedBox(
height: closedCardSize,
);
}
return Transform.translate(
offset: Offset(0, roundRadiusSize / 2),
child: SizedOverflowBox(
size: Size(double.infinity, closedCardSize),
child: Container(
height: closedCardSize + roundRadiusSize,
child: Container(
decoration: BoxDecoration(
borderRadius: BorderRadius.vertical(top: Radius.circular(16)),
color: color,
),
),
),
),
);
}
}
Upvotes: 3