Created with <3 with dartpad.dev.
Created
August 14, 2023 10:40
-
-
Save knaeckeKami/0c07ec62c56e8f6cdae4489e36a38692 to your computer and use it in GitHub Desktop.
silent-ritual-3131
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'; | |
import 'dart:math' as math; | |
import 'dart:ui' as ui; | |
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, | |
useMaterial3: true, | |
), | |
scrollBehavior: MaterialScrollBehavior().copyWith( | |
dragDevices: { | |
ui.PointerDeviceKind.mouse, | |
ui.PointerDeviceKind.touch, | |
ui.PointerDeviceKind.stylus, | |
ui.PointerDeviceKind.unknown | |
}, | |
), | |
debugShowCheckedModeBanner: false, | |
home: Scaffold( | |
body: CustomScrollView(slivers: [ | |
SliverAppBar( | |
stretch: true, | |
pinned: true, | |
flexibleSpace: MyFlexibleSpaceBar( | |
stretchModes: <StretchMode>[StretchMode.fadeTitle], | |
centerTitle: false, | |
titlePaddingTween: EdgeInsetsTween( | |
begin: const EdgeInsets.only(left: 16.0, bottom: 16), | |
end: const EdgeInsets.only(left: 72.0, bottom: 8)), | |
title: Column( | |
mainAxisSize: MainAxisSize.min, | |
crossAxisAlignment: CrossAxisAlignment.start, | |
children: [ | |
Text("title"), | |
Text("subtitle", | |
style: Theme.of(context).primaryTextTheme.titleSmall) | |
], | |
), | |
), | |
leading: IconButton(icon: Icon(Icons.close), onPressed: () {}), | |
expandedHeight: 200, | |
collapsedHeight: 62, | |
actions: [ | |
IconButton(icon: Icon(Icons.list), onPressed: () {}), | |
IconButton(icon: Icon(Icons.pause), onPressed: () {}), | |
IconButton(icon: Icon(Icons.stop), onPressed: () {}), | |
]), | |
SliverList( | |
delegate: SliverChildListDelegate( | |
List.generate( | |
100, | |
(i) => ListTile( | |
title: Text(i.toString()), | |
), | |
), | |
), | |
) | |
]), | |
), | |
); | |
} | |
} | |
class MyWidget extends StatelessWidget { | |
@override | |
Widget build(BuildContext context) { | |
return Text( | |
'Hello, World!', | |
style: Theme.of(context).textTheme.headlineMedium, | |
); | |
} | |
} | |
class MyFlexibleSpaceBar extends StatefulWidget { | |
/// Creates a flexible space bar. | |
/// | |
/// Most commonly used in the [AppBar.flexibleSpace] field. | |
const MyFlexibleSpaceBar({ | |
Key? key, | |
required this.title, | |
this.foreground, | |
this.background, | |
required this.centerTitle, | |
required this.titlePaddingTween, | |
this.collapseMode = CollapseMode.parallax, | |
this.stretchModes = const <StretchMode>[StretchMode.zoomBackground], | |
}) : assert(collapseMode != null), | |
super(key: key); | |
/// The primary contents of the flexible space bar when expanded. | |
/// | |
/// Typically a [Text] widget. | |
final Widget title; | |
final Widget? foreground; | |
/// Shown behind the [title] when expanded. | |
/// | |
/// Typically an [Image] widget with [Image.fit] set to [BoxFit.cover]. | |
final Widget? background; | |
/// Whether the title should be centered. | |
/// | |
/// By default this property is true if the current target platform | |
/// is [TargetPlatform.iOS] or [TargetPlatform.macOS], false otherwise. | |
final bool centerTitle; | |
/// Collapse effect while scrolling. | |
/// | |
/// Defaults to [CollapseMode.parallax]. | |
final CollapseMode collapseMode; | |
/// Stretch effect while over-scrolling. | |
/// | |
/// Defaults to include [StretchMode.zoomBackground]. | |
final List<StretchMode> stretchModes; | |
final EdgeInsetsTween titlePaddingTween; | |
@override | |
_MyFlexibleSpaceBarState createState() => _MyFlexibleSpaceBarState(); | |
} | |
class _MyFlexibleSpaceBarState extends State<MyFlexibleSpaceBar> { | |
bool _getEffectiveCenterTitle(ThemeData theme) { | |
if (widget.centerTitle != null) return widget.centerTitle; | |
assert(theme.platform != null); | |
switch (theme.platform) { | |
case TargetPlatform.android: | |
case TargetPlatform.fuchsia: | |
case TargetPlatform.linux: | |
case TargetPlatform.windows: | |
return false; | |
case TargetPlatform.iOS: | |
case TargetPlatform.macOS: | |
return true; | |
} | |
} | |
Alignment _getTitleAlignment(bool effectiveCenterTitle) { | |
if (effectiveCenterTitle) return Alignment.bottomCenter; | |
final TextDirection textDirection = Directionality.of(context); | |
assert(textDirection != null); | |
switch (textDirection) { | |
case TextDirection.rtl: | |
return Alignment.bottomRight; | |
case TextDirection.ltr: | |
return Alignment.bottomLeft; | |
} | |
} | |
double _getCollapsePadding(double t, FlexibleSpaceBarSettings settings) { | |
switch (widget.collapseMode) { | |
case CollapseMode.pin: | |
return -(settings.maxExtent - settings.currentExtent); | |
case CollapseMode.none: | |
return 0.0; | |
case CollapseMode.parallax: | |
final double deltaExtent = settings.maxExtent - settings.minExtent; | |
return -Tween<double>(begin: 0.0, end: deltaExtent / 4.0).transform(t); | |
} | |
} | |
@override | |
Widget build(BuildContext context) { | |
return LayoutBuilder( | |
builder: (BuildContext context, BoxConstraints constraints) { | |
final FlexibleSpaceBarSettings settings = context | |
.dependOnInheritedWidgetOfExactType<FlexibleSpaceBarSettings>()!; | |
final List<Widget> children = <Widget>[]; | |
final double deltaExtent = settings.maxExtent - settings.minExtent; | |
// 0.0 -> Expanded | |
// 1.0 -> Collapsed to toolbar | |
final double t = | |
(1.0 - (settings.currentExtent - settings.minExtent) / deltaExtent) | |
.clamp(0.0, 1.0); | |
// background | |
if (widget.background != null) { | |
final double fadeStart = | |
math.max(0.0, 1.0 - kToolbarHeight / deltaExtent); | |
const double fadeEnd = 1.0; | |
assert(fadeStart <= fadeEnd); | |
final double opacity = 1.0 - Interval(fadeStart, fadeEnd).transform(t); | |
double height = settings.maxExtent; | |
// StretchMode.zoomBackground | |
if (widget.stretchModes.contains(StretchMode.zoomBackground) && | |
constraints.maxHeight > height) { | |
height = constraints.maxHeight; | |
} | |
children.add(Positioned( | |
top: _getCollapsePadding(t, settings), | |
left: 0.0, | |
right: 0.0, | |
height: height, | |
child: Opacity( | |
// IOS is relying on this semantics node to correctly traverse | |
// through the app bar when it is collapsed. | |
alwaysIncludeSemantics: true, | |
opacity: opacity, | |
child: widget.background, | |
), | |
)); | |
// StretchMode.blurBackground | |
if (widget.stretchModes.contains(StretchMode.blurBackground) && | |
constraints.maxHeight > settings.maxExtent) { | |
final double blurAmount = | |
(constraints.maxHeight - settings.maxExtent) / 10; | |
children.add(Positioned.fill( | |
child: BackdropFilter( | |
child: Container( | |
color: Colors.transparent, | |
), | |
filter: ui.ImageFilter.blur( | |
sigmaX: blurAmount, | |
sigmaY: blurAmount, | |
)))); | |
} | |
} | |
// title | |
if (widget.title != null) { | |
final ThemeData theme = Theme.of(context); | |
Widget title; | |
switch (theme.platform) { | |
case TargetPlatform.iOS: | |
case TargetPlatform.macOS: | |
title = widget.title; | |
break; | |
case TargetPlatform.android: | |
case TargetPlatform.fuchsia: | |
case TargetPlatform.linux: | |
case TargetPlatform.windows: | |
title = Semantics( | |
namesRoute: true, | |
child: widget.title, | |
); | |
break; | |
} | |
// StretchMode.fadeTitle | |
if (widget.stretchModes.contains(StretchMode.fadeTitle) && | |
constraints.maxHeight > settings.maxExtent) { | |
final double stretchOpacity = 1 - | |
(((constraints.maxHeight - settings.maxExtent) / 100) | |
.clamp(0.0, 1.0)); | |
title = Opacity( | |
opacity: stretchOpacity, | |
child: title, | |
); | |
} | |
final double opacity = settings.toolbarOpacity; | |
if (opacity > 0.0) { | |
TextStyle titleStyle = theme.appBarTheme.titleTextStyle ?? | |
theme.primaryTextTheme.headlineSmall!; | |
titleStyle = titleStyle.copyWith( | |
color: titleStyle.color!.withOpacity(opacity)); | |
final bool effectiveCenterTitle = _getEffectiveCenterTitle(theme); | |
final padding = widget.titlePaddingTween.transform(t) ?? | |
EdgeInsetsDirectional.only( | |
start: effectiveCenterTitle ? 0.0 : 72.0, | |
bottom: 16.0, | |
); | |
final double scaleValue = | |
Tween<double>(begin: 1.5, end: 1.0).transform(t); | |
final Matrix4 scaleTransform = Matrix4.identity() | |
..scale(scaleValue, scaleValue, 1.0); | |
final Alignment titleAlignment = | |
_getTitleAlignment(effectiveCenterTitle); | |
children.add(Container( | |
padding: padding, | |
child: Transform( | |
alignment: titleAlignment, | |
transform: scaleTransform, | |
child: Align( | |
alignment: titleAlignment, | |
child: DefaultTextStyle( | |
style: titleStyle, | |
child: LayoutBuilder(builder: | |
(BuildContext context, BoxConstraints constraints) { | |
return Container( | |
width: constraints.maxWidth / scaleValue, | |
alignment: titleAlignment, | |
child: title, | |
); | |
}), | |
), | |
), | |
), | |
)); | |
} | |
} | |
//if(widget.foregrund != null) { | |
//children.add(widget.foreground); | |
//} | |
return ClipRect(child: Stack(children: children)); | |
}); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment