The original panel experiment developed one concern at a time: draw the panel, add a visibility action, then animate the transition. Keeping those stages separate makes the moving parts easier to inspect.
Start with a static surface
class HomeScreenBody extends StatelessWidget {
SlideUpController slideUpController = SlideUpController();
@override
Widget build(BuildContext context) {
return Stack(
alignment: Alignment.bottomCenter,
children: [
Container(
child: Column(
children: [
RaisedButton(
child: Text('Slide up widget'),
onPressed: () {
},
)
],
),
),
SlideUpWidget()
],
);
}
}
import 'package:flutter/material.dart';
class SlideUpWidget extends StatefulWidget {
final SlideUpController controller;
const SlideUpWidget({Key key, this.controller}) : super(key: key);
@override
_SlideUpWidgetState createState() => _SlideUpWidgetState();
}
class _SlideUpWidgetState extends State<SlideUpWidget> {
@override
Widget build(BuildContext context) {
return Container(
height: 200,
decoration: BoxDecoration(
color: Colors.red,
borderRadius: BorderRadius.only(
topLeft: Radius.circular(20),
topRight: Radius.circular(20))),
);
}
}
Add a visibility model and controller
import 'package:provider/provider.dart';
class SlideUpProvider with ChangeNotifier {
bool isShow = false;
void updateState(bool newState) {
isShow = newState;
notifyListeners();
}
}
class SlideUpController {
SlideUpController._private();
static final SlideUpController instance = SlideUpController._private();
factory SlideUpController() => instance;
BuildContext _providerContext;
set providerContext(BuildContext context) {
if (_providerContext != context) {
_providerContext = context;
}
}
void toggle() {
if (_providerContext != null) {
final provider = _providerContext.read<SlideUpProvider>();
provider.updateState(!provider.isShow);
} else {
print('Need init provider context');
}
}
}
The controller stores a provider context in a singleton. That couples its lifetime to a particular widget tree, so stale context and ownership need review before reuse.
The next widget and screen variants connect that controller:
class _SlideUpWidgetState extends State<SlideUpWidget> {
@override
Widget build(BuildContext context) {
return ChangeNotifierProvider(
create: (_) => SlideUpProvider(),
child: Consumer<SlideUpProvider>(
builder: (context, provider, child) {
widget.controller?.providerContext = context;
return provider.isShow
? Container(
height: 200,
decoration: BoxDecoration(
color: Colors.red,
borderRadius: BorderRadius.only(
topLeft: Radius.circular(20),
topRight: Radius.circular(20))),
)
: Container();
},
),
);
}
}
class HomeScreenBody extends StatelessWidget {
SlideUpController slideUpController = SlideUpController();
@override
Widget build(BuildContext context) {
return Stack(
alignment: Alignment.bottomCenter,
children: [
Container(
child: Column(
children: [
RaisedButton(
child: Text('Slide up widget'),
onPressed: () {
slideUpController.toggle();
},
)
],
),
),
SlideUpWidget(
controller: slideUpController,
)
],
);
}
}
Add the transition
class HomeScreenBody extends StatefulWidget {
@override
_HomeScreenBodyState createState() => _HomeScreenBodyState();
}
class _HomeScreenBodyState extends State<HomeScreenBody>
with SingleTickerProviderStateMixin {
SlideUpController slideUpController = SlideUpController();
AnimationController _controller;
Animation<Offset> _offsetAnimation;
Animation<double> _fadeAnimation;
@override
void initState() {
super.initState();
_controller = AnimationController(
duration: const Duration(milliseconds: 350),
vsync: this,
);
_offsetAnimation = Tween<Offset>(
begin: const Offset(0, 1),
end: Offset.zero,
).animate(CurvedAnimation(
parent: _controller,
curve: Curves.decelerate,
))
..addStatusListener((status) {
if (status == AnimationStatus.forward) {
// Start animation at begin
slideUpController.toggle();
} else if (status == AnimationStatus.dismissed) {
// To hide widget, we need complete animation first
slideUpController.toggle();
}
});
_fadeAnimation = Tween<double>(
begin: 1,
end: 0,
).animate(CurvedAnimation(
parent: _controller,
curve: Curves.decelerate,
));
}
@override
void dispose() {
super.dispose();
_controller.dispose();
}
@override
Widget build(BuildContext context) {
return Stack(
alignment: Alignment.bottomCenter,
children: [
FadeTransition(
opacity: _fadeAnimation,
child: Container(
height: double.infinity,
width: double.infinity,
color: Colors.grey,
child: RaisedButton(
child: Text('Slide up widget'),
onPressed: () {
if (_controller.isDismissed) {
_controller.forward();
} else {
_controller.reverse();
}
},
),
),
),
SlideTransition(
position: _offsetAnimation,
child: SlideUpWidget(
controller: slideUpController,
),
)
],
);
}
}
The final variant combines an opacity change for the main UI with a slide transition for the panel. These listings are successive versions, not seven files to concatenate unchanged. Animation-controller lifetime, disposal, and repeated toggles need checking together. No Flutter animation was run for this revision.