// 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 createState() => _HideLucasAppState(); } class _HideLucasAppState extends State { 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, ), ], ), ), ); } }