How to create infinity PageView in Flutter - flutter

How to create an PageView which are supported circle scroll in Flutter? That's mean when I stand on 0 page, I could scroll to left to the last page.
Updated: I answered this question and update a gist source also.

What I did with mine was I set my page controller's initialPage to 10000 * pageCount, and in my page view itself, I have itemBuilder: (context, index) => pages[index % pageCount], and itemCount: null. It's not really infinite, but most users will not scroll 10000 pages back, so it works for my use case. As far as I know, there isn't an elegant way to make it truly infinite. You could probably set up a listener so that whenever the controller.page is about to become 0, you set it back to 10000 * pageCount or something similar.

I found a solution here. I create a CustomScrollView with 2 slivers. One for go forward, one for go back. However, I have to calculate if my list short.
typedef Widget Builder(BuildContext buildContext, int index);
class InfiniteScrollView extends StatefulWidget {
final Key center = UniqueKey();
final Builder builder;
final int childCount;
InfiniteScrollView(
{Key key, #required this.builder, #required this.childCount})
: super(key: key);
#override
_InfiniteScrollViewState createState() => _InfiniteScrollViewState();
}
class _InfiniteScrollViewState extends State<InfiniteScrollView> {
#override
Widget build(BuildContext context) {
return Container(
child: CustomScrollView(
center: widget.center,
scrollDirection: Axis.horizontal,
slivers: <Widget>[
SliverList(
delegate: SliverChildBuilderDelegate(
(BuildContext context, int index) => widget.builder(
context, widget.childCount - index % widget.childCount - 1),
),
),
SliverList(
delegate: SliverChildBuilderDelegate(widget.builder),
key: widget.center,
)
],
),
);
}
}
Updated: I write a new widget which support infinity TabBar.
https://gist.github.com/MrNinja/6f6a5fc73803bdfaf2a493a35c258fee

Related

How can i smoothly scroll between NestedScrollView and ListView.builder

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.

Dynamic Row height in GridView.builder (Flutter)

I'm trying to use a GridView.builder with dynamic height depending on the largest item in each row. A Wrap is not ok for this, because it renders all items at once. the builder is only rendering the visible items. I won't calculate a childAspectRatio manually for each row.
Any ideas?
I've made a WrapBuilder which sort of like a Wrap but uses a builder. The caveat though is that you need to know the width of the items beforehand. If that's okay you can try using this class:
import 'dart:math';
import 'package:flutter/cupertino.dart';
typedef ValueWidgetBuilder<T> = Widget Function(T value);
class WrapBuilder extends StatelessWidget {
final double itemWidth;
final List items;
final ValueWidgetBuilder itemBuilder;
const WrapBuilder(
{Key? key,
required this.itemWidth,
required this.items,
required this.itemBuilder})
: super(key: key);
#override
Widget build(BuildContext context) {
return LayoutBuilder(builder: (context, constraints) {
var itemsPerRow = max(1, constraints.maxWidth ~/ itemWidth);
return ListView.builder(
shrinkWrap: true,
controller: ScrollController(),
itemCount: (items.length / itemsPerRow).ceil(),
itemBuilder: (BuildContext context, int index) {
var rowItems = items.sublist(itemsPerRow * index,
min(itemsPerRow * (index + 1), items.length));
return Row(children: [
for (final item in rowItems)
SizedBox(
width: itemWidth,
child: itemBuilder(item))
]);
},
);
});
}
}
You might need to tweak it for your use case. In my case I have a list that is some data and the itemBuilder takes one of this data as parameter.
you can pass height as variable in list and then set height for the row from list

Flutter - Using Dismissible on a PageView creates an ugly animation

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

Provider in ListView not getting disposed?

I have ListView with a list (values) containing ~600 objects:
ListView.builder(
itemCount: values.length,
itemBuilder: (context, index) {
return ChangeNotifierProxyProvider<DatabaseRepository,
AlbumViewModel>(
initialBuilder: (context) => AlbumViewModel(album: values[index]),
builder: (context, dbRepo, vm) => vm..databaseRepository = dbRepo,
child: const AlbumEntryView(),
);
With the according AlbumEntryView
class AlbumEntryView extends StatelessWidget {
const AlbumEntryView({Key key}) : super(key: key);
Widget _flagIconButton(AlbumViewModel model) {
IconData icon = model.album.flagged ? Icons.flag : Icons.outlined_flag;
return IconButton(icon: Icon(icon), onPressed: () => model.toggleFlagged());
}
#override
Widget build(BuildContext context) {
AlbumViewModel model = Provider.of(context);
return ListTile(
title: Text(model.album.title),
trailing: _flagIconButton(model),;
}
I checked the memory in my app for the amount of ChangeNofifierProxyProvider objects and it keeps increasing when I scroll through the list (it holds the AlbumEntryView object).
As far as I know, the ListView releases widgets when they are not in the viewPort and the Provider is getting disposed when it's not in the view hierarchy anymore. Does that mean, that the list items are not getting disposed/reused resulting the objects to grow in the memory view or did I misunderstand something? If so, how can I properly use the provider as a list item without facing the issue of a potential leak of the app?

Animating changes in a SliverList

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