Skip to content

Instantly share code, notes, and snippets.

@HansMuller
Created July 28, 2017 18:46
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save HansMuller/aedeb2df3292f492fb71a0860f701303 to your computer and use it in GitHub Desktop.
Save HansMuller/aedeb2df3292f492fb71a0860f701303 to your computer and use it in GitHub Desktop.
import 'dart:math' as math;
import 'package:flutter/material.dart';
const _kHeadingGap = 8.0;
const _kMinHeadingsHeight = 90.0;
// Scroll animation from full-screen section heading column layout to row layout.
const Duration _kScrollDuration = const Duration(milliseconds: 400);
const Curve _kScrollCurve = Curves.fastOutSlowIn;
const TextStyle _kTitleStyle = const TextStyle(
inherit: false,
fontSize: 24.0,
fontWeight: FontWeight.w500,
color: Colors.white,
textBaseline: TextBaseline.alphabetic,
);
class Section {
const Section({ this.title, this.color });
final String title;
final Color color;
}
const List<Section> allSections = const <Section>[
const Section(
title: 'ONE',
color: Colors.indigo,
),
const Section(
title: 'TWO',
color: Colors.deepPurple,
),
const Section(
title: 'FREE',
color: Colors.amber,
),
const Section(
title: 'FOUR',
color: Colors.lightBlue,
),
const Section(
title: 'FIVE',
color: Colors.teal,
),
];
class SectionItem extends StatelessWidget {
SectionItem({ Key key, this.section, this.index }) : super(key: key);
final Section section;
final int index;
@override
build(BuildContext context) {
return new Container(
margin: const EdgeInsets.symmetric(vertical: 8.0),
height: 48.0, // height + vertical margins = 64.0, see SliverFixedExtentList itemExtent below
color: section.color,
alignment: FractionalOffset.center,
child: new Text('${section.title} Item $index', style: _kTitleStyle),
);
}
}
class SectionHeading extends StatelessWidget {
SectionHeading({ Key key, this.section }) : super(key: key);
final Section section;
@override
Widget build(BuildContext context) {
return new Container(
decoration: new BoxDecoration(
borderRadius: new BorderRadius.circular(4.0),
color: section.color,
),
alignment: FractionalOffset.center,
child: new Text(section.title, style: _kTitleStyle),
);
}
}
class SectionHeadingsLayout extends MultiChildLayoutDelegate {
SectionHeadingsLayout({
this.headingCount,
this.maxHeight,
this.selectedIndex,
});
final int headingCount;
final double maxHeight;
final int selectedIndex;
@override
void performLayout(Size size) {
final double headingHeight = (maxHeight - _kHeadingGap * (headingCount - 1)) / headingCount;
final double tColumnToRow = 1.0 - (size.height - _kMinHeadingsHeight) / (maxHeight - _kMinHeadingsHeight);
final double colHeadingHeight = (maxHeight - _kHeadingGap * (headingCount - 1)) / headingCount;
final Size colHeadingSize = new Size(size.width, colHeadingHeight);
final Size rowHeadingSize = new Size(size.width, _kMinHeadingsHeight);
double columnY = 0.0;
double rowX = -1.0 * selectedIndex * rowHeadingSize.width;
for (int index = 0; index < headingCount; index++) {
final Rect colHeadingRect = new Offset(0.0, columnY) & colHeadingSize;
final Rect rowHeadingRect = new Offset(rowX, 0.0) & rowHeadingSize;
final Rect headingRect = Rect.lerp(colHeadingRect, rowHeadingRect, tColumnToRow);
final String headingId = 'heading$index';
final Size headingSize = layoutChild(headingId, new BoxConstraints.tight(headingRect.size));
positionChild(headingId, headingRect.topLeft);
columnY += headingSize.height + _kHeadingGap;
rowX += headingSize.width;
}
}
@override
bool shouldRelayout(SectionHeadingsLayout oldDelegate) {
return headingCount != oldDelegate.headingCount
|| maxHeight != oldDelegate.maxHeight
|| selectedIndex != oldDelegate.selectedIndex;
}
}
class SliverSectionHeadingsDelegate extends SliverPersistentHeaderDelegate {
SliverSectionHeadingsDelegate({ this.maxHeight, this.child });
final double maxHeight;
final Widget child;
@override double get minExtent => _kMinHeadingsHeight;
@override double get maxExtent => math.max(maxHeight, _kMinHeadingsHeight);
@override
Widget build(BuildContext context, double shrinkOffset, bool overlapsContent) {
return new SizedBox.expand(child: child);
}
@override
bool shouldRebuild(SliverSectionHeadingsDelegate oldDelegate) {
return maxHeight != oldDelegate.maxHeight || child != oldDelegate.child;
}
}
class PosseDemo extends StatefulWidget {
@override
PosseDemoState createState() => new PosseDemoState();
}
class PosseDemoState extends State<PosseDemo> {
ScrollController _scrollController = new ScrollController();
int _selectedIndex = 2;
@override
Widget build(BuildContext context) {
final MediaQueryData mediaQueryData = MediaQuery.of(context);
final double screenHeight = mediaQueryData.size.height;
final double statusBarHeight = mediaQueryData.padding.top;
final double maxHeadingsHeight = screenHeight - statusBarHeight;
List<Widget> sectionHeadings = new List<Widget>(allSections.length);
for (int index = 0; index < allSections.length; index++) {
sectionHeadings[index] = new LayoutId(
id: 'heading$index',
child: new GestureDetector(
onTap: () {
setState(() {
_selectedIndex = index;
});
final double offset = maxHeadingsHeight - _kMinHeadingsHeight;
_scrollController.animateTo(offset, curve: _kScrollCurve, duration: _kScrollDuration);
},
child: new SectionHeading(section: allSections[index]),
),
);
}
return new Scaffold(
body: new Padding(
padding: new EdgeInsets.only(top: statusBarHeight),
child: new CustomScrollView(
controller: _scrollController,
slivers: <Widget>[
new SliverPersistentHeader(
pinned: true,
delegate: new SliverSectionHeadingsDelegate(
maxHeight: maxHeadingsHeight,
child: new CustomMultiChildLayout(
delegate: new SectionHeadingsLayout(
headingCount: allSections.length,
maxHeight: maxHeadingsHeight,
selectedIndex: _selectedIndex,
),
children: sectionHeadings,
),
),
),
new SliverFixedExtentList(
itemExtent: 64.0,
delegate: new SliverChildBuilderDelegate(
(BuildContext context, int index) {
return new SectionItem(
section: allSections[_selectedIndex],
index: index,
);
},
),
)
],
),
),
);
}
}
void main() {
runApp(new MaterialApp(home: new PosseDemo()));
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment