Skip to content

Instantly share code, notes, and snippets.

@slightfoot
Last active April 17, 2024 16:25
Show Gist options
  • Star 4 You must be signed in to star a gist
  • Fork 2 You must be signed in to fork a gist
  • Save slightfoot/a72cfdb4d40632b15fb388fba5bc1ad0 to your computer and use it in GitHub Desktop.
Save slightfoot/a72cfdb4d40632b15fb388fba5bc1ad0 to your computer and use it in GitHub Desktop.
Sliver Magic - by Simon Lightfoot - #HumpdayQandA - 19th July 2023 - #Flutter #Dart - https://www.youtube.com/live/zNOZD0JSFQY?feature=share&t=7315
// MIT License
//
// Copyright (c) 2023 Simon Lightfoot
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//
import 'dart:math' as math;
import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
void main() {
runApp(const RevealApp());
// runApp(const HideLucasApp());
}
@immutable
class RevealApp extends StatelessWidget {
const RevealApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
theme: ThemeData.dark(useMaterial3: true),
home: Material(
child: ScrollConfiguration(
behavior: const ScrollBehavior().copyWith(
dragDevices: {
PointerDeviceKind.mouse,
PointerDeviceKind.touch,
},
overscroll: false,
),
child: const CustomScrollView(
slivers: [
SliverItem(color: Colors.green),
SliverItem(color: Colors.green),
SliverScrollReveal(
height: 400.0,
child: BoxItem(color: Colors.red),
),
SliverItem(color: Colors.blue),
SliverItem(color: Colors.blue),
],
),
),
),
);
}
}
class SliverScrollReveal extends StatelessWidget {
const SliverScrollReveal({
super.key,
required this.height,
required this.child,
});
final double height;
final Widget child;
@override
Widget build(BuildContext context) {
return SliverLayoutBuilder(
builder: (BuildContext context, SliverConstraints constraints) {
final width = constraints.crossAxisExtent;
final actualHeight = math.min(
height,
math.max(constraints.remainingPaintExtent, 0.0),
);
return SliverToBoxAdapter(
child: ClipRect(
child: Transform.translate(
offset: Offset(0.0, constraints.scrollOffset),
child: SizedBox(
height: actualHeight,
child: OverflowBox(
alignment: constraints.scrollOffset > 0
? Alignment.topLeft
: Alignment.bottomLeft,
minWidth: width,
maxWidth: width,
minHeight: height,
maxHeight: height,
child: Stack(
fit: StackFit.expand,
children: [
child,
Positioned.fill(
child: Align(
alignment: constraints.scrollOffset > 0
? Alignment.topLeft
: Alignment.bottomLeft,
child: Padding(
padding:
const EdgeInsets.symmetric(horizontal: 12.0),
child: Text(
'axisDirection: ${constraints.axisDirection}\n'
'growthDirection: ${constraints.growthDirection}\n'
'userScrollDirection: ${constraints.userScrollDirection}\n'
'scrollOffset: ${constraints.scrollOffset.toStringAsFixed(3)}\n'
'precedingScrollExtent: ${constraints.precedingScrollExtent.toStringAsFixed(3)}\n'
'overlap: ${constraints.overlap.toStringAsFixed(3)}\n'
'remainingPaintExtent: ${constraints.remainingPaintExtent.toStringAsFixed(3)}\n'
'crossAxisExtent: ${constraints.crossAxisExtent.toStringAsFixed(3)}\n'
'crossAxisDirection: ${constraints.crossAxisDirection}\n'
'viewportMainAxisExtent: ${constraints.viewportMainAxisExtent.toStringAsFixed(3)}\n',
style: const TextStyle(
color: Colors.black,
fontWeight: FontWeight.w500,
fontSize: 16.0,
),
),
),
),
),
],
),
),
),
),
),
);
},
);
}
}
@immutable
class BoxItem extends StatelessWidget {
const BoxItem({
super.key,
required this.color,
});
final Color color;
@override
Widget build(BuildContext context) {
return ColoredBox(
color: color,
child: const AspectRatio(
aspectRatio: 1.3,
child: Placeholder(
color: Colors.white,
),
),
);
}
}
@immutable
class SliverItem extends StatelessWidget {
const SliverItem({
super.key,
required this.color,
});
final Color color;
@override
Widget build(BuildContext context) {
return SliverPadding(
padding: const EdgeInsets.symmetric(horizontal: 12.0),
sliver: SliverToBoxAdapter(
child: BoxItem(color: color),
),
);
}
}
@immutable
class HideLucasApp extends StatefulWidget {
const HideLucasApp({super.key});
@override
State<HideLucasApp> createState() => _HideLucasAppState();
}
class _HideLucasAppState extends State<HideLucasApp> {
final _controller = ScrollController();
final _items = [...Colors.primaries, ...Colors.accents];
late final _keys = List.generate(
_items.length,
(index) => GlobalKey(),
);
bool _hidden = false;
@override
void initState() {
super.initState();
_controller.addListener(_onScrollChanged);
}
@override
void dispose() {
_controller.dispose();
super.dispose();
}
void _onScrollChanged() {
final offset = _controller.hasClients && _controller.position.hasPixels
? _controller.position.pixels
: 0.0;
//print(offset);
if (_hidden == false && offset > 1400.0) {
double height = 0;
for (int i = 0; i < 3; i++) {
final render =
_keys[i].currentContext!.findRenderObject() as RenderSliver;
final childHeight = render.geometry?.scrollExtent ?? 0.0;
height += childHeight;
}
setState(() {
_hidden = true;
_controller.position.correctBy(-height);
print('trigger $height');
});
}
}
@override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
theme: ThemeData.dark(useMaterial3: true),
home: Material(
child: CustomScrollView(
controller: _controller,
slivers: [
for (final (index, color)
in _items.skip(_hidden ? 3 : 0).indexed) //
if (color == Colors.blue)
SliverToBoxAdapter(
child: Builder(
builder: (BuildContext context) {
final scrollable = Scrollable.of(context);
return AnimatedBuilder(
animation: scrollable.position,
builder: (BuildContext context, Widget? child) {
// print('scrollable: ${scrollable.position.pixels}');
return child!;
},
child: BoxItem(color: color),
);
},
),
)
else
SliverItem(
key: _keys[index],
color: color,
),
],
),
),
);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment