i have the following simple code
import 'package:flutter/material.dart';
class Test extends StatefulWidget {
const Test({Key? key}) : super(key: key);
#override
State<Test> createState() => _TestState();
}
class _TestState extends State<Test> {
late Offset offsetAll = const Offset(0,0);
#override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: GestureDetector(
onPanUpdate: (t){
offsetAll+=t.delta;
setState(() {});
},
child: Transform.translate(
offset: offsetAll,
child: Container(
color: Colors.red,
width: 100,
height: 100,
),
),
),
),
)
}
}
)
the previous code is for animate the widget depending on user finger . and it is work perfectly
my question is how could i know the current swipe speed that user's finger doing it
for example if the user swipe slow .. fast .. etc ...
i need to use that speed value later ... to the following
Future.delayed(const Duration(.....HERE....), () {
});
Is this possible?
Perhaps this subclass of GestureDetector class might help your case. It allows to access primary velocity of Horizontal drag. like this,
GestureDetector(
onHorizontalDragEnd: (dragEndDetails) {
double t = dragEndDetails.primaryVelocity;
},
),
Related
i want to use the animated positioned class to change widget position with the launch of the screen without pressing a button!
class MyStatefulWidget extends StatefulWidget {
const MyStatefulWidget({Key? key}) : super(key: key);
#override
State<MyStatefulWidget> createState() => _MyStatefulWidgetState();
}
class _MyStatefulWidgetState extends State<MyStatefulWidget> {
bool selected = false;
#override
Widget build(BuildContext context) {
return SizedBox(
width: 200,
height: 350,
child: Stack(
children: <Widget>[
AnimatedPositioned(
width: selected ? 200.0 : 50.0,
height: selected ? 50.0 : 200.0,
top: selected ? 50.0 : 150.0,
duration: const Duration(seconds: 2),
curve: Curves.fastOutSlowIn,
child: GestureDetector(
onTap: () {
setState(() {
selected = !selected;
});
},
child: Container(
color: Colors.blue,
child: const Center(child: Text('Tap me')),
),
),
),
],
),
);
}
}
i tried to change it by using a WidgetsBinding like this :
WidgetsBinding.instance.addPostFrameCallback((_) {
setState(() {
selected = true;
});
print(' widget binding : $selected');
});
it works fine but the problem was that it doesnt excuted once, it keeps running and change the value to true! like this :
I copy/past the code you have shared to see what's going on, and it's working just fine for me. I have this code
import 'package:flutter/material.dart';
void main() async {
return runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return const MaterialApp(
home: Scaffold(
body: MyStatefulWidget(),
),
);
}
}
class MyStatefulWidget extends StatefulWidget {
const MyStatefulWidget({Key? key}) : super(key: key);
#override
State<MyStatefulWidget> createState() => _MyStatefulWidgetState();
}
class _MyStatefulWidgetState extends State<MyStatefulWidget> {
#override
void initState() {
super.initState();
WidgetsBinding.instance.addPostFrameCallback((_) {
setState(() {
selected = true;
});
print(' widget binding : $selected');
});
}
bool selected = false;
#override
Widget build(BuildContext context) {
return SizedBox(
width: 200,
height: 350,
child: Stack(
children: <Widget>[
AnimatedPositioned(
width: selected ? 200.0 : 50.0,
height: selected ? 50.0 : 200.0,
top: selected ? 50.0 : 150.0,
duration: const Duration(seconds: 2),
curve: Curves.fastOutSlowIn,
child: GestureDetector(
onTap: () {
setState(() {
selected = !selected;
});
},
child: Container(
color: Colors.blue,
child: const Center(child: Text('Tap me')),
),
),
),
],
),
);
}
}
The animation is run only once, when the postFrameCallback is called. The problem must be elsewhere
I am using the pull_to_refresh package.
I am having a Stack() with two elements. One of them is the Refresher(). When I pull down on my screen, activating the refreshing animation, the build method is called constantly. The problem is that my second Widget in my Stack is quite complex to build and takes some time. I want to prevent having it build all the time when triggering the Refresher-Animation. Is this possible?
My simplified code would look like this:
#override
Widget build(BuildContext context) {
return Scaffold(
key: _scaffoldKey,
body: Stack(children: <Widget>[
SafeArea(
child: Column(children: [
Expanded(
child: Container(
margin: EdgeInsets.all(0),
width: 100.w,
constraints: const BoxConstraints.expand(),
child: SizedBox(
width: 100.w,
child: Refresher( refresher stuff )
)
)
)
)
),
SecondItem()
)
)
}
Somehow the build method of SecondItem is called all the time. Not the build method of the whole scaffold.
If Your second Item dont want to get refresh, then add it as a separeate class like,
Expanded(
child: Container(
margin: EdgeInsets.all(0),
width: 100.w,
constraints: const BoxConstraints.expand(),
child: SizedBox(
width: 100.w,
child: Refresher( refresher stuff )
)
)
)
)
),
SecondItem()
)
class SecondItem extends StatefulWidget {
#override
_SecondItemState createState() => _SecondItemState();
}
class _SecondItemState extends State<SecondItem> {
int counter = 0;
#override
Widget build(BuildContext context) {
return Scaffold(
body: Text("Your Second Widget"),
);
}
}
Now your SecondItem() will not get refresh when you refresh your FirstItem()
Since I wasn't really able to replicate the problem, I build a working structure that implements refreshing.
First the main widget, in my case MyHomePage.
This widget implements the Scaffold and Stack with FirstWidget and SecondWidget as children.
class MyHomePage extends StatefulWidget {
const MyHomePage({
Key? key,
}) : super(key: key);
#override
State<MyHomePage> createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
#override
Widget build(BuildContext context) {
return Scaffold(
body: Stack(
children: const <Widget>[
FirstWidget(),
SecondWidget(),
],
),
);
}
}
FirstWidget is a statefull widget with a counter in the state.
It implements the refresher with a specific controller.
Once the refresh is triggered, it calls set state and updates the counter within his state.
That should trigger only his build again and not any other.
I implemented a Text to show the counter value increasing at each refresh, and a Print to expose the build.
class FirstWidget extends StatefulWidget {
const FirstWidget({
Key? key,
}) : super(key: key);
#override
State<FirstWidget> createState() => _FirstWidgetState();
}
class _FirstWidgetState extends State<FirstWidget> {
late int _counter;
late RefreshController _refreshController;
#override
void initState() {
_counter = 1;
_refreshController = RefreshController(initialRefresh: false);
super.initState();
}
#override
Widget build(BuildContext context) {
print('First widget built');
return SafeArea(
child: Column(
children: [
Container(
margin: const EdgeInsets.all(0),
width: double.infinity,
height: 500,
color: Colors.red,
child: SmartRefresher(
controller: _refreshController,
onRefresh: () async {
setState(() {
_counter++;
});
await Future.delayed(const Duration(milliseconds: 1000));
_refreshController.refreshCompleted();
},
),
),
Text("Counter: $_counter"),
],
),
);
}
}
Last we got the SecondWidget which is a another simple widget with a print statement.
In case of build it writes on the console.
When the FirstWidget refresh, the second doesn't build becouse his state has not changed.
class SecondWidget extends StatefulWidget {
const SecondWidget({
Key? key,
}) : super(key: key);
#override
State<SecondWidget> createState() => _SecondWidgetState();
}
class _SecondWidgetState extends State<SecondWidget> {
#override
Widget build(BuildContext context) {
print('Second widget built');
return const Center(child: Text('Second here!'));
}
}
Possible cause of your problem.
It could be that when refreshing, you actually are updating the state of a parent widget that, on cascade, causes the re build of your second widget.
If state is handled correctly, and your second widget doesn't depends on your first widget state, the refresh should not rebuild the second.
How to clip Stack children within it's size.
In this image there are 3 grid-Items using orange color and every item using InkWell to use hover-Method to Align on Stack. While hover:false the Pop PoP Widget won't be visible to the UI. With align property it works, but as you can see the Right Top GridItem's item:2 pop POp widget is visible outside the Stack<Griditem> and I want to make it invisible outside the stack. I've tested using clipBehavior: with every Clip enums.
I want to hide the Pop POp widget while it is outside the Stack and yes I need this pop-up effect.
For Flutter web and I'm using Flutter V2.5.2
Current Layout with Issue
Full Code to reproduce the issue
import 'package:flutter/material.dart';
void main() => runApp(
const MaterialApp(
home: Appp(),
),
);
class Appp extends StatelessWidget {
const Appp({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return const BodyX();
}
}
class BodyX extends StatelessWidget {
const BodyX({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return Scaffold(body: LayoutBuilder(
builder: (context, constraints) {
return GridView.count(
crossAxisCount: 2,
children: [
...List.generate(
3,
(index) => GridItem(
key: UniqueKey(),
maxWidth: constraints.maxWidth / 2,
),
),
],
);
},
));
}
}
class GridItem extends StatefulWidget {
const GridItem({
Key? key,
required this.maxWidth,
}) : super(key: key);
final double maxWidth;
#override
State<GridItem> createState() => _AppXState();
}
class _AppXState extends State<GridItem> {
bool _isHovered = false;
#override
Widget build(BuildContext context) {
print("ItemWidth : ${widget.maxWidth}");
return SizedBox(
//though it wont effect here,
// just finding the size of Grid because it will 1x1
width: widget.maxWidth,
height: widget.maxWidth,
child: InkWell(
onTap: () {},
hoverColor: Colors.black,
onHover: (value) {
setState(() {
_isHovered = value;
});
},
child: Stack(
clipBehavior: Clip.antiAliasWithSaveLayer,
children: [
Container(
color: Colors.deepOrange.withOpacity(.2),
),
AnimatedAlign(
alignment: Alignment(0, _isHovered ? .7 : 2),
child: Container(
padding: const EdgeInsets.all(22),
color: Colors.greenAccent,
child: const Text(
"Pop POp",
),
),
duration: const Duration(
milliseconds: 200,
),
)
],
),
),
);
}
}
If you don't want a Widget to draw beyond its layout size, you can use ClipRect to clip it.
In your case, you can wrap ClipRect on your Stack, like so:
ClipRect(
child: Stack(
children: ...
),
)
Further more, you can use ClipRRect to clip a rounded rectangle shape (circular border) or ClipPath to clip a custom shape, like a triangle. You can read more about these widgets in the official docs.
I am trying to use an AnimatedSwitcher within Stack. This leads to very strange behaviour of the animation. It animates the respective child widget (a red box in my case) in the center of my Stack and upon completion it snaps to the top left corner of my screen(which is where I would also like the animation to to take place). When I switch back, the same odd behaviour occurs.
My code looks as follows:
import 'package:flutter/material.dart';
void main() => runApp(MaterialApp(home: Home()));
class Home extends StatefulWidget {
#override
_HomeState createState() => _HomeState();
}
class _HomeState extends State<Home> {
bool _showMenu = false;
#override
Widget build(BuildContext context) {
return Scaffold(
body: SafeArea(
child: Stack(
children: [
GestureDetector(
onTap: () => setState(() => _showMenu = !_showMenu),
child: SizedBox.expand(
child: Container(
color: Colors.yellow,
),
),
),
AnimatedSwitcher(
duration: Duration(milliseconds: 500),
child: _showMenu
? Container(
key: UniqueKey(),
height: 200,
width: 200,
color: Colors.red,
)
: Container())
],
),
),
);
}
}
Which produces the following behaviour on the tap-event somewhere on the screen:
Any ideas why the red box is not animated in the top left corner but only goes there once the animation has finished?
The problem lies, as #Marino Zorilla pointed out, in the unique key I specified for my animating widget. Once I removed this key and also changed the "empty" Container (for the false-condition of my ternary operation) to a SizedBox it works as desired.
Apparently, this has to do with how flutter works internally (when the element tree and the widget tree are compared to determine which widgets need to be rebuild). If the widget changes to a different type (like in my case from Container to SizedBox) no key is needed for flutter to know that this widget needs to be rebuild.
The correct code looks as follows:
import 'package:flutter/material.dart';
void main() => runApp(MaterialApp(home: Home()));
class Home extends StatefulWidget {
#override
_HomeState createState() => _HomeState();
}
class _HomeState extends State<Home> {
bool _showBox = false;
#override
Widget build(BuildContext context) {
return Scaffold(
body: SafeArea(
child: Stack(
children: [
GestureDetector(
onTap: () => setState(() => _showBox = !_showBox),
child: SizedBox.expand(
child: Container(
color: Colors.yellow,
),
),
),
AnimatedSwitcher(
duration: Duration(milliseconds: 500),
child: _showBox
? Container(
height: 200.0,
width: 200.0,
color: Colors.red,
)
: SizedBox(),
)
],
)),
);
}
}
Inside MyAppState I have created a ConfettiController called controllerTopCenter that I got from this package - https://pub.dev/packages/confetti
I also added a key to MyApp in the hopes of triggering the confetti animation from another class.
In the other class I added GlobalKey<MyAppState> key = GlobalKey<MyAppState>(); and then added key.currentState.controllerTopCenter.play(); which will trigger every time a certain thing is achieved within the app. However when it is triggered I get the error message "NoSuchMethodError: invalid member on null: 'controllerTopCenter'".
I'm wondering what is causing this error.
Here is my code:
import 'package:flutter/material.dart';
import 'package:confetti/confetti.dart';
void main() => runApp(MyApp());
class MyApp extends StatefulWidget {
MyApp({Key key}) : super(key: key);
#override
MyAppState createState() => MyAppState();
}
class MyAppState extends State<MyApp> with SingleTickerProviderStateMixin {
ConfettiController controllerTopCenter;
void initState() {
controllerTopCenter =
ConfettiController(duration: const Duration(seconds: 10))
..addListener(() => setState(() {}));
super.initState();
}
void dispose() {
controllerTopCenter.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
Tap tap = new Tap();
return MaterialApp(
title: 'Sum Mini',
home: Scaffold(
body: Column(
children: [
Align(
alignment: Alignment.center,
child: ConfettiWidget(
confettiController: controllerTopCenter,
blastDirectionality: BlastDirectionality
.explosive, // don't specify a direction, blast randomly
shouldLoop:
true, // start again as soon as the animation is finished
colors: const [
Colors.green,
Colors.blue,
Colors.pink,
Colors.orange,
Colors.purple
], // manually specify the colors to be used
),
),
GestureDetector(
onTap: () {
tap.onTap();
},
child: Container(
width: 100.0,
height: 100.0,
decoration: BoxDecoration(
color: Colors.greenAccent,
shape: BoxShape.circle,
),
),
),
],
),
));
}
}
class Tap {
GlobalKey<MyAppState> key = GlobalKey<MyAppState>();
onTap() {
key.currentState.controllerTopCenter.play();
}
}
Any help would be greatly appreciated.
You can copy paste run full code below
You can move Tap tap outside from build and pass to MyApp
code snippet
Tap tap = new Tap();
void main() => runApp(MyApp(key: tap.key));
working demo
full code
import 'package:flutter/material.dart';
import 'package:confetti/confetti.dart';
Tap tap = new Tap();
void main() => runApp(MyApp(key: tap.key));
class MyApp extends StatefulWidget {
MyApp({Key key}) : super(key: key);
#override
MyAppState createState() => MyAppState();
}
class MyAppState extends State<MyApp> with SingleTickerProviderStateMixin {
ConfettiController controllerTopCenter;
void initState() {
controllerTopCenter =
ConfettiController(duration: const Duration(seconds: 10))
..addListener(() => setState(() {}));
super.initState();
}
void dispose() {
controllerTopCenter.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Sum Mini',
home: Scaffold(
body: Column(
children: [
Align(
alignment: Alignment.center,
child: ConfettiWidget(
confettiController: controllerTopCenter,
blastDirectionality: BlastDirectionality
.explosive, // don't specify a direction, blast randomly
shouldLoop:
true, // start again as soon as the animation is finished
colors: const [
Colors.green,
Colors.blue,
Colors.pink,
Colors.orange,
Colors.purple
], // manually specify the colors to be used
),
),
GestureDetector(
onTap: () {
tap.onTap();
},
child: Container(
width: 100.0,
height: 100.0,
decoration: BoxDecoration(
color: Colors.greenAccent,
shape: BoxShape.circle,
),
),
),
],
),
));
}
}
class Tap {
GlobalKey<MyAppState> key = GlobalKey<MyAppState>();
onTap() {
key.currentState.controllerTopCenter.play();
}
}