Related
I am currently using the carousel-slider library to get a carousel in Flutter.
This library is based on a PageView, and in a PageView the elements are centered.
That's the carousel I get:
And this is what I'd like to have:
Here is the code where is use the CarouselSlider:
CarouselSlider(
height: 150,
viewportFraction: 0.5,
initialPage: 0,
enableInfiniteScroll: false,
items: widget.user.lastGamesPlayed.map((game) {
return Builder(
builder: (BuildContext context) {
return Padding(
padding: EdgeInsets.symmetric(horizontal: 10),
child: GestureDetector(
onTap: () {
game.presentGame(context, widget.user);
},
child: ClipRRect(
borderRadius: BorderRadius.all(Radius.circular(25)),
child: Container(
color: Theme.MyColors.lightBlue,
child: Center(
child: Padding(
padding: EdgeInsets.all(20),
child: AutoSizeText(game.name,
style: TextStyle(fontSize: 70),
maxLines: 1)),
),
))));
},
);
}).toList(),
)
And here is the code inside the CarouselSlider library:
#override
Widget build(BuildContext context) {
return getWrapper(PageView.builder(
physics: widget.isScrollEnabled
? AlwaysScrollableScrollPhysics()
: NeverScrollableScrollPhysics(),
scrollDirection: widget.scrollDirection,
controller: widget.pageController,
reverse: widget.reverse,
itemCount: widget.enableInfiniteScroll ? null : widget.items.length,
onPageChanged: (int index) {
int currentPage =
_getRealIndex(index, widget.realPage, widget.items.length);
if (widget.onPageChanged != null) {
widget.onPageChanged(currentPage);
}
},
itemBuilder: (BuildContext context, int i) {
final int index = _getRealIndex(
i + widget.initialPage, widget.realPage, widget.items.length);
return AnimatedBuilder(
animation: widget.pageController,
child: widget.items[index],
builder: (BuildContext context, child) {
// on the first render, the pageController.page is null,
// this is a dirty hack
if (widget.pageController.position.minScrollExtent == null ||
widget.pageController.position.maxScrollExtent == null) {
Future.delayed(Duration(microseconds: 1), () {
setState(() {});
});
return Container();
}
double value = widget.pageController.page - i;
value = (1 - (value.abs() * 0.3)).clamp(0.0, 1.0);
final double height = widget.height ??
MediaQuery.of(context).size.width * (1 / widget.aspectRatio);
final double distortionValue = widget.enlargeCenterPage
? Curves.easeOut.transform(value)
: 1.0;
if (widget.scrollDirection == Axis.horizontal) {
return Center(
child:
SizedBox(height: distortionValue * height, child: child));
} else {
return Center(
child: SizedBox(
width:
distortionValue * MediaQuery.of(context).size.width,
child: child));
}
},
);
},
));
}
How can I prevent elements from being centered?
Thank you in advance
If you don't want to animate page size over scroll there is no need to use this carousel-slider library.
Also, PageView is not the best Widget to achieve the layout you want, you should use a horizontal ListView with PageScrollPhysics.
import 'package:flutter/material.dart';
class Carousel extends StatelessWidget {
Carousel({
Key key,
#required this.items,
#required this.builderFunction,
#required this.height,
this.dividerIndent = 10,
}) : super(key: key);
final List<dynamic> items;
final double dividerIndent;
final Function(BuildContext context, dynamic item) builderFunction;
final double height;
#override
Widget build(BuildContext context) {
return Container(
height: height,
child: ListView.separated(
physics: PageScrollPhysics(),
separatorBuilder: (context, index) => Divider(
indent: dividerIndent,
),
scrollDirection: Axis.horizontal,
itemCount: items.length,
itemBuilder: (context, index) {
Widget item = builderFunction(context, items[index]);
if (index == 0) {
return Padding(
child: item,
padding: EdgeInsets.only(left: dividerIndent),
);
} else if (index == items.length - 1) {
return Padding(
child: item,
padding: EdgeInsets.only(right: dividerIndent),
);
}
return item;
}),
);
}
}
Usage
Carousel(
height: 150,
items: widget.user.lastGamesPlayed,
builderFunction: (context, item) {
return ClipRRect(
borderRadius: BorderRadius.all(Radius.circular(25)),
child: Container(
width: 200,
color: Theme.MyColors.lightBlue,
child: Center(
child: Padding(
padding: EdgeInsets.all(20),
child: AutoSizeText(
item.name,
style: TextStyle(fontSize: 70),
maxLines: 1,
),
),
),
),
);
},
)
UPDATE
As observed by #AdamK, my solution doesn't have the same scroll physics behavior as a PageView, it acts more like a horizontal ListView.
If you are looking for this pagination behavior you should consider to write a custom ScrollPhysics and use it on your scrollable widget.
This is a very well explained article that helps us to achieve the desired effect.
I have the following code which displays network images in a carousel. When I start my app, I see a blank white screen in my carousel. I would like to show a placeholder image instead. I tried to put the placeholder image under the if clause but the if clause displays the placeholder only when I navigate to another screen and come back to the carousel screen. I want the placeholder to be displayed when I start my app. How can I achieve this ?
return Container(
child: FutureBuilder(
future: getCarouselWidget(),
builder: (context, AsyncSnapshot snapshot) {
List<NetworkImage> list = new List<NetworkImage>();
if (snapshot.connectionState == ConnectionState.waiting || snapshot.connectionState == ConnectionState.active
) {
debugPrint("connection state is " + snapshot.connectionState.toString() );
return new FadeInImage(
height: 200.0, // Change it to your need
width: 300.0, // Change it to your need
fit: BoxFit.cover,
placeholder: new AssetImage("assets/placeholder.jpg"),
image: new AssetImage("assets/placeholder.jpg"),
);
} else if(snapshot.connectionState == ConnectionState.done) {
debugPrint("connection state is inside else" + snapshot.connectionState.toString() );
if (snapshot.hasError) {
return new Text("fetch error");
} else {
for (int i = 0; i < snapshot.data.length; i++) {
//debugPrint("Index is " + idx.toString());
list.add(NetworkImage(snapshot.data[i].data["image"]));
//idx++;
}
return new Container(
height: 250.0,
child: InkWell(
child: new Carousel(
boxFit: BoxFit.cover,
images: list,
onImageTap: (imageIndex) {
Navigator.of(context).push(
new MaterialPageRoute(
builder: (context) => new CustomClass(
name:
snapshot.data[imageIndex].data["title"],
pic: snapshot
.data[imageIndex].data["image"]),
),
);
},
autoplay: false,
dotSize: 4.0,
indicatorBgPadding: 4.0,
animationCurve: Curves.fastOutSlowIn,
animationDuration: Duration(milliseconds: 1000),
)));
}
}
}),
);
you can use cached network image library which provides delay, placeholder and fade animations, an example :
child: new CachedNetworkImage(
imageUrl: "http://via.placeholder.com/350x150",
placeholder: new Image.asset('assets/placeholder.jpg'),
errorWidget: new Icon(Icons.error),
fit: BoxFit.fill,
fadeInCurve: Curves.easeIn ,
fadeInDuration: Duration(seconds: 2),
fadeOutCurve: Curves.easeOut,
fadeOutDuration: Duration(seconds: 2),
)
I use showRoundedModalBottomSheet, how can I adjust this modal height till the appbar?
[Update]
In showModalBottomSheet(...) set the property isScrollControlled:true.
It will make bottomSheet to full height.
[Original answer]
You can Implement the FullScreenDialog instead.
Flutter Gallery app has an example of FullScreenDialog
You can open your Dialog using below code:
Navigator.of(context).push(new MaterialPageRoute<Null>(
builder: (BuildContext context) {
return Dialog();
},
fullscreenDialog: true
));
Check this blog post too for more:
Hope it will help you.
You can control the height by using FractionallySizedBox and setting the isScrollControlled to true.
showModalBottomSheet(
context: context,
isScrollControlled: true,
builder: (context) {
return FractionallySizedBox(
heightFactor: 0.9,
child: Container(),
);
});
If you call showModalBottomSheet() with isScrollControlled: true, then the dialog will be allowed to occupy the whole height.
To adjust the height to the content, you can proceed as usually, for example, using Container and Wrap widgets.
Example:
final items = <Widget>[
ListTile(
leading: Icon(Icons.photo_camera),
title: Text('Camera'),
onTap: () {},
),
ListTile(
leading: Icon(Icons.photo_library),
title: Text('Select'),
onTap: () {},
),
ListTile(
leading: Icon(Icons.delete),
title: Text('Delete'),
onTap: () {},
),
Divider(),
if (true)
ListTile(
title: Text('Cancel'),
onTap: () {},
),
];
showModalBottomSheet(
context: context,
builder: (BuildContext _) {
return Container(
child: Wrap(
children: items,
),
);
},
isScrollControlled: true,
);
What worked for me was returning the modal's content wrapped in a DraggableScrollableSheet:
showModalBottomSheet(
backgroundColor: Colors.transparent,
context: context,
isScrollControlled: true,
isDismissible: true,
builder: (BuildContext context) {
return DraggableScrollableSheet(
initialChildSize: 0.75, //set this as you want
maxChildSize: 0.75, //set this as you want
minChildSize: 0.75, //set this as you want
expand: true,
builder: (context, scrollController) {
return Container(...); //whatever you're returning, does not have to be a Container
}
);
}
)
I guess the easiest way is:
showModalBottomSheet(
isScrollControlled: true,
context: context,
builder: (context) => Wrap(children: [YourSheetWidget()]),
);
You open class BottomSheet in library of flutter and change maxHeight
from
BoxConstraints getConstraintsForChild(BoxConstraints constraints) {
return BoxConstraints(
minWidth: constraints.maxWidth,
maxWidth: constraints.maxWidth,
minHeight: 0.0,
maxHeight: constraints.maxHeight * 9.0 / 16.0
);}
to
BoxConstraints getConstraintsForChild(BoxConstraints constraints) {
return BoxConstraints(
minWidth: constraints.maxWidth,
maxWidth: constraints.maxWidth,
minHeight: 0.0,
maxHeight: constraints.maxHeight
);}
You can create a new class with other name and copy source code from class BottomSheet and change maxHeight
You can wrap the contents in a Container and provide height to full height
await showModalBottomSheet(
context: context,
shape: const RoundedRectangleBorder(
borderRadius: BorderRadius.only(
topLeft: Radius.circular(30), topRight: Radius.circular(30))),
backgroundColor: Colors.white,
builder: (BuildContext context) {
return Container(
height: MediaQuery.of(context).size.height,
child: ListView(
)
)
}
}
You can modify this method in the definition of the bottom sheet. Normally, it is 9.0, but as you can see here I changed it to 13.0. 16.0 is full screen.
#override
BoxConstraints getConstraintsForChild(BoxConstraints constraints) {
return BoxConstraints(
minWidth: constraints.maxWidth,
maxWidth: constraints.maxWidth,
minHeight: 0.0,
maxHeight: isScrollControlled
? constraints.maxHeight
: constraints.maxHeight * 13.0 / 16.0,
);
}
I'm trying to use flutter popup menu button, but I can't seem to make it smaller with a scroll.
Is it doable? Or am I using the wrong widget to do it?
Image below as reference, would like to show only the first 4 / 5 items, and scroll to show the rest!
Thanks in advance!
You can create your own PopUp Widget instead.
A Card wrapped into a AnimatedContainer with specific dimensions and a ListView inside.
Place this widget on your screen using Stack and Positioned widgets so it will be above other elements on the top | right.
class CustomPopup extends StatefulWidget {
CustomPopup({
#required this.show,
#required this.items,
#required this.builderFunction,
});
final bool show;
final List<dynamic> items;
final Function(BuildContext context, dynamic item) builderFunction;
#override
_CustomPopupState createState() => _CustomPopupState();
}
class _CustomPopupState extends State<CustomPopup> {
#override
Widget build(BuildContext context) {
return Offstage(
offstage: !widget.show,
child: AnimatedContainer(
duration: Duration(milliseconds: 300),
height: widget.show ? MediaQuery.of(context).size.height / 3 : 0,
width: MediaQuery.of(context).size.width / 3,
child: Card(
elevation: 3,
child: MediaQuery.removePadding(
context: context,
removeTop: true,
child: ListView.builder(
scrollDirection: Axis.vertical,
itemCount: widget.items.length,
itemBuilder: (context, index) {
Widget item = widget.builderFunction(
context,
widget.items[index],
);
return item;
},
),
),
),
),
);
}
}
return Stack(
children: <Widget>[
Container(
color: Colors.blueAccent,
),
Positioned(
right: 0,
top: 60,
child: CustomPopup(
show: shouldShow,
items: [1, 2, 3, 4, 5, 6, 7, 8],
builderFunction: (context, item) {
return ListTile(
title: Text(item.toString()),
onTap: () {}
);
},
),
),
],
);
You can create this in two ways: the first one is PopupMenuButton widget and the second one is PopupRoute.
class HomePage extends StatefulWidget {
#override
_HomepageState createState() => _HomepageState();
}
class _HomepageState extends State {
Listitems = [1,2,3,4,5,6,7,8,9,10,11,12,13];
#override
Widget build(BuildContext context) {
return Scaffold(body: Center(
child: PopupMenuButton(
child: Icon(Icons.add_shopping_cart),
offset: Offset(-1.0, -220.0),
elevation: 0,
color: Colors.transparent,
shape: RoundedRectangleBorder(borderRadius: BorderRadius.all(Radius.circular(10))),
itemBuilder: (context) {
return <PopupMenuEntry<Widget>>[
PopupMenuItem<Widget>(
child: Container(
decoration: ShapeDecoration(
color: Colors.white,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(10))),
child: Scrollbar(
child: ListView.builder(
padding: EdgeInsets.only(top: 20),
itemCount: items.length,
itemBuilder: (context, index) {
final trans = items[index];
return ListTile(
title: Text(
trans.toString(),
style: TextStyle(
fontSize: 16,
),
),
onTap: () {
//what would you like to do?
},
);
},
),
),
height: 250,
width: 500,
),
)
];
}),
)
You can also adjust the number of items you want to show by reducing or increasing height of the container. I also added a scrollbar just in case.
You can use maxHeight for constrains property.
...
PopupMenuButton(
constraints:
BoxConstraints(minWidth: context.maxWidth, maxHeight: 300),
...
I'm having a hard time understanding how to best create a scrollable container for the body that holds inside children that by default are scrollable as well.
In this case the grid shouldn't scroll but it's the entire page that should scroll so you are able to see more of the elements inside the grid. So basically the whole content should move vertically with the addition of the ListView moving horizontally (but that works fine already)
I had it working but it was using a bunch of "silver" widget, and I'm hoping there's a better solution that works without using all those extra widgets.
Thanks
Here's my code so far:
class GenresAndMoodsPage extends AbstractPage {
#override
String getTitle() => 'Genres & Moods';
#override
int getPageBottomBarIndex() => BottomBarItems.Browse.index;
static const kListHeight = 150.0;
Widget _buildHorizontalList() => SizedBox(
height: kListHeight,
child: ListView.builder(
scrollDirection: Axis.horizontal,
itemCount: 20,
itemBuilder: (_, index) =>
CTile(heading: 'Hip Hop', subheading: '623 Beats'),
),
);
Widget _buildGrid() => GridView.count(
crossAxisCount: 2,
crossAxisSpacing: LayoutSpacing.sm,
mainAxisSpacing: LayoutSpacing.sm,
children: List.generate(10, (index) {
return CTile(
padding: false,
heading: 'Kevin Gates Type Beat',
subheading: '623 FOLLOWERS',
width: double.infinity,
);
}),
);
#override
Widget buildBody(_) {
return ListView(children: [
CSectionHeading('Popular Genres & Moods'),
_buildHorizontalList(),
CSectionHeading('All Genres & Moods'),
_buildGrid(),
]);
}
}
The result should be something like this
Create List with Horizontal Scroll direction and called it as a child for Vertical Scroll direction.
body: new ListView.builder(itemBuilder: (context, index){
return new HorizList();
})
class HorizList extends StatelessWidget{
#override
Widget build(BuildContext context) {
return new Container(
height: 100.0,
child: new ListView.builder(itemBuilder: (context, index){
return new Card(child: new Container(width: 80.0,
child: new Text('Hello'),alignment: Alignment.center,));
}, scrollDirection: Axis.horizontal,),
);
}
}
As we want Popular Genres & Moods section also to scroll, we should not using nestedScroll. In above example GridView is nested inside `ListView. Because of which when we scroll, only the GridView will scroll.
I used Only one ListView to achieve the similar screen.
Number of children = (AllGenresAndMoodsCount/2) + 1
divide by 2 as we are having 2 elements per row
+1 for the first element which is horizontal scroll view.
Please refer the code:
import 'package:flutter/material.dart';
void main() {
runApp(new Home());
}
class Home extends StatelessWidget {
#override
Widget build(BuildContext context) {
var image = new Image.network("http://www.gstatic.com/webp/gallery/1.jpg");
var container = new Container(
child: image,
padding: EdgeInsets.only(left: 5.0, right: 5.0, top: 5.0, bottom: 5.0),
width: 200.0,
height: 200.0,
);
return MaterialApp(
title: "Scroller",
home: Scaffold(
body: Center(
child: new ListView.builder(
itemBuilder: (context, index) {
if (index == 0) { //first row is horizontal scroll
var singleChildScrollView = SingleChildScrollView(
child: Row(
children: <Widget>[
container,
container,
container,
],
),
scrollDirection: Axis.horizontal);
return singleChildScrollView;
} else {
return new Row(
children: <Widget>[container, container],
);
}
},
itemCount: 10, // 9 rows of AllGenresAndMoods + 1 row of PopularGenresAndMoods
)),
),
);
}
}