Flutter random Image.asset changes on hovering Button? - image

I want to show a random picture everytime the user enters the page. I also have a Button (the red container with hovering) on this page, and when the user is hovering it, a new random picture shows, but it shouldn't change since the page was loaded. I think it has something to do with the setState(), but I don't know what to do. Code:
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
import 'dart:core';
import 'dart:math';
class Home2 extends StatefulWidget {
const Home2({Key? key}) : super(key: key);
#override
_Home2State createState() => _Home2State();
}
class _Home2State extends State<Home2> {
dynamic listCinematicImages = [
"assets/cinematic/1.jpg",
"assets/cinematic/2.jpg",
"assets/cinematic/3.jpg",
"assets/cinematic/4.jpg",
"assets/cinematic/5.jpg",
"assets/cinematic/6.jpg",
"assets/cinematic/7.jpg",
];
late Random rnd;
#override
bool isHoveringButton = false;
#override
Widget build(BuildContext context) {
Size size = MediaQuery.of(context).size;
return Scaffold(
backgroundColor: Colors.black,
body: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Container(
height: double.infinity,
width: size.width * 0.5,
alignment: Alignment.center,
child: InkWell(
onTap: () {
},
onHover: (hovering) {
setState(() => isHoveringButton = hovering);
},
child: Container(
height: 50,
width: 50,
color: Colors.red,
),
),
),
Container(
height: double.infinity,
width: size.width * 0.5,
child: img(),
),
],
),
);
}
Image img() {
int min = 0;
int max = listCinematicImages.length-1;
rnd = new Random();
int r = min + rnd.nextInt(max - min);
String image_name = listCinematicImages[r].toString();
return Image.asset(image_name, fit: BoxFit.cover,);
}
}
I dont know if this helps but this is some error given out:
Error: Expected a value of type 'Map<String, dynamic>', but got one of type 'Null'
at Object.throw_ [as throw] (http://localhost:60569/dart_sdk.js:5054:11)
at Object.castError (http://localhost:60569/dart_sdk.js:5013:15)
at Object.cast [as as] (http://localhost:60569/dart_sdk.js:5336:17)
at Function.as_C [as as] (http://localhost:60569/dart_sdk.js:4959:19)

alright there's a lot a things going on here. Let's think about this step by step. You're trying to show a new image on certain events. So we need to create a variable to keep track of the current image:
class Home2 extends StatefulWidget {
const Home2({Key? key}) : super(key: key);
#override
_Home2State createState() => _Home2State();
}
class _Home2State extends State<Home2> {
static const listCinematicImages = [
"assets/cinematic/1.jpg",
"assets/cinematic/2.jpg",
"assets/cinematic/3.jpg",
"assets/cinematic/4.jpg",
"assets/cinematic/5.jpg",
"assets/cinematic/6.jpg",
"assets/cinematic/7.jpg",
];
late String currentImage;
}
before we start worrying about the events, let's figure out how to select a random image from your list. the function you provided has the right elements but there's some funky stuff going on. a simple example I'd give is:
String _getRandomImage() {
final randomIndex = Random().nextInt(listCinematicImages.length-1);
return listCinematicImages[randomIndex];
}
now we have all the elements, we just have to update the widget at the correct time. firstly you want to set a new image every time this widget is loaded. we can do this using the initState method:
#override
void initState() {
super.initState();
final newImage = _getRandomImage();
setState(() => currentImage = newImage);
}
and you want to change the image again when a user hovers over the image. You can indeed do this with an InkWell but keep in mind according to the documentation the onHover will provide a callback every time a mouse enters AND leaves the region, so we have to make sure to only update the image when we enter the region:
InkWell(
onHover: (bool hasEntered) {
if(!hasEntered) return;
final newImage = _getRandomImage();
setState(() => currentImage = newImage);
}
);
And that's it! to put it all together with your example:
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
import 'dart:core';
import 'dart:math';
class Home2 extends StatefulWidget {
const Home2({Key? key}) : super(key: key);
#override
_Home2State createState() => _Home2State();
}
class _Home2State extends State<Home2> {
static const listCinematicImages = [
"assets/cinematic/1.jpg",
"assets/cinematic/2.jpg",
"assets/cinematic/3.jpg",
"assets/cinematic/4.jpg",
"assets/cinematic/5.jpg",
"assets/cinematic/6.jpg",
"assets/cinematic/7.jpg",
];
late String currentImage;
#override
void initState() {
super.initState();
final newImage = _getRandomImage();
setState(() => currentImage = newImage);
}
#override
Widget build(BuildContext context) {
Size size = MediaQuery.of(context).size;
return Scaffold(
backgroundColor: Colors.black,
body: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Container(
height: double.infinity,
width: size.width * 0.5,
alignment: Alignment.center,
child: InkWell(
onHover: (bool hasEntered) {
if(!hasEntered) return;
final newImage = _getRandomImage();
setState(() => currentImage = newImage);
},
child: Container(
height: 50,
width: 50,
color: Colors.red,
),
),
),
Container(
height: double.infinity,
width: size.width * 0.5,
child: Image.asset(currentImage, fit: BoxFit.cover,);,
),
],
),
);
}
String _getRandomImage() {
final randomIndex = Random().nextInt(listCinematicImages.length-1);
return listCinematicImages[randomIndex];
}
}

Related

Flutter make image with GestureDetectors also Draggable

My goal is to have an image that I can zoom & move around inside a CustomClipperImage and it should also be Draggable!
Right now I can scale the image in its Clip and this looks like this:
Screenvideo
This is the code for it:
child: Container(
height: _containetWidth,
width: _containetWidth,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(10.0),
border: Border.all(color: Colors.white, width: 5),
),
child: GestureDetector(
onTap: () => print("tapped"),
onScaleStart: (details) {
_startingFocalPoint.value = details.focalPoint;
_previousOffset.value = _offset.value;
_previousZoom.value = _zoom.value;
},
onScaleUpdate: (details) {
_zoom.value = _previousZoom.value * details.scale;
final Offset normalizedOffset =
(_startingFocalPoint.value - _previousOffset.value) /
_previousZoom.value;
_offset.value =
details.focalPoint - normalizedOffset * _zoom.value;
},
child: Stack(
children: [
ClipPath(
clipper: CustomClipperImage(),
child: Transform(
transform: Matrix4.identity()
..translate(_offset.value.dx, _offset.value.dy)
..scale(_zoom.value),
child: Image.asset('assets/images/example.jpg',
width: _containetWidth,
height: _containetWidth,
fit: BoxFit.fill),
),
),
CustomPaint(
painter: MyPainter(),
child: Container(
width: _containetWidth, height: _containetWidth),
),
],
),
),
),
But I can not make it Draggable... I tried wrapping the whole Container or also just the Image.asset inside Draggable but when doing this, scaling stops working and Draggable is not working either.
What is the best way to achieve this? I couldn't find anything on this... Let me know if you need more details!
The problem you have is a conflict between:
zooming and dragging the image inside the custom ClipPath
dragging the images between two custom ClipPath
The solution I propose is to use drag handles to swap the images
!!! SPOILER : It does not work (yet) !!!
To implement this drag-n-drop with custom ClipPath, we need the support of HitTestBehavior.deferToChild on DragTarget.
The good news is... It's already available in Flutter master channel! [ref]
So, if you can wait a bit for it to be released in stable, here is my solution:
The main idea is to have the zoomable images as DragTargets and for each image a drag handle as Draggable.
I added a layer of State Management to keep the zoom level and offset when swapping the images.
I also improved the zoomable feature to ensure that the image always covers the full ClipPath.
Full source code (250 lines)
import 'dart:math' show min, max;
import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:freezed_annotation/freezed_annotation.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
part '66474773.drag.freezed.dart';
void main() {
runApp(
ProviderScope(
child: MaterialApp(
debugShowCheckedModeBanner: false,
title: 'Flutter Demo',
home: HomePage(),
),
),
);
}
class HomePage extends HookWidget {
#override
Widget build(BuildContext context) {
final images = useProvider(imagesProvider.state);
final _width = MediaQuery.of(context).size.shortestSide * .8;
void swapImages() => context.read(imagesProvider).swap();
return Scaffold(
backgroundColor: Colors.black87,
body: Padding(
padding: const EdgeInsets.all(24.0),
child: Container(
height: _width,
width: _width,
child: Stack(
children: [
DragTarget<VerticalDirection>(
hitTestBehavior: HitTestBehavior.deferToChild,
onWillAccept: (direction) =>
direction == VerticalDirection.up,
onAccept: (_) => swapImages(),
builder: (_, __, ___) => _Zoomable(
key: GlobalKey(),
width: _width,
pathFn: topPathFn,
imageId: 0,
),
),
DragTarget<VerticalDirection>(
hitTestBehavior: HitTestBehavior.deferToChild,
onWillAccept: (direction) =>
direction == VerticalDirection.down,
onAccept: (_) => swapImages(),
builder: (_, __, ___) => _Zoomable(
key: GlobalKey(),
width: _width,
pathFn: bottomPathFn,
imageId: 1,
),
),
Positioned.fill(
child: Align(
alignment: Alignment.topLeft,
child: _DragHandle(
direction: VerticalDirection.down,
imgAssetPath: images[0].assetPath,
),
),
),
Positioned.fill(
child: Align(
alignment: Alignment.bottomRight,
child: _DragHandle(
direction: VerticalDirection.up,
imgAssetPath: images[1].assetPath,
),
),
),
],
)),
),
);
}
}
class _DragHandle extends StatelessWidget {
final VerticalDirection direction;
final String imgAssetPath;
const _DragHandle({Key key, this.direction, this.imgAssetPath})
: super(key: key);
#override
Widget build(BuildContext context) {
return Draggable<VerticalDirection>(
data: direction,
child: Container(
decoration: BoxDecoration(
color: Colors.grey.shade200,
border: Border.all(color: Colors.grey.shade700),
),
child: Icon(Icons.open_with),
),
childWhenDragging: Container(),
feedback: Image.asset(imgAssetPath, width: 80),
);
}
}
class _Zoomable extends HookWidget {
final double width;
final Path Function(Size) pathFn;
final int imageId;
const _Zoomable({
Key key,
this.width,
this.pathFn,
this.imageId,
}) : super(key: key);
#override
Widget build(BuildContext context) {
final image =
useProvider(imagesProvider.state.select((state) => state[imageId]));
final _startingFocalPoint = useState(Offset.zero);
final _previousOffset = useState<Offset>(null);
final _offset = useState(image.offset);
final _previousZoom = useState<double>(null);
final _zoom = useState(image.zoom);
return CustomPaint(
painter: MyPainter(pathFn: pathFn),
child: GestureDetector(
onTap: () {}, // onScaleUpdate not triggered if onTap is not defined
onScaleStart: (details) {
_startingFocalPoint.value = details.focalPoint;
_previousOffset.value = _offset.value;
_previousZoom.value = _zoom.value;
},
onScaleUpdate: (details) {
_zoom.value = max(1, _previousZoom.value * details.scale);
final newOffset = details.focalPoint -
(_startingFocalPoint.value - _previousOffset.value) *
details.scale;
_offset.value = Offset(
min(0, max(-width * (_zoom.value - 1), newOffset.dx)),
min(0, max(-width * (_zoom.value - 1), newOffset.dy)),
);
},
onScaleEnd: (_) => context.read(imagesProvider).update(
imageId, image.copyWith(zoom: _zoom.value, offset: _offset.value)),
child: ClipPath(
clipper: MyClipper(pathFn: pathFn),
child: Transform(
transform: Matrix4.identity()
..translate(_offset.value.dx, _offset.value.dy)
..scale(_zoom.value),
child: Image.asset(
image.assetPath,
width: width,
height: width,
fit: BoxFit.fill,
),
),
),
),
);
}
}
Path bottomPathFn(Size size) => Path()
..moveTo(size.width, 0)
..lineTo(0, size.height)
..lineTo(size.height, size.height)
..close();
Path topPathFn(Size size) => Path()
..moveTo(size.width, 0)
..lineTo(0, size.height)
..lineTo(0, 0)
..close();
class MyClipper extends CustomClipper<Path> {
final Path Function(Size) pathFn;
MyClipper({this.pathFn});
#override
getClip(Size size) => pathFn(size);
#override
bool shouldReclip(CustomClipper oldClipper) {
return false;
}
}
class MyPainter extends CustomPainter {
final Path Function(Size) pathFn;
Path _path;
MyPainter({this.pathFn});
#override
void paint(Canvas canvas, Size size) {
_path = pathFn(size);
final paint = Paint()
..color = Colors.white
..strokeWidth = 4.0
..style = PaintingStyle.stroke;
canvas.drawPath(_path, paint);
}
#override
bool hitTest(Offset position) {
return _path?.contains(position);
}
#override
bool shouldRepaint(covariant CustomPainter oldDelegate) => false;
}
final imagesProvider =
StateNotifierProvider<ImagesNotifier>((ref) => ImagesNotifier([
ZoomedImage(assetPath: 'images/abstract.jpg'),
ZoomedImage(assetPath: 'images/abstract2.jpg'),
]));
class ImagesNotifier extends StateNotifier<List<ZoomedImage>> {
ImagesNotifier(List<ZoomedImage> state) : super(state);
void swap() {
state = state.reversed.toList();
}
void update(int id, ZoomedImage updatedImage) {
state = [...state]..[id] = updatedImage;
}
}
#freezed
abstract class ZoomedImage with _$ZoomedImage {
const factory ZoomedImage({
String assetPath,
#Default(1.0) double zoom,
#Default(Offset.zero) Offset offset,
}) = _ZoomedImage;
}

Intro-slider Flutter Image Not Appearing

I am quite new to app development and I had a few questions about Flutter I was hoping someone could help me with!
First, I am trying to code an intro-slide part in my code. I have found this code online (https://flutterawesome.com/simple-and-configurable-app-introduction-slider-for-flutter/) and when I tried executing it, using my own images, only the background color seems to print. When I remove the background colors, it is just a white screen. Is my pageImage part correct? I saved an assert folder everywhere, so I'm unsure if that is the problem. I have included my code at the end.
Thank you for your time!
class _MyHomePageState extends State<MyHomePage> {
List<Slide> slides = new List();
#override
void initState() {
super.initState();
slides.add(
new Slide(
title: "ERASER",
description: "Allow miles wound place the leave had. To sitting subject no improve studied limited",
pathImage: "assets/images/1.png",
backgroundColor: Colors.pink[200],
),
);
slides.add(
new Slide(
title: "PENCIL",
description: "Ye indulgence unreserved connection alteration appearance",
pathImage: "assets/images/1.png",
backgroundColor: Colors.blue[200],
),
);
slides.add(
new Slide(
title: "RULER",
description:
"Much evil soon high in hope do view. Out may few northward believing attempted. Yet timed being songs marry one defer men our. Although finished blessing do of",
pathImage: "assets/images/3.jpg",
),
);
}
void onDonePress() {
// TODO: go to next screen
}
void onSkipPress() {
// TODO: go to next screen
}
#override
Widget build(BuildContext context) {
return new IntroSlider(
slides: this.slides,
onDonePress: this.onDonePress,
onSkipPress: this.onSkipPress,
);
}
}
**Solution: edit assets in pubspec page
Edit:
On the left (the orange part) is how I want the blue image to appear: No scrolling and fills the whole page. However, I tried to make my image (on the right) fill the page by editing the width and height and I started having to scroll where there is the pink background below and above the image (I assume it is because it keeps having to center the image).
Is there any way to make my image my background so it is like the picture on the left? I understand the orange color background is the background color, but hopefully, by comparing the two it makes sense. Thank you!
I create new intro widget. Here is the code.
import 'package:flutter/material.dart';
class MyIntroView extends StatefulWidget {
final List<Widget> pages;
final VoidCallback onIntroCompleted;
const MyIntroView({
Key key,
#required this.pages,
#required this.onIntroCompleted,
}) : assert(pages != null),
assert(onIntroCompleted != null),
super(key: key);
#override
_MyIntroViewState createState() => _MyIntroViewState();
}
class _MyIntroViewState extends State<MyIntroView> {
PageController _pageController;
int _currentPage = 0;
#override
void initState() {
_pageController = PageController(
initialPage: _currentPage,
);
super.initState();
}
#override
void dispose() {
_pageController.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
return Stack(
children: <Widget>[
NotificationListener<ScrollEndNotification>(
onNotification: (x) {
setState(() {
_currentPage = _pageController.page.round();
});
return false;
},
child: PageView(
children: widget.pages,
controller: _pageController,
),
),
Align(
alignment: Alignment.bottomCenter,
child: _buildBottomButtons(),
),
],
);
}
bool get _isFinalPage => _currentPage == widget.pages.length - 1;
Widget _buildBottomButtons() {
return Padding(
padding: const EdgeInsets.all(8.0),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[
Opacity(
opacity: _isFinalPage ? 0.0 : 1.0,
child: _buildButton("SKIP", _gotoLastPage),
),
_buildNavIndicator(),
_isFinalPage
? _buildButton("DONE", widget.onIntroCompleted)
: _buildButton("NEXT", _gotoNextPage),
],
),
);
}
Widget _buildButton(String title, VoidCallback callback) {
return FlatButton(
child: Text(
title,
style: TextStyle(color: Colors.white),
),
onPressed: callback,
);
}
void _gotoLastPage() {
_pageController.animateToPage(
widget.pages.length - 1,
duration: const Duration(milliseconds: 600),
curve: Curves.ease,
);
}
void _gotoNextPage() {
_pageController.nextPage(
duration: const Duration(milliseconds: 600),
curve: Curves.easeInOut,
);
}
Widget _buildNavIndicator() {
final indicatorList = <Widget>[];
for (int i = 0; i < widget.pages.length; i++)
indicatorList.add(_buildIndicator(i == _currentPage));
return Row(children: indicatorList);
}
Widget _buildIndicator(bool isActive) {
return Padding(
padding: const EdgeInsets.all(5.0),
child: DecoratedBox(
decoration: BoxDecoration(
shape: BoxShape.circle,
color: isActive ? Colors.white : Colors.white30,
),
child: SizedBox(width: 8, height: 8),
),
);
}
}
Usage:
import 'package:flutter/material.dart';
import 'package:flutter_app_test3/my_intro_view.dart';
Future<void> main() async {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
home: MyHomePage(),
);
}
}
class MyHomePage extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MyIntroView(
pages: <Widget>[
Image.asset("assets/images/1.png", fit: BoxFit.cover),
Image.asset("assets/images/2.png", fit: BoxFit.cover),
Image.asset("assets/images/3.jpg", fit: BoxFit.cover),
],
onIntroCompleted: () {
print("Into is Completed");
//To the navigation stuff here
},
);
}
}
Ask me if you have any doubts in the comment
just try wrapping your Widget into Scaffold Widget and return
#override
Widget build(BuildContext context) {
return Scaffold(body:IntroSlider(
slides: this.slides,
onDonePress: this.onDonePress,
onSkipPress: this.onSkipPress,
));
}
I was facing the same issue and I fixed it by setting fit:Boxfit.fill for the image.

Flutter: Show where user clicked

Is it possible to show an animation where the user clicked on the screen in flutter. I would like a small animated star to appear right where the user pressed on the screen.
I have this animation which for the moments just loops but i only want it to loop while the user presses the screen.
new Container(
alignment: Alignment(0.0, -1.0),
child: new Container(
height: 410.0,
child: FlareActor(
"assets/images/nyastar.flr",
animation: _animationName,
)
)
),
Source: https://stackoverflow.com/a/46566392/5882307
import 'package:flutter/material.dart';
class Demo extends StatefulWidget {
#override
MyHomePageState createState() => new MyHomePageState();
}
class MyHomePageState extends State<Demo> {
double posx = 100.0;
double posy = 100.0;
void onTapDown(BuildContext context, TapDownDetails details) {
final RenderBox box = context.findRenderObject();
final Offset localOffset = box.globalToLocal(details.globalPosition);
setState(() {
posx = localOffset.dx;
posy = localOffset.dy;
});
}
#override
Widget build(BuildContext context) {
return new GestureDetector(
onTapDown: (TapDownDetails details) => onTapDown(context, details),
child: new Stack(fit: StackFit.expand, children: <Widget>[
// Hack to expand stack to fill all the space. There must be a better
// way to do it.
new Container(color: Colors.white),
new Positioned(
child: Text('hello'), //your widget to be shown
left: posx,
top: posy,
)
]),
);
}
}

Flutter : How to change status bar color when scrolling with SingleChildScrollView

I'm new with flutter.
I'm creating a new page with SingleChildScrollView.
my problem is how to change status bar color only when scrolling active?
I've seen this effect from here but this code for sliver.
everytime scroll active at some offset, the statusbar color change.
here's the example picture what I want achieve : screenshoot
here's my basic code :
import 'package:flutter/material.dart';
class HomeScreen extends StatefulWidget {
HomeScreen({Key key}) : super(key: key);
_HomeScreenState createState() => _HomeScreenState();
}
class _HomeScreenState extends State<HomeScreen> {
#override
void initState() {
super.initState();
_scrollController = ScrollController();
_scrollController.addListener(_scrollListener);
}
#override
void dispose() {
_scrollController.removeListener(_scrollListener);
_scrollController.dispose();
super.dispose();
}
void _scrollListener() {
setState(() {
});
}
final image = 'assets/images/bg_header.png';
ScrollController _scrollController;
#override
Widget build(BuildContext context) {
return Scaffold(
body: SingleChildScrollView(
physics: ClampingScrollPhysics(),
child: Stack(
children: <Widget>[
SizedBox(
width: double.infinity,
child: Image.asset(
image,
fit: BoxFit.cover,
),
),
Container(
padding: EdgeInsets.all(40.0),
width: MediaQuery.of(context).size.width,
height: MediaQuery.of(context).size.height * 0.5,
decoration: BoxDecoration(color: Color.fromRGBO(14, 67, 39, .8)),
),
Padding(
padding: EdgeInsets.only(left: 10.0, right: 10.0, top: 25.0),
child: Column(
children: <Widget>[
some content....
],
),
),
],
),
),
);
}
}
after trying some code, i found solution implement using Inkino app.
here's the work code :
main.dart
import 'package:flutter/material.dart';
import 'package:scroll_effects.dart';
class HomeScreen extends StatefulWidget {
#override
_HomeScreenState createState() => _HomeScreenState();
}
class _HomeScreenState extends State<HomeScreen> {
ScrollController _scrollController;
ScrollEffects _scrollEffects;
#override
void initState() {
super.initState();
_scrollController = ScrollController();
_scrollController.addListener(_scrollListener);
_scrollEffects = ScrollEffects();
}
#override
void dispose() {
_scrollController.removeListener(_scrollListener);
_scrollController.dispose();
super.dispose();
}
void _scrollListener() {
setState(() {
_scrollEffects.updateScrollOffset(context, _scrollController.offset);
});
}
Widget _buildStatusBarBackground() {
final statusBarColor = Theme.of(context).primaryColor;
return Container(
height: _scrollEffects.statusBarHeight,
color: statusBarColor,
);
}
#override
Widget build(BuildContext context) {
final content = <Widget>[
**list widget.....**
];
content.add(const SizedBox(height: 32.0));
final scrollview = CustomScrollView(
physics: ClampingScrollPhysics(),
controller: _scrollController,
slivers: [
SliverList(delegate: SliverChildListDelegate(content)),
],
);
return Scaffold(
// backgroundColor: const Color(0xFFF0F0F0),
body: Stack(
children: [
scrollview,
_buildStatusBarBackground(),
],
),
);
}
}
and here's scroll_effects.dart :
import 'dart:math';
import 'package:flutter/material.dart';
class ScrollEffects {
static const double kHeaderHeight = 225.0;
ScrollEffects() {
updateScrollOffset(null, 0.0);
}
double _scrollOffset;
double statusBarHeight;
void updateScrollOffset(BuildContext context, double offset) {
_scrollOffset = offset;
_recalculateValues(context);
}
void _recalculateValues(BuildContext context) {
statusBarHeight = _calculateStatusBarHeight(context);
}
double _calculateStatusBarHeight(BuildContext context) {
double statusBarMaxHeight = 0.0;
if (context != null) {
statusBarMaxHeight = MediaQuery.of(context).padding.top;
}
return max(
0.0,
min(
statusBarMaxHeight,
_scrollOffset - kHeaderHeight + (statusBarMaxHeight * 4),
),
);
}
}

Flutter - Call animate function of a child from parent, or from another child class

I have two animating menus. dropDownMenu is working fine, and I can set onPress events from this child to perform functions in the parent class using callbacks, for example:
class dropDownMenu extends StatefulWidget {
final Function() onPressed;
final String tooltip;
final IconData icon;
final _callback;
dropDownMenu({Key key, this.onPressed, this.tooltip, this.icon, #required void singlePlayerCallbacks(String callBackType) } ):
_callback = singlePlayerCallbacks, super(key: key);
#override
_dropDownMenuState createState() => _dropDownMenuState();
}
class _dropDownMenuState extends State<dropDownMenu>
with SingleTickerProviderStateMixin {
#override
Widget build(BuildContext context) {
return Stack(
children: <Widget> [
Column(
Container(
child: Opacity(
opacity: 0.0,
child: FloatingActionButton(
heroTag: null,
onPressed: isOpened == true? (){
widget?._callback('dice');
} : () {},
),
),
),
],
);
}
And then in the parent class:
class SinglePlayerMode extends StatefulWidget {
#override
SinglePlayerModeParentState createState() => SinglePlayerModeParentState();
}
class SinglePlayerModeParentState extends State<SinglePlayerMode> {
callBacks(String callBackType) {
switch(callBackType) {
case 'dice':
{
diceVisible = !diceVisible;
int rng = new Random().nextInt(19) + 1;
setState(() {
diceResult = rng;
});
}
}
break;
}
#override
Widget build(BuildContext context) {
child: Scaffold(
body: Container(
Padding(
padding: EdgeInsets.all(0.0),
child: dropDownMenu(
singlePlayerCallbacks: callBacks,
),
),
),
),
}
As a quick example, and this works perfectly fine.
What I need to do next is have another animated menu, called styleMenu, that animates when a button from dropDownMenu is pressed. This is where I am running into massive hurdles. I honestly don't mind HOW I get this done, I just need to get it done. This is what I am trying currently, without any success:
In dropDownMenu I have another button with a callback to the parent first:
Container(
child: Opacity(
opacity: 0.0,
child: FloatingActionButton(
heroTag: null,
onPressed: isOpened == true? (){
widget?._callback('theme');
} : () {},
),
),
),
Which triggers the callback function of the parent again, with a different switch case:
callBacks(String callBackType) {
case 'theme':
{
styleMenuState().animate();
}
break;
I obviously can't do this because it tells me that I am trying to animate a null object. Like I somehow have to instantiate styleMenu before I can call this function from here, but I don't know how to do this or even if it is possible.
My styleMenu class (extract):
class styleMenu extends StatefulWidget {
final Function() onPressed;
final String tooltip;
final IconData icon;
final _callback;
final VoidCallback animate;
styleMenu({this.onPressed, this.tooltip, this.animate, this.icon, #required void singlePlayerCallbacks(String callBackType) } ):
_callback = singlePlayerCallbacks;
#override
styleMenuState createState() => styleMenuState();
}
class styleMenuState extends State<styleMenu>
with SingleTickerProviderStateMixin {
bool isOpened = false;
AnimationController animationController;
Animation<Color> _buttonColor;
Animation<double> _animateIcon;
Animation<double> _translateButton;
Curve _curve = Curves.easeOut;
double _fabHeight = 52.0;
#override
initState() {
animationController =
AnimationController(vsync: this, duration: Duration(milliseconds: 600))
..addListener(() {
setState(() {});
});
_animateIcon =
Tween<double>(begin: 0.0, end: 1.0).animate(animationController);
_buttonColor = ColorTween(
begin: Colors.blue,
end: Colors.red,
).animate(CurvedAnimation(
parent: animationController,
curve: Interval(
0.0,
1.0,
curve: Curves.linear,
),
));
_translateButton = Tween<double>(
begin: 0.0,
end: _fabHeight,
).animate(CurvedAnimation(
parent: animationController,
curve: Interval(
0.0,
1.0,
curve: _curve,
),
));
super.initState();
}
#override
dispose() {
animationController.dispose();
super.dispose();
}
animate() {
if (!isOpened) {
styleMenuState().animationController.forward();
} else {
styleMenuState().animationController.reverse();
}
isOpened = !isOpened;
}
#override
Widget build(BuildContext context) {
return Stack(
children: <Widget> [
Column(
mainAxisAlignment: MainAxisAlignment.end,
children: [
Stack(
children: <Widget>[
Transform(
transform: Matrix4.translationValues(
0,
_translateButton.value,
0,
),
child: blueTheme(),
),
Transform(
transform: Matrix4.translationValues(
0,
_translateButton.value * 2,
0,
),
child: greenTheme(),
),
Transform(
transform: Matrix4.translationValues(
0,
_translateButton.value * 3,
0,
),
child: redTheme(),
),
blackTheme(),
],
),
],
),
);
}
Again, I just need to be able to trigger the animate function from the styleMenu to pop this menu out by pressing a button inside the dropDownMenu and I just can't get my head around how to do this! I am sure there must be a simple way, but I am unable to find anything online.
Any pros out there?
I was just working on a similar problem. Using Streams, Provider with ChangeNotifiers, or inherited widgets are viable options. If you do want to simply trigger the child widget animations on setState called from the parent you can do that by using the didUpdateWidget in the child widget as shown below
#override
void didUpdateWidget(ImageHeartAnimation oldWidget) {
_controller.forward().orCancel;
super.didUpdateWidget(oldWidget);
}

Resources