Created with <3 with dartpad.dev.
Last active
January 10, 2024 09:56
-
-
Save epatel/3215f6ab171814cc48b9978b883de7ed to your computer and use it in GitHub Desktop.
Sync Scroll
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import 'package:flutter/material.dart'; | |
const Color darkBlue = Color.fromARGB(255, 18, 32, 47); | |
void main() { | |
runApp(MyApp()); | |
} | |
class MyApp extends StatelessWidget { | |
@override | |
Widget build(BuildContext context) { | |
return MaterialApp( | |
theme: ThemeData.dark().copyWith( | |
scaffoldBackgroundColor: darkBlue, | |
), | |
debugShowCheckedModeBanner: false, | |
home: const Scaffold( | |
body: MainPage(title: 'Sync Scroll'), | |
), | |
); | |
} | |
} | |
class MainPage extends StatefulWidget { | |
const MainPage({super.key, required this.title}); | |
final String title; | |
@override | |
State<MainPage> createState() => _MainPageState(); | |
} | |
class _MainPageState extends State<MainPage> { | |
final _controller = ScrollController(); | |
final GlobalKey _main = GlobalKey(); | |
final GlobalKey _left = GlobalKey(); | |
final GlobalKey _right = GlobalKey(); | |
double _heightMain = 0; | |
double _heightLeft = 0; | |
double _heightRight = 0; | |
double _offsetLeft = 0; | |
double _offsetRight = 0; | |
void setupSizes() { | |
_heightMain = | |
(_main.currentContext?.findRenderObject() as RenderBox).size.height; | |
_heightLeft = | |
(_left.currentContext?.findRenderObject() as RenderBox).size.height; | |
_heightRight = | |
(_right.currentContext?.findRenderObject() as RenderBox).size.height; | |
} | |
@override | |
void initState() { | |
super.initState(); | |
WidgetsBinding.instance.addPostFrameCallback((_) => setupSizes()); | |
_controller.addListener(() { | |
if (_heightLeft > _heightRight) { | |
final end = _heightMain + _controller.offset; | |
if (end > _heightRight) { | |
setState(() { | |
_offsetRight = end - _heightRight; | |
}); | |
} else if (_offsetRight != 0) { | |
setState(() { | |
_offsetRight = 0; | |
}); | |
} | |
} else { | |
final end = _heightMain + _controller.offset; | |
if (end > _heightLeft) { | |
setState(() { | |
_offsetLeft = end - _heightLeft; | |
}); | |
} else if (_offsetLeft != 0) { | |
setState(() { | |
_offsetLeft = 0; | |
}); | |
} | |
} | |
}); | |
} | |
@override | |
void dispose() { | |
_controller.dispose(); | |
super.dispose(); | |
} | |
@override | |
Widget build(BuildContext context) { | |
return Scaffold( | |
appBar: AppBar( | |
backgroundColor: Theme.of(context).colorScheme.inversePrimary, | |
title: Text(widget.title), | |
), | |
body: NotificationListener<SizeChangedLayoutNotification>( | |
onNotification: (SizeChangedLayoutNotification notification) { | |
WidgetsBinding.instance.addPostFrameCallback((_) => setupSizes()); | |
return false; | |
}, | |
child: SizeChangedLayoutNotifier( | |
child: SingleChildScrollView( | |
key: _main, | |
controller: _controller, | |
child: Row( | |
crossAxisAlignment: CrossAxisAlignment.start, | |
children: [ | |
Expanded( | |
child: Column( | |
key: _left, | |
children: [ | |
SizedBox( | |
height: _offsetLeft, | |
width: 2, | |
), | |
...List.generate( | |
50, | |
(index) => ListTile( | |
title: Text('Item Left ${index + 1}'), | |
), | |
), | |
], | |
), | |
), | |
Expanded( | |
child: Column( | |
key: _right, | |
children: [ | |
SizedBox( | |
height: _offsetRight, | |
width: 2, | |
), | |
...List.generate( | |
130, | |
(index) => ListTile( | |
title: Text('Item Right ${index + 1}'), | |
), | |
), | |
], | |
), | |
), | |
], | |
), | |
), | |
), | |
), | |
); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment