I need to design screen like this in short videovideo_link
I did this but its not very similar to the previous effect
here I create CustomScrollView
with NotificationListener
to achieve snap effect, but is still begging and isused TransitionAppBar to make appbar and used SliverPersistentHeader which implements SliverPersistentHeaderDelegate to control on align size and position
import 'dart:math';
import 'dart:developer' as d;
import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
class FinalScroll extends StatefulWidget {
FinalScroll({Key? key}) : super(key: key);
@override
State<FinalScroll> createState() => _FinalScrollState();
}
class _FinalScrollState extends State<FinalScroll> {
final ScrollController _controller = ScrollController();
final double kLength = 300;
final double kLengthSnap = 225;
final Duration kDurationSnap = const Duration(milliseconds: 20);
@override
Widget build(BuildContext context) {
if (!(!_controller.hasClients || _controller.offset == 0)) {
// print('controll offset ${_controller.offset}');
}
double sizeImage = kLength;
return SafeArea(
child: Scaffold(
body: LayoutBuilder(builder: (context, s) {
return NotificationListener(
onNotification: (scrollEndNotification) {
// print('controll offset ${_controller.offset}');
if (scrollEndNotification is ScrollEndNotification) {
if (_controller.offset < 0.4 * kLength) {
print('snap from CTB');
Future.microtask(
() {
return _controller.animateTo(0.0,
duration: kDurationSnap, curve: Curves.bounceInOut);
},
);
setState(() {
sizeImage = kLength;
});
}
if (_controller.offset > kLength * 0.4 &&
_controller.offset <= kLength * .6) {
print('snap from CTT');
Future.microtask(() => _controller.animateTo(kLength * 0.6,
duration: kDurationSnap, curve: Curves.bounceInOut));
setState(() {
sizeImage = 50;
});
}
}
return false;
},
child: CustomScrollView(
controller: _controller,
physics: const BouncingScrollPhysics(),
slivers: <Widget>[
TransitionAppBar(
extent: kLength,
sizeImage: sizeImage,
avatar: const Text("Rancho"),
title: const Text(''),
),
SliverList(
delegate: SliverChildBuilderDelegate(
(context, index) {
return ListTile(
title: Text("${index}a"),
);
},
childCount: 25,
),
)
],
),
);
}),
),
);
}
}
class TransitionAppBar extends StatefulWidget {
final Widget avatar;
final Widget title;
final double extent;
final double sizeImage;
const TransitionAppBar({
required this.avatar,
required this.title,
this.extent = 250,
required this.sizeImage,
Key? key,
}) : super(key: key);
@override
State<TransitionAppBar> createState() => _TransitionAppBarState();
}
class _TransitionAppBarState extends State<TransitionAppBar>
with TickerProviderStateMixin {
@override
Widget build(BuildContext context) {
return MediaQuery.removePadding(
context: context,
removeBottom: true,
child: SliverPersistentHeader(
pinned: true,
floating: true,
delegate: _TransitionAppBarDelegate(
avatar: widget.avatar,
sizeImage: widget.sizeImage,
title: widget.title,
extent: widget.extent > 200 ? widget.extent : 200,
vsync: this),
),
);
}
}
class _TransitionAppBarDelegate extends SliverPersistentHeaderDelegate {
_TransitionAppBarDelegate(
{required this.avatar,
required this.title,
required this.sizeImage,
this.extent = 250,
required this.vsync});
final double sizeImage;
final Widget avatar;
final Widget title;
final double extent;
/// =================== init ========= ////
final _avatarMarginTween =
EdgeInsetsTween(begin: EdgeInsets.zero, end: const EdgeInsets.all(16));
///from BTC
final _titleMarginTweenBTC = EdgeInsetsTween(
begin: EdgeInsets.zero, end: const EdgeInsets.only(left: 70));
final _titleAlignTweenBTC = AlignmentTween(
begin: const Alignment(-.7, .8), end: const Alignment(-1.0, 0.2));
final _avatarAlignTweenBTC =
AlignmentTween(begin: const Alignment(-1, 1), end: Alignment.centerLeft);
///from CTT
final _titleMarginTweenCTT = EdgeInsetsTween(
begin: const EdgeInsets.only(left: 70), end: EdgeInsets.zero);
final _titleAlignTweenCTT = AlignmentTween(
begin: const Alignment(-1.0, 0.2), end: const Alignment(-.2, 0));
final _avatarAlignTweenCTT = AlignmentTween(
begin: Alignment.centerLeft, end: const Alignment(-.7, 0.0));
final decorationTween = DecorationTween(
begin: const BoxDecoration(
shape: BoxShape.rectangle,
color: Colors.red,
image: DecorationImage(
fit: BoxFit.cover,
alignment: Alignment.centerLeft,
image: AssetImage('asset/dash.jpeg'))),
end: const BoxDecoration(
shape: BoxShape.circle,
color: Colors.yellow,
image: DecorationImage(
alignment: Alignment.centerLeft,
fit: BoxFit.fill,
image: AssetImage(
'asset/dash.jpeg',
),
),
));
@override
Widget build(
BuildContext context, double shrinkOffset, bool overlapsContent) {
final double topPadding = MediaQuery.of(context).padding.top;
final double visibleMainHeight = maxExtent - shrinkOffset - topPadding;
final double extraToolbarHeight =
max(minExtent - extent - topPadding - (kToolbarHeight), 0.0);
final double visibleToolbarHeight =
visibleMainHeight - extent - extraToolbarHeight;
final bool isScrolledUnder =
overlapsContent || (shrinkOffset > maxExtent - minExtent);
final double toolbarOpacity =
(visibleToolbarHeight / (kToolbarHeight)).clamp(0.0, 1.0);
final progress = min(1.0, shrinkOffset / maxExtent);
//margin
final avatarMargin = _avatarMarginTween.lerp(progress);
final titleMarginBTC = _titleMarginTweenBTC.lerp(progress);
final titleMarginCTT = _titleMarginTweenCTT.lerp(shrinkOffset / 300);
//align
final avatarAlignBTC = _avatarAlignTweenBTC.lerp(progress);
final titleAlignBTC = _titleAlignTweenBTC.lerp(progress);
final avatarAlignCTT = _avatarAlignTweenCTT.lerp(shrinkOffset / extent);
final titleAlignCTT = _titleAlignTweenCTT.lerp(shrinkOffset / extent);
//decoration
final decorationT = decorationTween.lerp(progress);
// print(
// 'shrinkOffset:$shrinkOffset maxExtent: $maxExtent minExtent: $minExtent progress : $progress');
print(sizeImage);
final Widget toolbar = Stack(
fit: StackFit.passthrough,
children: <Widget>[
AnimatedContainer(
duration: const Duration(milliseconds: 100),
height: minExtent,
constraints: BoxConstraints(maxHeight: minExtent),
color: Colors.grey.shade100,
),
LayoutBuilder(builder: (context, constraint) {
var checkIfProgressThanFromValue = progress < 0.3;
var checkLessThan1 = !(progress > 0.01);
return SizedBox(
height: sizeImage,
width: sizeImage==maxExtent?MediaQuery.of(context).size.width:30,
child: Row(
mainAxisSize: MainAxisSize.max,
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
Flexible(
child: Padding(
padding: avatarMargin,
child: AnimatedContainer(
duration: const Duration(milliseconds: 100),
constraints: BoxConstraints(
maxHeight: maxExtent,
maxWidth: constraint.maxWidth,
minHeight: 0,
minWidth: 0),
height: checkLessThan1 ? maxExtent : 50,
width: checkLessThan1
? MediaQuery.of(context).size.width
: 30,
// decoration: decorationT,
child: OverflowBox(
maxWidth: MediaQuery.of(context).size.width,
child: SizedBox(
height: sizeImage,
width: checkLessThan1
? MediaQuery.of(context).size.width
: 30,
child: FittedBox(
fit: BoxFit.fitHeight,
alignment: shrinkOffset > minExtent
? avatarAlignCTT
: avatarAlignBTC,
child: ClipRRect(
borderRadius: checkLessThan1
? BorderRadius.zero
: BorderRadius.circular(
min(24, progress * 24)),
clipBehavior: Clip.antiAlias,
child: AnimatedSize(
duration: Duration(milliseconds: 100),
child: Image.asset(
'asset/dash.jpeg',
// alignment: shrinkOffset > minExtent
// ? avatarAlignCTT
// : avatarAlignBTC,
fit: BoxFit.fill,
height: sizeImage,
width: sizeImage == maxExtent
? MediaQuery.of(context).size.width
: 30,
// height: checkIfProgressThanFromValue
// ? maxExtent
// : 50,
// width: checkIfProgressThanFromValue
// ? MediaQuery.of(context).size.width
// : 50,
),
),
),
),
),
),
),
),
),
],
),
);
}),
_RenderTilte(
progress: progress,
titleMarginBTC: titleMarginBTC,
titleMarginCTT: titleMarginCTT,
titleAlignBTC: titleAlignBTC,
titleAlignCTT: titleAlignCTT)
],
);
final Widget appBar = FlexibleSpaceBar.createSettings(
minExtent: minExtent,
maxExtent: maxExtent,
currentExtent: max(minExtent, maxExtent - shrinkOffset),
child: toolbar,
);
return SafeArea(
child: LayoutBuilder(builder: (context, c) {
return SizedBox(
height: c.maxHeight,
width: c.maxWidth,
child: ClipRect(child: appBar));
}),
);
}
@override
double get maxExtent => extent;
@override
double get minExtent => max(kToolbarHeight, (maxExtent * .33));
@override
bool shouldRebuild(_TransitionAppBarDelegate oldDelegate) {
return true;
}
@override
OverScrollHeaderStretchConfiguration get stretchConfiguration =>
OverScrollHeaderStretchConfiguration(
stretchTriggerOffset: 150, onStretchTrigger: () async {});
@override
FloatingHeaderSnapConfiguration? get snapConfiguration =>
FloatingHeaderSnapConfiguration(
curve: Curves.bounceInOut,
duration: const Duration(milliseconds: 10));
@override
PersistentHeaderShowOnScreenConfiguration? get showOnScreenConfiguration =>
const PersistentHeaderShowOnScreenConfiguration();
@override
final TickerProvider vsync;
}
class _RenderTilte extends StatelessWidget {
const _RenderTilte({
Key? key,
required this.progress,
required this.titleMarginBTC,
required this.titleMarginCTT,
required this.titleAlignBTC,
required this.titleAlignCTT,
}) : super(key: key);
final double progress;
final EdgeInsets titleMarginBTC;
final EdgeInsets titleMarginCTT;
final Alignment titleAlignBTC;
final Alignment titleAlignCTT;
@override
Widget build(BuildContext context) {
return Padding(
padding: progress < 0.3 ? titleMarginBTC : titleMarginCTT,
child: Align(
alignment: progress < 0.3 ? titleAlignBTC : titleAlignCTT,
child: Opacity(
opacity: progress == 0 || progress == 1 ? 1 : progress * 0.01,
child: const Text(
'abd alazeez ',
),
),
),
);
}
}
here how to build the previous design and I don't know what the next step