Skip to content

Instantly share code, notes, and snippets.

@filiph
Last active October 11, 2022 13:21
Show Gist options
  • Save filiph/11dff46c9adcf51be0b8ac5e18919bed to your computer and use it in GitHub Desktop.
Save filiph/11dff46c9adcf51be0b8ac5e18919bed to your computer and use it in GitHub Desktop.
Fading Scroller (naive implementation in Flutter)
// The accompanying tweet (incl. video) is here:
// https://twitter.com/filiphracek/status/1494213873422974978
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
debugShowCheckedModeBanner: false,
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: const MyHomePage(title: 'Fading Scroller (naive implementation)'),
);
}
}
class MyHomePage extends StatefulWidget {
final String title;
const MyHomePage({
Key? key,
required this.title,
}) : super(key: key);
@override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: FadingScroller(
builder: (context, scrollController) => ListView.builder(
controller: scrollController,
itemCount: 20,
itemBuilder: (context, index) => ListTile(
leading: const Icon(Icons.icecream),
title: Text('Lorem ipsum #${index + 1}'),
),
),
),
);
}
}
class FadingScroller extends StatefulWidget {
final Widget Function(BuildContext context, ScrollController scrollController)
builder;
final ScrollController? scrollController;
const FadingScroller({Key? key, required this.builder, this.scrollController})
: super(key: key);
@override
State<FadingScroller> createState() => _FadingScrollerState();
}
class _FadingScrollerState extends State<FadingScroller> {
late final ScrollController _scrollController;
bool _overlayIsVisible = true;
@override
void initState() {
super.initState();
_scrollController = widget.scrollController ?? ScrollController();
_scrollController.addListener(_handleScrollUpdate);
}
@override
void dispose() {
if (widget.scrollController == null) {
// Only dispise if it was _us_ creating the controller.
_scrollController.dispose();
}
super.dispose();
}
void _handleScrollUpdate() {
if (_scrollController.position.extentAfter <= 0) {
setState(() {
_overlayIsVisible = false;
});
} else {
setState(() {
_overlayIsVisible = true;
});
}
}
@override
Widget build(BuildContext context) {
return Stack(
children: [
widget.builder(context, _scrollController),
IgnorePointer(
child: AnimatedOpacity(
opacity: _overlayIsVisible ? 1 : 0,
duration: const Duration(milliseconds: 500),
child: Container(
decoration: const BoxDecoration(
gradient: LinearGradient(
begin: Alignment.topCenter,
end: Alignment.bottomCenter,
colors: [
Color(0x00FFFFFF),
Color(0xFFFFFFFF),
],
stops: [
0.8,
1,
],
),
),
),
),
),
],
);
}
}
@gazialankus
Copy link

gazialankus commented Mar 22, 2022

Hi @filiph! Thank you for the code!

A little improvement on line 82: if _FadingScrollerState did not create the scroll controller, it should not try to dispose of it because whoever created it will dispose of it.

    if (widget.scrollController == null) {
      _scrollController.dispose();
    }

This prevented some exceptions in my logs.

Again, thank you for the nice widget!

@filiph
Copy link
Author

filiph commented Mar 23, 2022

Ah, good point! Thanks, @gazialankus!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment