115 lines
3.5 KiB
Dart
115 lines
3.5 KiB
Dart
import 'dart:ui';
|
|
import 'package:flutter/material.dart';
|
|
|
|
class FadedScrollView extends StatefulWidget {
|
|
final Widget child;
|
|
final double fadeHeight;
|
|
final double easingDistance;
|
|
final double maxBlur;
|
|
final ScrollController? controller;
|
|
final EdgeInsetsGeometry? padding;
|
|
|
|
const FadedScrollView({
|
|
super.key,
|
|
required this.child,
|
|
this.fadeHeight = 20.0,
|
|
this.easingDistance = 10.0,
|
|
this.maxBlur = 3.0,
|
|
this.controller,
|
|
this.padding,
|
|
});
|
|
|
|
@override
|
|
State<FadedScrollView> createState() => _FadedScrollViewState();
|
|
}
|
|
|
|
class _FadedScrollViewState extends State<FadedScrollView> {
|
|
late ScrollController _scrollController;
|
|
|
|
@override
|
|
void initState() {
|
|
super.initState();
|
|
_scrollController = widget.controller ?? ScrollController();
|
|
}
|
|
|
|
@override
|
|
void dispose() {
|
|
if (widget.controller == null) {
|
|
_scrollController.dispose();
|
|
}
|
|
super.dispose();
|
|
}
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
return AnimatedBuilder(
|
|
animation: _scrollController,
|
|
builder: (context, child) {
|
|
final double offset = _scrollController.hasClients ? _scrollController.offset : 0.0;
|
|
final double fadeProgress = (offset / widget.easingDistance).clamp(0.0, 1.0);
|
|
final double currentBlur = widget.maxBlur * fadeProgress;
|
|
|
|
return Stack(
|
|
children: [
|
|
// Main content
|
|
ShaderMask(
|
|
shaderCallback: (Rect bounds) {
|
|
final double fadeStop = widget.fadeHeight / bounds.height;
|
|
final Color transparentColor = Colors.white.withOpacity(1.0 - fadeProgress);
|
|
|
|
return LinearGradient(
|
|
begin: Alignment.topCenter,
|
|
end: Alignment.bottomCenter,
|
|
colors: [
|
|
transparentColor,
|
|
Colors.white,
|
|
Colors.white,
|
|
],
|
|
stops: [0.0, fadeStop, 1.0],
|
|
).createShader(bounds);
|
|
},
|
|
blendMode: BlendMode.dstIn,
|
|
child: SingleChildScrollView(
|
|
controller: _scrollController,
|
|
padding: widget.padding,
|
|
child: widget.child,
|
|
),
|
|
),
|
|
|
|
// Single blur with extended fade area for smoother transition
|
|
if (currentBlur > 0)
|
|
Positioned(
|
|
top: 0,
|
|
left: 0,
|
|
right: 0,
|
|
height: widget.fadeHeight * 1.5, // Extend beyond fade area
|
|
child: ClipRect(
|
|
child: BackdropFilter(
|
|
filter: ImageFilter.blur(
|
|
sigmaX: currentBlur * 0.7, // Reduce intensity slightly
|
|
sigmaY: currentBlur * 0.7,
|
|
),
|
|
child: Container(
|
|
decoration: BoxDecoration(
|
|
gradient: LinearGradient(
|
|
begin: Alignment.topCenter,
|
|
end: Alignment.bottomCenter,
|
|
colors: [
|
|
Colors.transparent,
|
|
Colors.transparent,
|
|
Colors.transparent,
|
|
Colors.black.withOpacity(0.0),
|
|
],
|
|
stops: [0.0, 0.6, 0.9, 1.0],
|
|
),
|
|
),
|
|
),
|
|
),
|
|
),
|
|
),
|
|
],
|
|
);
|
|
},
|
|
);
|
|
}
|
|
} |