i am using Dismissible widget to call Navigator.of(context) like following
#override
Widget build(BuildContext context) {
return
Dismissible(
resizeDuration: const Duration(milliseconds: 1),
direction: DismissDirection.vertical,
key: const Key('key'),
onDismissed: (_) => Navigator.of(context).pop(),
child:myWidget()
it is working great ..
but Dismissible has some direction options like vertical horizontal .. etc ...
my need is that direction be smoothly in the whole point even the corners like following
How i can make this job ? .. if it is not possible .. what should i use to make this job done?
Related
I'm looking for a screen independent way to detect clicks outside a widget.
Normally to detect clicks outside a widget I would need to wrap my screen around a GestureDetector with behavior: HitTestBehavior.opaque (or something like that).
Example:
#override
Widget build(BuildContext context) {
return Scaffold(
body: GestureDetector(
behavior: HitTestBehavior.opaque,
onTap: () => handleClickOutside(),
child: Container(
child: MyWidget(),
),
),
);
}
The problem is that if I want to use this widget anywhere, I would have to wrap every screen...
So my question is, is there a way to achieve what I'm trying to do without the mandatory screen wrap?
You can reduce the amount of boiler plate code by creating you own widget that collects all the stuff you need on every page:
class MyPage extends StatelessWidget {
final Widget child;
MyPage({super.key, required this.child});
#override
Widget build(BuildContext context) {
return Scaffold(
body: GestureDetector(
behavior: HitTestBehavior.opaque,
onTap: handleClickOutside,
child: child,
),
);
}
void handleClickOutside() {
// here is where you handle the click outside any widget
}
}
Note that you Container in you sample is not necessary.
Note the use of a method tear-off for the onTap parameter
with the following example code, is get a very ugly animation.
I would even say, it's no animation at all.
The next Page will just appear after the setstate is called.
How can I create a smooth delete animation using PageView?
If it is not possible via PageView, is there any alternative, that has the "snapping cards" feature?
Here is my code:
class SwipeScreen extends StatefulWidget {
const SwipeScreen({Key key}) : super(key: key);
static const routeName = '/swipe';
#override
_SwipeScreenState createState() => _SwipeScreenState();
}
class _SwipeScreenState extends State<SwipeScreen> {
List<String> content = ['one', 'two', 'three', 'four', 'five'];
#override
Widget build(BuildContext context) {
return Scaffold(
body: PageView.builder(
scrollDirection: Axis.vertical,
itemCount: content.length,
controller: PageController(viewportFraction: 0.8),
itemBuilder: (context, index) {
return Dismissible(
key: ValueKey(content[index]),
child: Card(
child: Container(
height: MediaQuery.of(context).size.height * 0.8,
child: Text('test'),
),
),
onDismissed: (direction) {
setState(() {
content = List.from(content)..removeAt(index);
});
},
);
},
),
);
}
}
Replacing PageView.builder() with ListView.builder() will create a smoother animation.
Hopefully this is what you're looking for!
Unfortunately, the PageView widget is not intended to be used with the Dismissible widget as the animation when the dismiss is complete is not implemented.
You can still change your PageView to a ListView and set a physics to PageScrollPhysics() to get the animation on dismiss but you will probably encounter some other issues on Widget sizes
I'm using OpenContainer animation to open a screen that could display alert dialog upon the opening of the screen - the case of the item the screen is trying to display is no longer valid or deleted.
Because OpenContainer renders the screen during the animation, the alert dialog is displayed several times.
My attempt to address the issue was to modify the OpenContainer buildPage method to return animation status to openBuilder callback. Is there better way to do without modifying OpenContainer code?
child: AnimatedBuilder(
animation: animation,
builder: (BuildContext context, Widget child) {
if (animation.isCompleted) {
return SizedBox.expand(
child: Material(
color: openColor,
elevation: openElevation,
shape: openShape,
child: Builder(
key: _openBuilderKey,
builder: (BuildContext context) {
return openBuilder(context, closeContainer, false); // added false
},
),
),
);
}
Code to reproduce the issue - https://gist.github.com/MartinJLee/0992a986ad641ef5b4f477fb1ce69249
You can add a listener to your AnimationController something like this
Consider you have an AnimationController like this -
AnimationController _animationController = AnimationController(
vsync: this,
duration: Duration(seconds: 5),
);
Then you can add a Status Listener to this _animationController using the addStatusListener method, something like this -
_animationController.addStatusListener((status) {
if(status == AnimationStatus.completed){
//Do something here
}
});
This listener will be called everytime the animation state changes.
Hope this helps!
I was able to do using the following code on the container for openBuilder.
void initState() {
super.initState();
WidgetsBinding.instance
.addPostFrameCallback((_) => yourFunction(context));
}
see original answers - Flutter: Run method on Widget build complete
Project
Hi, I'm trying to make some custom transition in flutter between two simple screen. My goal is to use Navigator.push(context, MyRoute(..)) to call another the second screen on top of the first one. My problem is that I want the second screen to be only half size of the height of the device. The rest of the screen should only display the old page, maybe with some kind of blur.
I'm searching for a BottomSheet style effect but without using the actual widget.
Problem
No matter what I try, when Navigator.push is called, the new screen will always be resized to fill the entire screen and I'm unable to get a smaller scaffold on top with some transparency to hide the old page.
Thanks
I think you can do something like the following. First create a transparent page route. Here is a class that extends the PageRoute class to create transparent page route so you can see what is behind it. It overrides the "opaque" value and sets it to false.
import 'package:flutter/widgets.dart';
/// Creates a route that leaves the background behind it transparent
///
///
class TransparentRoute extends PageRoute<void> {
TransparentRoute({
#required this.builder,
RouteSettings settings,
}) : assert(builder != null),
super(settings: settings, fullscreenDialog: false);
final WidgetBuilder builder;
#override
bool get opaque => false;
#override
Color get barrierColor => null;
#override
String get barrierLabel => null;
#override
bool get maintainState => true;
#override
Duration get transitionDuration => Duration(milliseconds: 350);
#override
Widget buildPage(BuildContext context, Animation<double> animation,
Animation<double> secondaryAnimation) {
final result = builder(context);
return FadeTransition(
opacity: Tween<double>(begin: 0, end: 1).animate(animation),
child: Semantics(
scopesRoute: true,
explicitChildNodes: true,
child: result,
),
);
}
}
Use that as your page route.
In the Scaffold that you want to navigate to you can do the following. This will make it so it only takes up half of the screen and shows the previous page behind it:
Navigator.of(context).push(
TransparentRoute(
builder: (context) => Scaffold(
appBar: null,
backgroundColor: Colors.transparent,
body: Align(
alignment: Alignment.bottomCenter,
child: Container(
height: MediaQuery.of(context).size.height / 2,
color: Colors.red,
),
),
)
),
);
If I understand your problem correctly I think this should do the trick!
I currently have a SliverList whose items are loaded dynamically. The issue is that once these items are loaded, the SliverList updates without animating the changes, making the transition between loading & loaded very jarring.
I see that AnimatedList exists but it isn't a sliver so I can't place it directly in a CustomScrollView.
You probably know about this now, but might as well mention it here to help people.
You can use SliverAnimatedList. It achieves the required result.
SliverAnimatedList Construction:
itemBuilder defines the way new items are built. The builder should typically return a Transition widget, or any widget that would use the animation parameter.
SliverAnimatedList(
key: someKey,
initialItemCount: itemCount,
itemBuilder: (context, index, animation) => SizeTransition(
sizeFactor: animation,
child: SomeWidget()
)
)
Adding/removing dynamically
You do that by using insertItem and removeItem methods of SliverAnimatedListState. You access the state by either:
providing a Key to the SliverAnimatedList and use key.currentState
using SliverAnimatedList.of(context) static method.
In cases where you need to make changes from outside of the list, you're always going to need to use the key.
Here's a full example to clarify things. Items are added by tapping the FloatingActionButton and are removed by tapping the item itself. I used both the key and of(context) ways to access the SliverAnimatedListState.
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
class SliverAnimatedListTest extends StatefulWidget {
#override
_SliverAnimatedListTestState createState() => _SliverAnimatedListTestState();
}
class _SliverAnimatedListTestState extends State<SliverAnimatedListTest> {
int itemCount = 2;
// The key to be used when accessing SliverAnimatedListState
final GlobalKey<SliverAnimatedListState> _listKey =
GlobalKey<SliverAnimatedListState>();
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text("Sliver Animated List Test")),
// fab will handle inserting a new item at the last index of the list.
floatingActionButton: FloatingActionButton(
child: Icon(Icons.add),
onPressed: () {
_listKey.currentState.insertItem(itemCount);
itemCount++;
},
),
body: CustomScrollView(
slivers: <Widget>[
SliverAnimatedList(
key: _listKey,
initialItemCount: itemCount,
// Return a widget that is wrapped with a transition
itemBuilder: (context, index, animation) =>
SizeTransition(
sizeFactor: animation,
child: SomeWidget(
index: index,
// Handle removing an item using of(context) static method.
// Returned widget should also utilize the [animation] param
onPressed: () {
SliverAnimatedList.of(context).removeItem(
index,
(context, animation) => SizeTransition(
sizeFactor: animation,
child: SomeWidget(
index: index,
)));
itemCount--;
}),
))
],
),
);
}
}
class SomeWidget extends StatelessWidget {
final int index;
final Function() onPressed;
const SomeWidget({Key key, this.index, this.onPressed}) : super(key: key);
#override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.all(20.0),
child: Center(
child: FlatButton(
child: Text("item $index"),
onPressed: onPressed,
)));
}
}
You can use Implicitly Animated Reorderable List
import 'package:implicitly_animated_reorderable_list/implicitly_animated_reorderable_list.dart';
import 'package:implicitly_animated_reorderable_list/transitions.dart';
...
SliverImplicitlyAnimatedList<Comment>(
items: comments,
areItemsTheSame: (a, b) => a.id == b.id,
itemBuilder: (BuildContext context, Animation<double> animation, Comment item, int index) {
return SizeFadeTransition(
sizeFraction: 0.7,
curve: Curves.easeInOut,
animation: animation,
child: CommentSliver(
comment: item,
),
);
},
);
I have a workaround for using a simple ListView with a Sliver. It's not perfect and it has limitations, but it works for the case where you just have 2 Slivers, the AppBar and a SliverList.
NestedScrollView(
headerSliverBuilder: (_, _a) => SliverAppBar(<Insert Code Here>),
body: MediaQuery.removePadding(
removeTop: true,
context: context,
child: AnimatedList(
<InsertCodeHere>
)))
You can tweak around with the Widget tree, but that's the basic idea. Wrap the sliver appbar in a NestedScrollView and place the List in the body.
You could Wrap your list items in an AnimatedWidget
Read about it in the docs AnimatedWidget