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
Related
I have the following simple full code ..
import 'package:flutter/material.dart';
class Profile extends StatefulWidget {
const Profile({ Key? key, }) : super(key: key);
#override
ProfileState createState() => ProfileState();
}
class ProfileState extends State<Profile>{
#override
Widget build(BuildContext context) {
return SafeArea(
child: NestedScrollView(
headerSliverBuilder: (context,value){
return[
const SliverAppBar(
expandedHeight: 400,
)
];
},
body: ListView.builder(
itemCount: 200,
itemBuilder: (BuildContext context, int index) {
return Center(child: Text(index.toString()));
},
)
),
);
}
}
in the previous code, everything is ok and it shifted the scroll in a smooth way BUT when I provide ScrollController into my ListView.builder the scroll is no longer smooth anymore.
so Please How could I keep the first result (with no providing ScrollController) the same as (with providing ScrollController)? .
I recreated your requirements using CustomScrollView, the API is "harder" to use but it allows you implement more complex features (like nested scrollviews as you are doing) because we have direct access to the Slivers API.
You can see that almost any Flutter scrollable widget is a derivation of either CustomScrollView or ScrollView which makes use of Slivers.
NestedScrollView is a subclass of CustomScrollView.
ListView and GridView widgets are subclasses of ScrollView.
Although seems complicated a Sliver is just a portion of a scrollable area:
CustomScrollView(
controller: _scrollController,
slivers: [
const SliverAppBar(expandedHeight: 400),
SliverList(
delegate: SliverChildBuilderDelegate(
(context, index) {
return Center(child: Text('item $index.'));
},
),
),
],
)
So, instead of creating multiple scrollviews and trying to mix them together (which lead to buggy behaviors), just declare multiple scrollable areas and put them together inside a CustomScrollView, that's it.
See the working example at: https://dartpad.dev/?id=60cb0fa073975f3c80660815ae88af4e.
I am using the lib pinch_zoom_release_unzoom to pinch zoom image. I create it inside SingleChildScrollView but when user use 2 finger to pinch zoom image. it very hard to zoom because sometime page is Scrollable. so I want to solve this problem
this is my example code
import 'package:flutter/material.dart';
import 'package:modal_bottom_sheet/modal_bottom_sheet.dart';
import 'package:pinch_zoom_release_unzoom/pinch_zoom_release_unzoom.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Tutorial',
home: HomePage(),
);
}
}
class HomePage extends StatelessWidget {
String imageUrl = 'https://cdn.pixabay.com/photo/2015/04/23/22/00/tree-736885__480.jpg';
TransformationController controller = TransformationController();
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Flutter Tutorial'),
),
body: Column(
children: [
Center(
child: ElevatedButton(
onPressed: () {
showMaterialModalBottomSheet(
expand: false,
context: context,
builder: (context) => PinchZoomReleaseUnzoomWidget(
child: SingleChildScrollView(
controller: ModalScrollController.of(context),
physics: const ClampingScrollPhysics(),
child: Column(
children: [
const SizedBox(
height: 100,
),
Image.network(imageUrl),
const SizedBox(
height: 1000,
),
],
),
),
),
);
},
child: const Text(
'showModalBottomSheet',
),
),
),
],
),
);
}
}
You can interact with physics of scrollable widget to make scrolling different. For that purpose, you should change your physic inside SingleChildScrollView , whenever your zooming state changes. For example:
lass ParentWidget extends StatefulWidget {
const ParentWidget ({Key? key,}) : super(key: key);
#override
State<ParentWidget > createState() => _ParentWidgetState();
}
class _ParentWidgetState extends State<ParentWidget > {
late bool isScrolling;
#override
void initState() {
isScrolling = false;
super.initState();
}
#override
Widget build(BuildContext context) {
return SingleChildScrollView(
physics: isScrolling ? NeverScrollableScrollPhysics() : null,
child: YourWidget(function: (currentState) => setState(() {
isScrolling = currentState;
}));
class YourWidget extends StatelessWidget {
const SceneManaging({Key? key, this.callback}) : super(key: key);
final Function(bool isScrolling)? function;
}
In this case, you should call function inside your child widget whenever you use zoom. When it zooms - pass to it a true, to disable your parent widget scroll, whenever zooms actions stops - pass false.
physics: NeverScrollableScrollPhysics()
How the scroll view should respond to user input.
For example, determines how the scroll view continues to animate after the user stops dragging the scroll view.
Defaults to matching platform conventions. Furthermore, if primary is false, then the user cannot scroll if there is insufficient content to scroll, while if primary is true, they can always attempt to scroll.
To force the scroll view to always be scrollable even if there is insufficient content, as if primary was true but without necessarily setting it to true, provide an AlwaysScrollableScrollPhysics physics object, as in:
physics: const AlwaysScrollableScrollPhysics(),
To force the scroll view to use the default platform conventions and not be scrollable if there is insufficient content, regardless of the value of primary, provide an explicit ScrollPhysics object, as in:
physics: const ScrollPhysics(),
The physics can be changed dynamically (by providing a new object in a subsequent build), but new physics will only take effect if the class of the provided object changes. Merely constructing a new instance with a different configuration is insufficient to cause the physics to be reapplied. (This is because the final object used is generated dynamically, which can be relatively expensive, and it would be inefficient to speculatively create this object each frame to see if the physics should be updated.)
I'm using Package Confetti 0.6.0 ConfettiWidget() within Card()s generated in a GridView.builder
Upon scroll the animation stops working properly & even vanishes if I scroll too much.
I thought the issue had to do with lazy loading, so I tried two other particle animation widgets:
Particles 0.1.4 and
Animated Background 2.0.0
Both of which worked fine, displaying correct animation - even through scrolling.
-> It could be just an issue with Package Confetti 0.6.0, but I think the problem might come from the way I'm initializing the ConfettiController and calling .play() method & disposing of it.
Here is my complete simplified code:
class HomePage extends StatefulWidget {
#override
State<HomePage> createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> {
late ConfettiController _controllerCenter;
#override
void initState() {
super.initState();
_controllerCenter =
ConfettiController(duration: const Duration(seconds: 1000));
_controllerCenter.play();
}
#override
void dispose() {
_controllerCenter.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
return new Scaffold(
body: SafeArea(
child: new GridView.builder(
itemCount: 30,
itemBuilder: (BuildContext context, int index) {
return Card(
child: Center(
//
child: ConfettiWidget(
confettiController: _controllerCenter,
shouldLoop: true,
),
//
),
);
},
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 2,
),
),
),
);
}
}
-> I'd like to keep the animation running despite scrolling through the GridView.
I need to use a GridView.builder or GridView.count as data within the cards will be populated with Firebase Data.
I would like to display the index at the bottom of the listView by utilizing the scrollController, the same way it's displayed in the follow image :
After the user scrolls down, or scrolls up, the count on the left, highlighted by red, gets increased/decreased based on the scroll direction of the user.
What I want to achieve is to automatically update the displayed item's index, indicated by red on the picture. So whenever the user scrolls down or up, this index gets updated by the displayed item's index.
The picture shows that I have reached the 26th item. Whenever I scroll down or up, this index gets updated.
I have tried using the offset that is getting emitted for the scrolling event with no luck.
The way is using the scroll controller like you were doing.
You need to use a known item size and a listener.
// Declaring the controller and the item size
ScrollController _scrollController;
final itemSize = 100.0;
// Initializing
#override
void initState() {
_scrollController = ScrollController();
_scrollController.addListener(_scrollListener);
super.initState();
}
// Your list widget (must not be nested lists)
ListView.builder(
controller: _scrollController,
itemCount: <Your list length>,
itemExtent: itemSize,
itemBuilder: (context, index) {
return ListTile(<your items>);
},
),
// With the listener and the itemSize, you can calculate which item
// is on screen using the provided callBack. Something like this:
void _scrollListener() {
setState(() {
var index = (_scrollController.offset / itemSize).round() + 1;
});
}
Adding a listener to a scrollController will call the callback provided every time the list is scrolled. You can handle many behaviours of the list using the same logic, including identifying the type of event that fired the listener, the direction of the scroll, etc.
There is a lib called scroll to index that could help you. You could take the $index to show inside your toast message. Example bellow is from the lib's author:
import 'dart:math' as math;
import 'package:flutter/material.dart';
import 'package:scroll_to_index/scroll_to_index.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Scroll To Index Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: MyHomePage(title: 'Scroll To Index Demo'),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
static const maxCount = 100;
final random = math.Random();
final scrollDirection = Axis.vertical;
AutoScrollController controller;
List<List<int>> randomList;
#override
void initState() {
super.initState();
controller = AutoScrollController(
viewportBoundaryGetter: () => Rect.fromLTRB(0, 0, 0, MediaQuery.of(context).padding.bottom),
axis: scrollDirection
);
randomList = List.generate(maxCount, (index) => <int>[index, (1000 * random.nextDouble()).toInt()]);
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: ListView(
scrollDirection: scrollDirection,
controller: controller,
children: randomList.map<Widget>((data) {
return Padding(
padding: EdgeInsets.all(8),
child: _getRow(data[0], math.max(data[1].toDouble(), 50.0)),
);
}).toList(),
),
floatingActionButton: FloatingActionButton(
onPressed: _scrollToIndex,
tooltip: 'Increment',
child: Text(counter.toString()),
),
);
}
int counter = -1;
Future _scrollToIndex() async {
setState(() {
counter++;
if (counter >= maxCount)
counter = 0;
});
await controller.scrollToIndex(counter, preferPosition: AutoScrollPosition.begin);
controller.highlight(counter);
}
Widget _getRow(int index, double height) {
return _wrapScrollTag(
index: index,
child: Container(
padding: EdgeInsets.all(8),
alignment: Alignment.topCenter,
height: height,
decoration: BoxDecoration(
border: Border.all(
color: Colors.lightBlue,
width: 4
),
borderRadius: BorderRadius.circular(12)
),
child: Text('index: $index, height: $height'),
)
);
}
Widget _wrapScrollTag({int index, Widget child})
=> AutoScrollTag(
key: ValueKey(index),
controller: controller,
index: index,
child: child,
highlightColor: Colors.black.withOpacity(0.1),
);
}
https://medium.com/flutter-community/create-shop-list-with-flutter-d13d3c20d68b
Maybe this one can help you. Also source code is available. A simple math about item height might help.
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