I want to display Images from list in CarouselSlider.builder from index 1 to 3.
Here as below I got all Data from Api in _imageList.
List<ImageModel> _imageList = [];
#override
void initState() {
super.initState();
ApiHelper().getImageList().then((images) {
setState(() {
_imageList = images;
});
});
}
Here is my Image Slider Code as below.
CarouselSlider.builder(
itemCount: _imageList.length,
itemBuilder:
(BuildContext context, int index, realindex) {
return Image.network(
"$imageUrl" + _imageList[index].imgPath!,
width: MediaQuery.of(context).size.width - 40,
fit: BoxFit.fill,
);
},
options: CarouselOptions(
viewportFraction: 1,
enlargeCenterPage: true,
initialPage: 1,
),
),
So how can i do this?
Thank you.
in your initState method you can just create a sublist of the images list so your imageList only contains the indices 1-3.
_imageList = images.sublist(1, 4);
I made slide learning app but slide was looping playing continuously
I need to stop auto play it reaches the end.
I'm using card_swiper: ^1.0.2
I update my full code is here
-I'm also using a Assets audio player
I try loop: false it won't work
Thanks in advance
I don't know how to use it
import 'package:assets_audio_player/assets_audio_player.dart';
import 'package:card_swiper/card_swiper.dart';
import 'package:flutter/material.dart';
class Chemistry extends StatefulWidget {
#override
_ChemistryState createState() => _ChemistryState();
}
class _ChemistryState extends State<Chemistry> {
List images = [
'assets/images/che/s.jpg',
'assets/images/che/t.jpg',
'assets/images/che/u.jpg',
'assets/images/che/v.jpg',
'assets/images/che/w.jpg',
'assets/images/che/x.jpg',
'assets/images/che/y.jpg',
'assets/images/che/z.jpg',
];
#override
Widget build(BuildContext context) {
return Scaffold(
body:Swiper(
itemCount: images.length,
loop: false,
itemBuilder: (BuildContext context, int index) {
return Padding(
padding: const EdgeInsets.all(27.0),
child: Image.asset(
images[index],
),
);
},
indicatorLayout: PageIndicatorLayout.COLOR,
onIndexChanged: (index) {
playaudio(index);
},
autoplayDelay: 4000,
autoplay: true,
pagination: FractionPaginationBuilder(
color: Colors.red, activeColor: Colors.green, fontSize: 10),
// control: SwiperControl(),
),
),
);
}
}
void playaudio(index) async {
AssetsAudioPlayer.newPlayer().open(
Audio('assets/audio/Chemistry/a$index.mp3'),
);
}
You can set loop: false - to disable continuous loop mode.
If you want autoplay, then set autoplay: true
According to the documentation https://pub.dev/packages/card_swiper you can set loop attribute to false
Swiper(
itemBuilder: (BuildContext context,int index){
return Image.network("https://via.placeholder.com/350x150",fit: BoxFit.fill,);
},
itemCount: 3,
loop:false,
pagination: SwiperPagination(),
),
I have 4 widgets that might be visible. They should be displayed 2 in a row, but I can't know in advance if widget1 and widget2 will be visible, or widget1 and widget4, so I don't know how to wrap them in Row.
I tried using GridView.count, but I run into problems when one of them is marked dirty and rebuilt. Even if I hardcode two widgets unconditionally, I run into this proble:
return Container(
child: GridView.count(
shrinkWrap: true,
physics: ScrollPhysics(),
crossAxisCount: 2,
children: [
IncrementDecrementNumber(label: config['optional'][1]['data_value'], value: vel1,
onMinusPressed: (){
setState(() {
if (vel1 > 0)
vel1--;
});
},
onPlusPressed: () {
setState(() {
vel1++;
});
},
),
IncrementDecrementNumber(label: config['optional'][3]['data_value'], value: vel2,
onMinusPressed: (){
setState(() {
if (vel2 > 0)
vel2--;
});
},
onPlusPressed: () {
setState(() {
vel2++;
});
},
)
],
),
);
}
It looks like this:
When I click any of the + or - buttons, this happens (and on every click the widgets are shrunk, until they disappear):
Is there a way to use GridView and prevent this?
I still don't know why this happens, but using GridView instead GridView.count seems to work without shrinking.
return Container(
child: GridView.count(
shrinkWrap: true,
physics: ScrollPhysics(),
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 2,
crossAxisSpacing: 5.0,
mainAxisSpacing: 5.0,
),
children: [
IncrementDecrementNumber(label: config['optional'][1]['data_value'], value: vel1,
onMinusPressed: (){
setState(() {
if (vel1 > 0)
vel1--;
});
},
onPlusPressed: () {
setState(() {
vel1++;
});
},
),
IncrementDecrementNumber(label: config['optional'][3]['data_value'], value: vel2,
onMinusPressed: (){
setState(() {
if (vel2 > 0)
vel2--;
});
},
onPlusPressed: () {
setState(() {
vel2++;
});
},
)
],
),
);
}
I would like to develop a slider to adjust the brightness of the image instantly.
Just like want Instagram did.
I adopted StreamBuilder to retrieve the image after the brightness is adjusted.
But the image keeps showing image -> black -> image -> black when I am moving the slider.
And the slider is laggy, keep jumping when I move.
Show Image code:
child: StreamBuilder(
stream: _imageRefresher.imageRefreshStream,
initialData: widget.post.imagesFileList[0].imageFile,
builder: (context, snap) {
return PageView.builder(
controller: PageController(
initialPage: 0
),
scrollDirection: Axis.horizontal,
itemCount: widget.post.imagesFileList.length,
onPageChanged: (index) {
setState(() {
listIndex = index;
print(listIndex);
});
},
itemBuilder: (BuildContext context, int index) {
return Hero(
tag: 'heroFeatured$index',
child: Container(
decoration: new BoxDecoration(
image: new DecorationImage(
fit: BoxFit.cover,
image: FileImage(snap.data)
),
),
),
);
},
);
}
)
Slider code:
child:Slider(
label: 'Brightness',
min: -100,
max: 100,
value: widget.post.imagesFileList[listIndex].brightness,
onChanged: (value) {
print(value);
setState(() {
widget.post.imagesFileList[listIndex].brightness = value;
});
_imageRefresher.updatePicutre(widget.post.imagesFileList[listIndex].imagePath, value);
//updatePicutre(widget.post.imagesFileList[listIndex].brightness, value);
},
),
StreamController code:
class ImageRefresher {
StreamController<File> _controller;
final File imageFile;
ImageRefresher({this.imageFile}){
_controller = StreamController();
}
Stream<File> get imageRefreshStream => _controller.stream;
void updatePicutre(String imagePath, double brightness) async{
File f = await FlutterNativeImage.adjustBrightness(imagePath, brightness);
_controller.sink.add(f);
}
dispose() {
_controller.close();
}
}
I think StreamBuildier wasn't designed to support image updates. Check out the Texture class from Flutter dev it might be better suited for this.
https://api.flutter.dev/flutter/widgets/Texture-class.html
How can I realize items lazy loading for endless listview? I want to load more items by network when user scroll to the end of listview.
You can listen to a ScrollController.
ScrollController has some useful information, such as the scrolloffset and a list of ScrollPosition.
In your case the interesting part is in controller.position which is the currently visible ScrollPosition. Which represents a segment of the scrollable.
ScrollPosition contains informations about it's position inside the scrollable. Such as extentBefore and extentAfter. Or it's size, with extentInside.
Considering this, you could trigger a server call based on extentAfter which represents the remaining scroll space available.
Here's an basic example using what I said.
class MyHome extends StatefulWidget {
#override
_MyHomeState createState() => _MyHomeState();
}
class _MyHomeState extends State<MyHome> {
ScrollController controller;
List<String> items = List.generate(100, (index) => 'Hello $index');
#override
void initState() {
super.initState();
controller = ScrollController()..addListener(_scrollListener);
}
#override
void dispose() {
controller.removeListener(_scrollListener);
super.dispose();
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: Scrollbar(
child: ListView.builder(
controller: controller,
itemBuilder: (context, index) {
return Text(items[index]);
},
itemCount: items.length,
),
),
);
}
void _scrollListener() {
print(controller.position.extentAfter);
if (controller.position.extentAfter < 500) {
setState(() {
items.addAll(List.generate(42, (index) => 'Inserted $index'));
});
}
}
}
You can clearly see that when reaching the end of the scroll, it scrollbar expends due to having loaded more items.
Thanks for Rémi Rousselet's approach, but it does not solve all the problem. Especially when the ListView has scrolled to the bottom, it still calls the scrollListener a couple of times. The improved approach is to combine Notification Listener with Remi's approach. Here is my solution:
bool _handleScrollNotification(ScrollNotification notification) {
if (notification is ScrollEndNotification) {
if (_controller.position.extentAfter == 0) {
loadMore();
}
}
return false;
}
#override
Widget build(BuildContext context) {
final Widget gridWithScrollNotification = NotificationListener<
ScrollNotification>(
onNotification: _handleScrollNotification,
child: GridView.count(
controller: _controller,
padding: EdgeInsets.all(4.0),
// Create a grid with 2 columns. If you change the scrollDirection to
// horizontal, this would produce 2 rows.
crossAxisCount: 2,
crossAxisSpacing: 2.0,
mainAxisSpacing: 2.0,
// Generate 100 Widgets that display their index in the List
children: _documents.map((doc) {
return GridPhotoItem(
doc: doc,
);
}).toList()));
return new Scaffold(
key: _scaffoldKey,
body: RefreshIndicator(
onRefresh: _handleRefresh, child: gridWithScrollNotification));
}
The solution use ScrollController and I saw comments mentioned about page.
I would like to share my finding about package incrementally_loading_listview
https://github.com/MaikuB/incrementally_loading_listview.
As packaged said : This could be used to load paginated data received from API requests.
Basically, when ListView build last item and that means user has scrolled down to the bottom.
Hope it can help someone who have similar questions.
For purpose of demo, I have changed example to let a page only include one item
and add an CircularProgressIndicator.
...
bool _loadingMore;
bool _hasMoreItems;
int _maxItems = 30;
int _numItemsPage = 1;
...
_hasMoreItems = items.length < _maxItems;
...
return IncrementallyLoadingListView(
hasMore: () => _hasMoreItems,
itemCount: () => items.length,
loadMore: () async {
// can shorten to "loadMore: _loadMoreItems" but this syntax is used to demonstrate that
// functions with parameters can also be invoked if needed
await _loadMoreItems();
},
onLoadMore: () {
setState(() {
_loadingMore = true;
});
},
onLoadMoreFinished: () {
setState(() {
_loadingMore = false;
});
},
loadMoreOffsetFromBottom: 0,
itemBuilder: (context, index) {
final item = items[index];
if ((_loadingMore ?? false) && index == items.length - 1) {
return Column(
children: <Widget>[
ItemCard(item: item),
Card(
child: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
children: <Widget>[
Row(
crossAxisAlignment:
CrossAxisAlignment.start,
children: <Widget>[
Container(
width: 60.0,
height: 60.0,
color: Colors.grey,
),
Padding(
padding: const EdgeInsets.fromLTRB(
8.0, 0.0, 0.0, 0.0),
child: Container(
color: Colors.grey,
child: Text(
item.name,
style: TextStyle(
color: Colors.transparent),
),
),
)
],
),
Padding(
padding: const EdgeInsets.fromLTRB(
0.0, 8.0, 0.0, 0.0),
child: Container(
color: Colors.grey,
child: Text(
item.message,
style: TextStyle(
color: Colors.transparent),
),
),
)
],
),
),
),
Center(child: CircularProgressIndicator())
],
);
}
return ItemCard(item: item);
},
);
full example https://github.com/MaikuB/incrementally_loading_listview/blob/master/example/lib/main.dart
Package use ListView index = last item and loadMoreOffsetFromBottom to detect when to load more.
itemBuilder: (itemBuilderContext, index) {
if (!_loadingMore &&
index ==
widget.itemCount() -
widget.loadMoreOffsetFromBottom -
1 &&
widget.hasMore()) {
_loadingMore = true;
_loadingMoreSubject.add(true);
}
here is my solution for find end of listView
_scrollController.addListener(scrollListenerMilli);
if (_scrollController.position.pixels == _scrollController.position.maxScrollExtent) {
getMoreData();
}
If you want to load more data when 1/2 or 3/4 of a list view size, then use this way.
if (_scrollController.position.pixels == (_scrollController.position.maxScrollExtent * .75)) {//.5
getMoreData();
}
Additional -> Make sure you called getMore API only one time when reaching to the bottom. You can solve this in many ways, This is one of the ways to solve this by boolean variable.
bool loadMore = false;
if (_scrollController.position.pixels == _scrollController.position.maxScrollExtent && !loadMore) {
loadMore = true;
getMoreData().then(() => loadMore = false);
}
here is my approach which is inspired by answers above,
NotificationListener(onNotification: _onScrollNotification, child: GridView.builder())
bool _onScrollNotification(ScrollNotification notification) {
if (notification is ScrollEndNotification) {
final before = notification.metrics.extentBefore;
final max = notification.metrics.maxScrollExtent;
if (before == max) {
// load next page
// code here will be called only if scrolled to the very bottom
}
}
return false;
}
Use lazy_load_scrollview: 1.0.0 package that use same concept behind the scenes that panda world answered here. The package make it easier to implement.
The solutions posted don't solve the issue if you want to achieve lazy loading in up AND down direction. The scrolling would jump here, see this thread.
If you want to do lazy loading in up and down direction, the library bidirectional_listview could help.
Example (Source):
static const double kItemHeight = 30.0;
BidirectionalScrollController controller;
double oldScrollPosition = 0.0;
#override
void initState() {
super.initState();
for (int i = -10; i <= 10; i++) {
items[i] = "Item " + i.toString();
}
controller = new BidirectionalScrollController()
..addListener(_scrollListener);
}
#override
void dispose() {
controller.removeListener(_scrollListener);
super.dispose();
}
#override
void build() {
// ...
List<int> keys = items.keys.toList();
keys.sort();
return new BidirectionalListView.builder(
controller: controller,
physics: AlwaysScrollableScrollPhysics(),
itemBuilder: (context, index) {
return Container(
child: Text(items[index]),
height: kItemHeight,
},
itemCount: keys.first,
negativeItemCount: keys.last.abs(),
);
// ...
}
// Reload new items in up and down direction and update scroll boundaries
void _scrollListener() {
bool scrollingDown = oldScrollPosition < controller.position.pixels;
List<int> keys = items.keys.toList();
keys.sort();
int negativeItemCount = keys.first.abs();
int itemCount = keys.last;
double positiveReloadBorder = (itemCount * kItemHeight - 3 * kItemHeight);
double negativeReloadBorder =
(-(negativeItemCount * kItemHeight - 3 * kItemHeight));
// reload items
bool rebuildNecessary = false;
if (scrollingDown && controller.position.pixels > positiveReloadBorder)
{
for (int i = itemCount + 1; i <= itemCount + 20; i++) {
items[i] = "Item " + i.toString();
}
rebuildNecessary = true;
} else if (!scrollingDown &&
controller.position.pixels < negativeReloadBorder) {
for (int i = -negativeItemCount - 20; i < -negativeItemCount; i++) {
items[i] = "Item " + i.toString();
}
rebuildNecessary = true;
}
// set new scroll boundaries
try {
BidirectionalScrollPosition pos = controller.position;
pos.setMinMaxExtent(
-negativeItemCount * kItemHeight, itemCount * kItemHeight);
} catch (error) {
print(error.toString());
}
if (rebuildNecessary) {
setState(({});
}
oldScrollPosition = controller.position.pixels;
}
I hope that this helps a few people :-)
The accepted answer is correct but you can also do as follows,
Timer _timer;
Widget chatMessages() {
_timer = new Timer(const Duration(milliseconds: 300), () {
_scrollController.animateTo(
_scrollController.position.maxScrollExtent,
curve: Curves.easeOut,
duration: const Duration(milliseconds: 300),
);
});
return StreamBuilder(
stream: chats,
builder: (context, snapshot) {
return snapshot.hasData
? ListView.builder(
// physics: NeverScrollableScrollPhysics(),
controller: _scrollController,
shrinkWrap: true,
reverse: false,
itemCount: snapshot.data.documents.length,
itemBuilder: (context, index) {
return MessageTile(
message: snapshot.data.documents[index].data["message"],
sendByMe: widget.sendByid ==
snapshot.data.documents[index].data["sendBy"],
);
})
: Container();
},
);
}
There is also this package, taking away the boilerplate: https://pub.dev/packages/lazy_load_scrollview
There is a much simpler solution than working with Scroll Controllers and Notifications. Just use the built in lazy loading feature of ListView Builders:
I suggest (and tested) to just wrap two FutureBuilders within each other and let them handle everything for you. Alternatively, the outer FutureBuilder can be replaced by loading the values in the initState.
Create FutureBuilder to retrieve the most compact version of your data. Best a url or an id of the data items to be displayed
Create a ListView.builder, which according to the flutter doc Flutter Lists Codebook, already takes care of the lazy loading part
The standard ListView constructor works well for small lists. To work with lists that contain a large number of items, it’s best to
use the ListView.builder constructor.
In contrast to the default ListView constructor, which requires creating all items at once, the ListView.builder() constructor
creates items as they’re scrolled onto the screen.
Within the ListView builder, add another FutureBuilder, which fetches the individual content.
You're done
Have a look at this example code.
#override
Widget build(BuildContext context) {
return FutureBuilder(
future: <get a short list of ids to fetch from the web>,
builder: (BuildContext context, AsyncSnapshot<List<int>> snapshot) {
if (snapshot.hasData) {
return Expanded(
child: ListView.builder(
itemCount: snapshot.data!.length,
itemBuilder: (BuildContext context, final int index) {
final int recordId = snapshot.data![index];
return FutureBuilder(
future: <get the record content from the web>,
builder: (BuildContext context,
AsyncSnapshot<Issue?> snapshot) {
if (snapshot.hasData) {
final Record? record = snapshot.data;
if (issue != null) {
return ListTile(
isThreeLine: true,
horizontalTitleGap: 0,
title: <build record widget>,
);
}
}
return ListTile(
isThreeLine: true,
horizontalTitleGap: 0,
title: const Text("Loading data..."));
});
}),
);
}
return const Text("Loading data...",
style: TextStyle(color: Colors.orange));
});
Let me know what you think. Performance was great when I've tried it, I'm wondering what you experienced with this. Sure, this needs some clean up, I know :D
This is an old question and the current answer is to use the ListView.builder method.
Same is true for the GridView.builder, please refer to the example below.
GridView.builder(
// ask GridView to cache and avoid redundant callings of Futures
cacheExtent: 100,
shrinkWrap: true,
itemCount: c.thumbnails.length,
// Define this as you like
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 3,
mainAxisSpacing: 0.0,
crossAxisSpacing: 0.0,
childAspectRatio: 1.0,
),
itemBuilder: (BuildContext context, int index) {
return FutureBuilder<Image>(builder: (ctx, snap) {
if (!snap.hasData) {
return const SizedBox.expand(); // show nothing
}
if (snap.hasError) {
return Text('An error occured ${snap.error}');
}
return snap.data!;
},
future: <YOUR THUMBNAIL FUTURE>,
);
}
);
You can handle it by knowing the current page and the last page
By using listview builder
itemBuilder: (context, index) {
if(list.length - 1 == index && currentPage! < lastPage!){
currentPage = currentPage! + 1;
/// Call your api here to update the list
return Progress();
}
return ///element widget here.
},