Closed elertan closed 2 years ago
Hi @elertan 👋 Please, could you send me an example using darpad, like this: https://dartpad.dev/?id=6a833cba62679260bf1a556a4fdc9043
I was unable to figure out quickly how to share the dartpad here so I'll be sharing the source instead.
Here's an example that shows a scaffold with transparency that has a gradient that reveals what is below:
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 Home(),
);
}
}
class Home extends StatelessWidget {
const Home({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
const bottomNavigationBarColor = Colors.black;
return Scaffold(
extendBody: true,
body: ListView.builder(
itemCount: 30,
itemBuilder: (context, i) {
return SizedBox(
height: 65,
child: Text("Number $i"),
);
},
),
bottomNavigationBar: Container(
decoration: BoxDecoration(
gradient: LinearGradient(
colors: [
bottomNavigationBarColor.withOpacity(.6),
bottomNavigationBarColor.withOpacity(.7),
bottomNavigationBarColor.withOpacity(.8),
bottomNavigationBarColor.withOpacity(.9),
],
begin: Alignment.topCenter,
end: Alignment.bottomCenter,
tileMode: TileMode.clamp,
),
),
child: BottomNavigationBar(
type: BottomNavigationBarType.fixed,
items: const <BottomNavigationBarItem>[
BottomNavigationBarItem(
icon: Icon(Icons.home),
label: 'Home',
),
BottomNavigationBarItem(
icon: Icon(Icons.business),
label: 'Business',
),
BottomNavigationBarItem(
icon: Icon(Icons.school),
label: 'School',
),
],
currentIndex: 0,
onTap: (_) {},
backgroundColor: Colors.transparent,
unselectedItemColor: Colors.grey,
selectedItemColor: Colors.white,
),
),
);
}
}
And here's an example that shows why it's not working the way I want it to using we_slide
import 'dart:ui';
import 'package:flutter/material.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
// This widget is the root of your application.
@override
Widget build(BuildContext context) {
return MaterialApp(
themeMode: ThemeMode.dark,
//theme: AppTheme.lightThemeData,
//darkTheme: AppTheme.darkThemeData,
title: 'WeSlide Demo',
debugShowCheckedModeBanner: false,
//home: MusicApp(),
//home: StoreApp(),
home: Basic(),
);
}
}
class Basic extends StatefulWidget {
Basic({Key? key}) : super(key: key);
@override
_BasicState createState() => _BasicState();
}
class _BasicState extends State<Basic> {
@override
Widget build(BuildContext context) {
final _colorScheme = Theme.of(context).colorScheme;
final double _panelMinSize = 130.0;
final double _panelMaxSize = MediaQuery.of(context).size.height;
final _controller = WeSlideController();
const bottomNavigationBarColor = Colors.black;
return Scaffold(
backgroundColor: Colors.black,
body: WeSlide(
parallax: true,
hideAppBar: true,
hideFooter: false,
panelMinSize: _panelMinSize,
panelMaxSize: _panelMaxSize,
backgroundColor: Colors.tealAccent,
panelBorderRadiusBegin: 20.0,
panelBorderRadiusEnd: 20.0,
parallaxOffset: 0.3,
appBarHeight: 80.0,
footerHeight: 60.0,
controller: _controller,
appBar: AppBar(
title: Text("We Slide"),
leading: BackButton(),
backgroundColor: Colors.black,
),
body: ListView.builder(
itemCount: 30,
itemBuilder: (context, i) {
return SizedBox(
height: 65,
child: Text("Number $i"),
);
},
),
panel: Container(
color: _colorScheme.primary,
child: Center(child: Text("This is the panel 😊")),
),
panelHeader: GestureDetector(
onTap: () {
_controller.show();
},
child: Container(
height: 90.0,
color: _colorScheme.secondary,
child: Center(child: Text("Slide to Up ☝️")),
),
),
footer: Container(
decoration: BoxDecoration(
gradient: LinearGradient(
colors: [
bottomNavigationBarColor.withOpacity(.6),
bottomNavigationBarColor.withOpacity(.7),
bottomNavigationBarColor.withOpacity(.8),
bottomNavigationBarColor.withOpacity(.9),
],
begin: Alignment.topCenter,
end: Alignment.bottomCenter,
tileMode: TileMode.clamp,
),
),
child: BottomNavigationBar(
type: BottomNavigationBarType.fixed,
items: const <BottomNavigationBarItem>[
BottomNavigationBarItem(
icon: Icon(Icons.home),
label: 'Home',
),
BottomNavigationBarItem(
icon: Icon(Icons.business),
label: 'Business',
),
BottomNavigationBarItem(
icon: Icon(Icons.school),
label: 'School',
),
],
currentIndex: 0,
onTap: (_) {},
backgroundColor: Colors.transparent,
unselectedItemColor: Colors.grey,
selectedItemColor: Colors.white,
),
),
),
);
}
}
class WeSlide extends StatefulWidget {
/// This is the widget that will be below as a footer,
/// this can be used as a [BottomNavigationBar]
final Widget? footer;
/// This is the widget that will be on top as a AppBar,
/// this can be used as a [AppBar]
final Widget? appBar;
/// This is the widget that will be hided with [Panel].
/// You can fit any widget. This parameter is required
final Widget body;
/// This is the widget that will slide over the [Body].
/// You can fit any widget.
final Widget? panel;
/// This is the header that will be over the [Panel].
/// You can fit any widget.
final Widget? panelHeader;
/// This is the initial value that set the panel min height.
/// If the value is greater than 0, panel will be this size over [body]
/// By default is [150.0]. Set [0.0] if you want to hide [Panel]
final double panelMinSize;
/// This is the value that set the panel max height.
/// When slide up the panel this value define the max height
/// that panel will be over [Body]. By default is [400.0]
/// if you want that panel cover the whole [Body], set with
/// MediaQuery.of(context).size.height
final double panelMaxSize;
/// This is the value that set the panel width
/// by default is MediaQuery.of(context).size.width
final double? panelWidth;
/// Set this value to create a border radius over Panel.
/// When panelBorderRadiusBegin is diffrent from panelBorderRadiusEnd
/// and the panel is slide up, this create an animation border over panel
/// By default is 0.0
final double panelBorderRadiusBegin;
/// Set this value to create a border radius over Panel.
/// When panelBorderRadiusBegin is diffrent from panelBorderRadiusEnd
/// and the panel is slide up, this create an animation border over panel
/// By default is 0.0
final double panelBorderRadiusEnd;
/// Set this value to create a border radius over Body.
/// When bodyBorderRadiusBegin is diffrent from bodyBorderRadiusEnd
/// and the panel is slide up, this create an animation border over body
/// By default is 0.0
final double bodyBorderRadiusBegin;
/// Set this value to create a border radius over Body.
/// When bodyBorderRadiusBegin is diffrent from bodyBorderRadiusEnd
/// and the panel is slide up, this create an animation border over body.
/// By default is 0.0
final double bodyBorderRadiusEnd;
/// This is the value that set the body width.
/// By default is MediaQuery.of(context).size.width
final double? bodyWidth;
/// Set this value to create a parallax effect when the panel is slide up.
/// By default is 0.1
final double parallaxOffset;
/// This is the value that set the footer height.
/// by default is 60.0
final double footerHeight;
/// This is the value that set the appbar height.
/// by default is 80.0
final double appBarHeight;
/// This is the value that defines opacity
/// overlay effect bethen body and panel.
final double overlayOpacity;
/// This is the value that creates an image filter
/// that applies a Gaussian blur.
final double blurSigma;
/// This is the value that defines Transform scale begin effect
/// By default is 1.0
final double transformScaleBegin;
/// This is the value that defines Transform scale end effect
/// by default is 0.9
final double transformScaleEnd;
/// This is the value that defines overlay color effect.
/// By default is Colors.black
final Color overlayColor;
/// This is the value that defines blur color effect.
/// By default is Colors.black
final Color blurColor;
/// This is the value that defines background color.
/// By default is Colors.black end should be the same as [body]
final Color backgroundColor;
/// This is the value that defines if you want to hide the footer.
/// By default is true
final bool hideFooter;
/// This is the value that defines if you want to hide the [panelHeader].
/// By default is true
final bool hidePanelHeader;
/// This is the value that defines if you want to enable paralax effect.
/// By default is false
final bool parallax;
/// This is the value that defines if you want
/// to enable transform scale effect. By default is false
final bool transformScale;
/// This is the value that defines if you want
/// to enable overlay effect. By default is false
final bool overlay;
/// This is the value that defines if you want
/// to enable Gaussian blur effect. By default is false
final bool blur;
/// This is the value that defines if you want
/// to enable Gaussian blur effect. By default is false
final bool hideAppBar;
/// The [isDismissible] parameter specifies whether the panel
/// will be dismissed when user taps on the screen.
final bool isDismissible;
/// This is the value that create a fade transition over panel header
final List<TweenSequenceItem<double>> fadeSequence;
/// This is the value that sets the duration of the animation.
/// By default is 300 milliseconds
final Duration animateDuration;
/// This object used to control animations, using methods like hide or show
/// to display panel or check if is visible with variable [isOpened]
WeSlideController? controller;
/// Weslide Contructor
WeSlide({
Key? key,
this.footer,
this.appBar,
required this.body,
this.panel,
this.panelHeader,
this.panelMinSize = 150.0,
this.panelMaxSize = 400.0,
this.panelWidth,
this.panelBorderRadiusBegin = 0.0,
this.panelBorderRadiusEnd = 0.0,
this.bodyBorderRadiusBegin = 0.0,
this.bodyBorderRadiusEnd = 0.0,
this.bodyWidth,
this.transformScaleBegin = 1.0,
this.transformScaleEnd = 0.85,
this.parallaxOffset = 0.1,
this.overlayOpacity = 0.0,
this.blurSigma = 5.0,
this.overlayColor = Colors.black,
this.blurColor = Colors.black,
this.backgroundColor = Colors.black,
this.footerHeight = 60.0,
this.appBarHeight = 80.0,
this.hideFooter = true,
this.hidePanelHeader = true,
this.parallax = false,
this.transformScale = false,
this.overlay = false,
this.blur = false,
this.hideAppBar = true,
this.isDismissible = true,
List<TweenSequenceItem<double>>? fadeSequence,
this.animateDuration = const Duration(milliseconds: 300),
this.controller,
}) : /*assert(body != null, 'body could not be null'),*/
assert(panelMinSize >= 0.0, 'panelMinSize cannot be negative'),
assert(footerHeight >= 0.0, 'footerHeight cannot be negative'),
assert(appBarHeight >= 0.0, 'appBarHeight cannot be negative'),
assert(panel != null, 'panel could not be null'),
assert(panelMaxSize >= panelMinSize,
'panelMaxSize cannot be less than panelMinSize'),
fadeSequence = fadeSequence ??
[
TweenSequenceItem<double>(
weight: 1.0, tween: Tween(begin: 1, end: 0)),
TweenSequenceItem<double>(
weight: 8.0, tween: Tween(begin: 0, end: 0)),
],
super(key: key) {
if (controller == null) {
// ignore: unnecessary_this
this.controller = WeSlideController();
}
}
@override
_WeSlideState createState() => _WeSlideState();
}
class _WeSlideState extends State<WeSlide> with SingleTickerProviderStateMixin {
// Main Animation Controller
late AnimationController _ac;
// Panel Border Radius Effect[Tween]
late Animation<double> _panelborderRadius;
// Body Border Radius Effect [Tween]
late Animation<double> _bodyBorderRadius;
// Scale Animation Effect [Tween]
late Animation<double> _scaleAnimation;
// PanelHeader animation Effect [Tween]
late Animation<double> _fadeAnimation;
// Get current controller
WeSlideController get _effectiveController => widget.controller!;
// Check if panel is visible
bool get _ispanelVisible =>
_ac.status == AnimationStatus.completed ||
_ac.status == AnimationStatus.forward;
@override
void initState() {
// Subscribe to animated when value change
_effectiveController.addListener(_animatedPanel);
// Animation controller;
_ac = AnimationController(vsync: this, duration: widget.animateDuration);
// panel Border radius animation
_panelborderRadius = Tween<double>(
begin: widget.panelBorderRadiusBegin,
end: widget.panelBorderRadiusEnd)
.animate(_ac);
// body border radius animation
_bodyBorderRadius = Tween<double>(
begin: widget.bodyBorderRadiusBegin,
end: widget.bodyBorderRadiusEnd)
.animate(_ac);
// Transform scale animation
_scaleAnimation = Tween<double>(
begin: widget.transformScaleBegin, end: widget.transformScaleEnd)
.animate(_ac);
// Fade Animation sequence
_fadeAnimation = TweenSequence(widget.fadeSequence).animate(_ac);
// Super Init State
super.initState();
}
/// Required for resubscribing when hot reload occurs [ValueNotifier]
@override
void didUpdateWidget(WeSlide oldWidget) {
super.didUpdateWidget(oldWidget);
oldWidget.controller?.removeListener(_animatedPanel);
widget.controller?.addListener(_animatedPanel);
}
/// Animate the panel [ValueNotifier]
void _animatedPanel() {
if (_effectiveController.value != _ispanelVisible) {
_ac.fling(velocity: _ispanelVisible ? -2.0 : 2.0);
}
}
/// Dispose
@override
void dispose() {
///Animation Controller
_ac.dispose();
/// ValueNotifier
_effectiveController.dispose();
super.dispose();
}
/// Gesture Vertical Update [GestureDetector]
void _handleVerticalUpdate(DragUpdateDetails updateDetails) {
var delta = updateDetails.primaryDelta!;
var fractionDragged = delta / widget.panelMaxSize;
_ac.value -= 1.5 * fractionDragged;
}
/// Gesture Vertical End [GestureDetector]
void _handleVerticalEnd(DragEndDetails endDetails) {
var velocity = endDetails.primaryVelocity!;
if (velocity > 0.0) {
_ac.reverse().then((x) {
_effectiveController.value = false;
});
} else if (velocity < 0.0) {
_ac.forward().then((x) {
_effectiveController.value = true;
});
} else if (_ac.value >= 0.5 && endDetails.primaryVelocity == 0.0) {
_ac.forward().then((x) {
_effectiveController.value = true;
});
} else {
_ac.reverse().then((x) {
_effectiveController.value = false;
});
}
}
// Get Body Animation [Paralax]
Animation<Offset> _getAnimationOffSet(
{required double minSize, required double maxSize}) {
final _closedPercentage =
(widget.panelMaxSize - minSize) / widget.panelMaxSize;
final _openPercentage =
(widget.panelMaxSize - maxSize) / widget.panelMaxSize;
return Tween<Offset>(
begin: Offset(0.0, _closedPercentage),
end: Offset(0.0, _openPercentage))
.animate(_ac);
}
//Get Panel size
double _getPanelSize() {
var _size = 0.0;
/* If footer is visible*/
if (!widget.hideFooter && widget.footer != null) {
_size += widget.footerHeight;
}
/* If appbar is visible*/
if (!widget.hideAppBar && widget.appBar != null) {
_size += widget.appBarHeight;
}
return _size;
}
/* Get panel maxsize location*/
double _getPanelLocation() {
var _location = widget.panelMaxSize;
if (widget.appBar != null && !widget.hideAppBar) {
_location += -widget.appBarHeight;
}
return _location;
}
/* Get Body location*/
double _getBodyLocation() {
var _location = 0.0;
/* if appbar */
if (widget.appBar != null) {
_location += widget.appBarHeight;
}
/* if paralax*/
if (widget.parallax) {
_location += _ac.value *
(widget.panelMaxSize - widget.panelMinSize) *
-widget.parallaxOffset;
}
return _location;
}
double _getBodyHeight() {
var _size = widget.panelMinSize;
/* If appbar is visible*/
if (widget.appBar != null) _size += widget.appBarHeight;
/* if no panelMinSize value*/
if (widget.panelMinSize == 0.0 && widget.footer != null) {
_size += widget.footerHeight;
}
return _size;
}
@override
Widget build(BuildContext context) {
//Get MediaQuery Sizes
final _height = MediaQuery.of(context).size.height;
final _width = MediaQuery.of(context).size.width;
return Container(
height: _height,
color: widget.backgroundColor, // Same as body,
child: Stack(
alignment: Alignment.bottomCenter,
children: <Widget>[
/** Body widget **/
AnimatedBuilder(
animation: _ac,
builder: (context, child) {
return Positioned(
top: _getBodyLocation(),
child: Transform.scale(
scale: widget.transformScale ? _scaleAnimation.value : 1.0,
alignment: Alignment.bottomCenter,
child: ClipRRect(
borderRadius: BorderRadius.only(
topLeft: Radius.circular(_bodyBorderRadius.value),
topRight: Radius.circular(_bodyBorderRadius.value),
),
child: child,
),
),
);
},
child: Container(
height: _height - _getBodyHeight(),
width: widget.bodyWidth ?? _width,
child: widget.body,
),
),
/** Enable Blur Effect **/
if (widget.blur)
AnimatedBuilder(
animation: _ac,
builder: (context, _) {
/** Fix problem with body scroll */
if (_ac.value <= 0) return SizedBox.shrink();
return BackdropFilter(
filter: ImageFilter.blur(
sigmaX: widget.blurSigma * _ac.value,
sigmaY: widget.blurSigma * _ac.value),
child: Container(
color: widget.blurColor.withOpacity(0.1),
),
);
},
),
/** Enable Overlay Effect **/
if (widget.overlay)
AnimatedBuilder(
animation: _ac,
builder: (context, _) {
return Container(
color: _ac.value == 0.0
? null
: widget.overlayColor
.withOpacity(widget.overlayOpacity * _ac.value),
);
},
),
/** Dismiss Panel **/
ValueListenableBuilder(
valueListenable: _effectiveController,
builder: (_, __, ___) {
if (_effectiveController.isOpened && widget.isDismissible) {
return GestureDetector(
onTap: _effectiveController.hide,
child: Container(
color: Colors.transparent,
),
);
}
return SizedBox();
},
),
/** Panel widget **/
AnimatedBuilder(
animation: _ac,
builder: (_, child) {
return SlideTransition(
position: _getAnimationOffSet(
maxSize: _getPanelLocation(), minSize: widget.panelMinSize),
child: GestureDetector(
onVerticalDragUpdate: _handleVerticalUpdate,
onVerticalDragEnd: _handleVerticalEnd,
child: AnimatedContainer(
height: widget.panelMaxSize,
width: widget.panelWidth ?? _width,
duration: Duration(milliseconds: 200),
child: ClipRRect(
borderRadius: BorderRadius.only(
topLeft: Radius.circular(_panelborderRadius.value),
topRight: Radius.circular(_panelborderRadius.value),
),
child: child,
),
),
),
);
},
child: Stack(
children: <Widget>[
/** Panel widget **/
Container(
height: _height - _getPanelSize(),
child: widget.panel!,
),
/** Panel Header widget **/
widget.panelHeader != null && widget.hidePanelHeader
? FadeTransition(
opacity: _fadeAnimation,
child: ValueListenableBuilder(
valueListenable: _effectiveController,
builder: (_, __, ___) {
return IgnorePointer(
ignoring: _effectiveController.value &&
widget.hidePanelHeader,
child: widget.panelHeader,
);
},
),
)
: SizedBox.shrink(),
/** panelHeader widget is null ?**/
widget.panelHeader != null && !widget.hidePanelHeader
? widget.panelHeader!
: SizedBox.shrink(),
],
),
),
// Footer Widget
widget.footer != null
? AnimatedBuilder(
animation: _ac,
builder: (context, child) {
return Positioned(
height: widget.footerHeight,
bottom: widget.hideFooter
? _ac.value * -widget.footerHeight
: 0.0,
width: MediaQuery.of(context).size.width,
child: widget.footer!,
);
},
)
: SizedBox.shrink(),
// AppBar
widget.appBar != null
? AnimatedBuilder(
animation: _ac,
builder: (context, child) {
return Positioned(
height: widget.appBarHeight,
top: widget.hideAppBar
? _ac.value * -widget.appBarHeight
: 0.0,
left: 0,
right: 0,
child: widget.appBar!,
);
},
)
: SizedBox.shrink(),
],
),
);
}
}
class WeSlideController extends ValueNotifier<bool> {
/// WeslideController Construction
WeSlideController() : super(false);
/// show WeSlide Panel
void show() => value = true;
/// hide WeSlide Panel
void hide() => value = false;
/// Returns if the WeSlide Panel is opened or not
bool get isOpened => value;
}
The issue is that the we slide widget renders the full height even when it's off screen.
@elertan This is your example: https://dartpad.dev/?id=264f14b4edc171af103f8f4a61468c58
This package uses flutter stack to organize the components "Appbar, Footer, Body Panel etc..." When you set the footer transparency, other components with background will be shown below.
This is a documentation about stack. https://api.flutter.dev/flutter/widgets/Stack-class.html
I think it is not possible to apply transparency using stack 😔
Hey Luciano!
Awesome package. I'm having some trouble configuring transparency for the footer though, I have a
BottomNavigationBar
that was transparent using a gradient before, but that does not seem to work out of the box. I have tried settingoverlayColor
andbackgroundColor
to be transparent but with no luck. It seems as if it is being drawn onto some other color.I'll try to set up a small proof-of-concept if that would be helpful later.
Thanks!