I want lazy scrolling when my all page is scrolled so i have to use Custom Scroll View.
now CustomScrollView can have just silvers children, my problem is have many lists and widgets,
expanded widget that has his own list.
to achieve the best lazy scrolling i have to some how get to top level widget all my lists and that can become very nasty.
LeagueGamesList widget contain list of expanded widgets that every expanded widget is a list.
how i can do it and still keep order and maintain widgets?
SliverList(
delegate: SliverChildBuilderDelegate((BuildContext context, int index) {
return Container(
padding: EdgeInsets.only(left: 15),
child: Column(
children: <Widget>[
if (_highlights.length > 0)
Column(
children: [
Container(
child: Align(
alignment: Alignment.centerLeft,
child: Padding(
padding: const EdgeInsets.symmetric(vertical: 15),
child: Row(
children: [
Text(
'Highlights',
style: TextStyle(fontWeight: FontWeight.bold, fontSize: 18),
),
Image.asset(
AppIcons.rightArrow,
alignment: Alignment.center,
width: 20,
height: 20,
)
],
),
),
),
),
Container(
child: Align(
alignment: Alignment.centerLeft,
child: HighlightRow(highlights: _highlights),
),
),
],
),
Visibility(
visible: _advertisements.length != 0,
child: Padding(
padding: EdgeInsets.only(right: 15),
child: HomepageBanner(
advertisement: _homepageAdvertisement,
),
),
),
if (Provider.of<ArticlesProvider>(context).get().length > 0)
HomepageArticles(
articles: Provider.of<ArticlesProvider>(context).get(),
),
],
),
);
}, childCount: 1),
),
SliverList(
delegate: SliverChildBuilderDelegate((BuildContext context, int index) {
return Column(
children: [
Visibility(
visible: isLoading,
child: SpinKitFadingCircle(
color: Colors.black,
size: 20,
),
replacement: SizedBox(height: 20),
),
DateButton(
dateString: getDateString(),
dayForwards: dayForwards,
dayBackwards: dayBackwards,
),
SizedBox(height: 20),
],
);
}, childCount: 1),
),
SliverList(
delegate: SliverChildBuilderDelegate((BuildContext context, int index) {
print(index);
return LeagueGamesList(
leagueFixture: leagueFixtures[index],
closeExpanders: false,
openExpanders: isDateChanged,
rowHeight: rowHeight,
);
}, childCount: leagueFixtures.length),
),
],
)
my expanded container that will contain a list
class ExpandableContainer extends StatelessWidget {
final bool expanded;
final double collapsedHeight;
final double expandedHeight;
final Widget child;
ExpandableContainer({
#required this.child,
this.collapsedHeight = 0.0,
this.expandedHeight = 0,
this.expanded = true,
});
#override
Widget build(BuildContext context) {
double screenWidth = MediaQuery.of(context).size.width;
return new AnimatedContainer(
duration: new Duration(milliseconds: 500),
curve: Curves.easeInOut,
width: screenWidth,
height: expanded ? expandedHeight : collapsedHeight,
padding: EdgeInsets.all(0),
margin: EdgeInsets.all(0),
alignment: Alignment.topLeft,
child: child,
);
}
Instead of nesting lists in lists make only the deepest list be actual SliverLists and combine them together using MultiSlivers from my package sliver_tools
Related
I am trying to extend Stack a little bit down to create room for "All caught up" text.
Here is my code;
return Stack(
children: [
buildListView(scrollController),
buildBackToTop(scrollController, backtoTop),
buildBottomReached(isLastIndex),
],
);
Widget buildBackToTop(ScrollController scrollController, bool backtoTop) {
if (backtoTop) {
return Align(
alignment: Alignment.topCenter,
child: Padding(
padding: const EdgeInsets.all(8.0),
child: FloatingActionButton.extended(
onPressed: () {
scrollController.animateTo(0,
duration: const Duration(milliseconds: 200),
curve: Curves.linear);
},
label: const Text("Back to Top"),
),
),
);
} else {
return const SizedBox();
}
}
Widget buildBottomReached(bool isLastIndex) {
if (isLastIndex) {
return const Align(
alignment: Alignment.bottomCenter,
child: Text(
"All caught up 🎉",
style: TextStyle(
color: Colors.blue, fontWeight: FontWeight.bold, fontSize: 20.00),
),
);
} else {
return const SizedBox();
}
}
Widget buildListView(ScrollController scrollController) {
return ListView.builder(
controller: scrollController,
itemCount: 50,
itemBuilder: (context, index) {
return SizedBox(
width: 20,
child: Padding(
padding: const EdgeInsets.all(8.0),
child: Container(
width: 20,
decoration: BoxDecoration(
border: Border.all(color: Colors.black),
color: Colors.grey[100],
),
child: Center(
child: Align(
alignment: Alignment.centerLeft,
child: Padding(
padding: const EdgeInsets.all(8.0),
child: Text("Index $index"),
),
),
),
),
),
);
},
physics: const BouncingScrollPhysics());
}
When I do following,
return Stack(
children: [
Padding(
padding: const EdgeInsets.only(bottom: 30),
child: buildListView(scrollController),
),
buildBackToTop(scrollController, backtoTop),
buildBottomReached(isLastIndex),
],
);
This is desired but it adds whitespace to bottom all the time,
Is there a way to do this?
another valid solution for your case is to expand the Stack to the screen size, then align your widgets with the Align widget like this:
final mq = MediaQuery.of(context);
return ConstrainedBox(
constraints: BoxConstraints(
maxHeight: mq.size.height,
minHeight: mq.size.height,
maxWidth: mq.size.width,
minWidth: mq.size.width,
),
child: Stack(
fit: StackFit.expand,
children: [
buildListView(scrollController),
Align(
alignment: Alignment.bottomCenter,
child: buildBackToTop(scrollController, backtoTop),
),
buildBottomReached(isLastIndex),
],
),
);
Based on your UI. try Column widget instead of Stack.
return Column(
children: [
Expaneded(child: buildListView(scrollController)),
buildBackToTop(scrollController, backtoTop),
buildBottomReached(isLastIndex),
],
);
If it needs overlay, use Stack by combining listView and Text with Column.
Wrap the widget with Positioned and add bottom.
return Stack(
children: [
.....
Positioned(
bottom: yourValue,
child:
First, wrap Stack with ListView widget
Then, move buildBottomReached helper as a sibling of the Stack widget
return ListView(//this
children: [
Stack(
children: [
buildListView(),
buildBackToTop(backtoTop),
//buildBottomReached(isLastIndex),//move this a sibling of the Stack
],
),
buildBottomReached(isLastIndex),//moved here
],
);
Then, on buildListView helper, set ListView.builder property shrinkWrap to true.
Widget buildListView(ScrollController scrollController) {
return ListView.builder(
shrinkWrap: true,//this
controller: scrollController,
itemCount: 50,
itemBuilder: (context, index) {
return SizedBox(
width: 20,
child: Padding(
padding: const EdgeInsets.all(8.0),
child: Container(
width: 20,
decoration: BoxDecoration(
border: Border.all(color: Colors.black),
color: Colors.grey[100],
),
child: Center(
child: Align(
alignment: Alignment.centerLeft,
child: Padding(
padding: const EdgeInsets.all(8.0),
child: Text("Index $index"),
),
),
),
),
),
);
},
physics: const BouncingScrollPhysics());
}
I need to develope feature which paint the text by syllables (like screenshot shows). Bows and dots represent syllables (dot - 1 letter syllable, bow - 2 letter syllable). The painting is provided by dragging of green slider.
There is a variant of getting syllable position with the help of GlobalKey. I use the method List.generate for creating numerous GlobalKeys but get the exception Multiple widgets used the same GlobalKey. How can help me to resolve this task?
There is method for creating Row of Columns which include letter and bow or dot.
List<Widget> createDotAndBows(String sentence, List<GlobalKey> keys) {
DetailedScreenBloc _bloc = DetailedScreenBloc();
List<String> syllablesRow = [];
List<Widget> dotAndBows = [];
List<String> splittedText = sentence.split(' ');
for (int i = 0; i < splittedText.length; i++) {
syllablesRow.addAll(_bloc.syllables(splittedText[i]));
}
_syllablesTotal.add(syllablesRow.length);
for (int index = 0; index < syllablesRow.length; index++) {
dotAndBows.add(
(syllablesRow[index] != ' ')
? IntrinsicWidth(
key: keys[index],
child: Center(
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
Padding(
padding: const EdgeInsets.only(
bottom: FairyTaleSizes.tinyPadding4,
),
child: Text(
syllablesRow[index],
style: FairyTaleStyle(
fontWeight: FontWeight.w800,
fontSize: getProportionateScreenWidth(22),
color: FairyTaleColors.fullBlack,
),
textAlign: TextAlign.center,
maxLines: 1,
),
),
(_bloc.getSyllableType(syllablesRow[index]) == 2)
? Padding(
padding: const EdgeInsets.symmetric(
horizontal: FairyTaleSizes.theMostTinyPadding2,
),
child: CustomPaint(
painter: BowPainter(),
child: Center(
child: Container(
height: getProportionateScreenHeight(20),
),
),
),
)
: (_bloc.getSyllableType(syllablesRow[index]) == 1)
? Center(
child: Container(
height: getProportionateScreenHeight(23),
width: getProportionateScreenHeight(23),
decoration:
const BoxDecoration(shape: BoxShape.circle, color: FairyTaleColors.fullBlack),
),
)
: Container(),
],
),
),
)
: Text(
' ',
key: keys[index],
),
);
}
return dotAndBows;
}
This is the implementation of the method above in UI
class DetailedScreen extends StatefulWidget {
final String? detailedBanner;
final List<String>? fullText;
const DetailedScreen({this.detailedBanner, this.fullText});
#override
State<DetailedScreen> createState() => _DetailedScreenState();
}
class _DetailedScreenState extends State<DetailedScreen> {
final ItemScrollController _itemScrollController = ItemScrollController();
final ItemPositionsListener _itemPositionsListener = ItemPositionsListener.create();
int currentIndex = 1;
final _bloc = DetailedScreenBloc();
List<GlobalKey> keysList = [];
final GlobalKey _sliderKey = GlobalKey();
#override
void initState() {
super.initState();
}
#override
void dispose() {
super.dispose();
_bloc.dispose();
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: SizedBox(
height: MediaQuery.of(context).size.height,
child: SingleChildScrollView(
physics: const NeverScrollableScrollPhysics(),
child: Stack(
children: <Widget>[
Container(
width: SizeConfig.screenWidth,
height: SizeConfig.screenHeight,
decoration: const BoxDecoration(
color: FairyTaleColors.lightGold,
),
),
Column(
children: [
Align(
alignment: Alignment.topCenter,
child: Container(
height: SizeConfig.screenHeight / 2,
width: SizeConfig.screenWidth,
decoration: BoxDecoration(
image: DecorationImage(
image: Image.asset(widget.detailedBanner ?? '').image,
fit: BoxFit.fitWidth,
),
),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Align(
alignment: Alignment.topLeft,
child: ArrowWidget(
height: getProportionateScreenHeight(84),
width: getProportionateScreenWidth(36),
isForward: false,
onTap: () => Navigator.of(context).pop(),
),
),
StreamBuilder<bool>(
stream: _bloc.isVoiceOnStream,
builder: (context, snapshot) {
bool? _isVoiceOn = snapshot.data;
return Padding(
padding: const EdgeInsets.only(
right: FairyTaleSizes.bigHorizontalPadding36,
),
child: Align(
alignment: Alignment.topRight,
child: VoiceSwitcher(
isVoiceOn: _isVoiceOn,
onPressed: () {
if (_isVoiceOn != null && _isVoiceOn) {
_bloc.switchVoice(false);
} else {
_bloc.switchVoice(true);
}
}),
),
);
}),
],
),
),
),
Padding(
padding: const EdgeInsets.only(
top: FairyTaleSizes.middleHorizontalPadding22,
),
child: Stack(
children: [
StreamBuilder<int>(
stream: _bloc.syllablesTotalStream,
builder: (context, snapshot) {
return SizedBox(
height: MediaQuery.of(context).size.height,
child: ScrollablePositionedList.builder(
shrinkWrap: true,
padding: const EdgeInsets.only(
bottom: FairyTaleSizes.twoHundredPadding216,
),
physics: const NeverScrollableScrollPhysics(),
itemScrollController: _itemScrollController,
itemPositionsListener: _itemPositionsListener,
itemCount: widget.fullText!.length,
itemBuilder: (_, index) {
int total = snapshot.data ?? 0;
keysList.addAll(
List.generate(
total,
(index) => GlobalKey(),
),
);
print('$index - $keysList');
return Container(
padding: const EdgeInsets.only(
bottom: FairyTaleSizes.itemsMassivePadding140,
),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: _bloc.createDotAndBows(
widget.fullText![index],
keysList,
),
),
);
},
),
);
}),
StreamBuilder<double>(
initialData: 0.0,
stream: _bloc.shadowPositionStream,
builder: (context, snapshot) {
double _position = snapshot.data ?? 0.0;
return Positioned(
left: _position,
child: Container(
color: FairyTaleColors.lightGold.withOpacity(0.9),
height: getProportionateScreenWidth(23),
width: MediaQuery.of(context).size.width,
),
);
}),
StreamBuilder<List>(
stream: CombineLatestStream.list([
_bloc.sliderValueStream,
_bloc.shadowPositionStream,
]),
builder: (context, snapshot) {
double _sliderValue = snapshot.data?[0] ?? 0.0;
double _shadowPosition = snapshot.data?[1] ?? 0.0;
return Padding(
padding: const EdgeInsets.symmetric(
vertical: FairyTaleSizes.hundredPadding100,
horizontal: FairyTaleSizes.massivePadding64,
),
child: SliderTheme(
data: const SliderThemeData(
trackShape: GradientRectSliderTrackShape(),
trackHeight: 14.0,
thumbShape: GradientSliderThumbShape(
enabledThumbRadius: 16,
pressedElevation: 5.1,
),
),
child: Slider(
key: _sliderKey,
min: 0.0,
max: 200.0,
value: _sliderValue,
inactiveColor: FairyTaleColors.inactiveGrey,
onChanged: (value) {
_sliderValue = value;
_shadowPosition = value * 4.5;
_bloc.changeSliderValue(_sliderValue);
_bloc.stepBySyllable(keysList);
// _bloc.changeShadowPosition(_shadowPosition);
},
onChangeEnd: (value) {
if (value == 200) {
keysList.clear();
_bloc.changeSliderValue(0.0);
_shadowPosition = 0;
_bloc.scrollString(_itemScrollController, currentIndex);
_bloc.changeShadowPosition(0.0);
currentIndex++;
}
},
),
),
);
}),
],
),
),
],
),
],
),
),
),
);
}
}
I am building an app that has gmail type animation in which when the circular avatar button is clicked the List tile gets selected with a rotation animation.
The list of items are built using ListView.builder. But when the circle avatar of a list item is clicked it affects the whole list of items and rotates every circle avatar. From similar questions asked I changed Listtile into a separate stateful widget as every item could hold its own state. But this doesn't change the output.
I didn't get the output even after using keys(Unique and object). I am stuck here for a long time but couldn't figure what and how to do it. Is there any way to differentiate widgets in Listview.builder and maintain their states seperately?
Alternatively when I use checkbox/selectable icon its holding the state for each item induvidually.
class MailList extends StatefulWidget {
int index;
List<MailModel> account;
Animation rotationAnim;
AnimationController circleAvatarController;
OneShotAnimation riveAnimationController1;
OneShotAnimation riveAnimationController2;
List<int> selectedIndexes = [];
UniqueKey key;
MailList({
required this.index,
required this.account,
required this.rotationAnim,
required this.circleAvatarController,
required this.riveAnimationController1,
required this.riveAnimationController2,
required this.selectedIndexes,
required this.key
}):super(key: key);
#override
_MailListState createState() => _MailListState();
}
class _MailListState extends State<MailList> {
#override
Widget build(BuildContext context) {
return Listener(
key: UniqueKey(),
child: Dismissible(
key: ValueKey(widget.index),
child: GestureDetector(
key: ValueKey(widget.index),
child: Padding(
padding: const EdgeInsets.symmetric(vertical: 10, horizontal: 15.0),
child: Container(
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(20.0),
shape: BoxShape.rectangle,
color: widget.account[widget.index].isSelected ? Colors.blue.withOpacity(0.2): null
),
height: 70,
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
Column(
children: [
GestureDetector(
child: AnimatedBuilder(
builder: (context, child){
return Transform(
alignment: Alignment.center,
child: CircleAvatar(
key: ValueKey(widget.index),
backgroundColor: widget.account[widget.index].isSelected ? widget.account[widget.index].defaultOnSelectedColor : widget.account[widget.index].senderInfo.profileColor.withOpacity(0.75),
child: TweenAnimationBuilder(
tween: Tween<double>(begin: 0, end: 1),
builder: (context, double anim, child){
return widget.rotationAnim.value > 90 && widget.account[widget.index].isSelected
? Opacity(
child: Icon(
widget.account[widget.index].defaultOnSelectedIcon,
color: Colors.white,
),
opacity: anim,
)
: Text(
widget.account[widget.index].senderInfo.senderName[0].toString(),
style: const TextStyle(
fontWeight: FontWeight.bold,
fontSize: 21.0,
color: Colors.white
));
},
duration: const Duration(seconds: 1),
),
),
transform: /*isSelected(widget.index) ? (Matrix4.identity()
..setEntry(3, 2, 0.001)
..rotateY((widget.rotationAnim.value) / 180 * math.pi))
: (Matrix4.identity()
..setEntry(3, 2, 0.001)
..rotateY(0 / 180 * math.pi))*/
Matrix4.identity()
..setEntry(3, 2, 0.001)
..rotateY((widget.rotationAnim.value) / 180 * math.pi),
);
},
animation: widget.circleAvatarController,
),
onTap: (){
selectUnSelect(widget.index);
widget.account[widget.index].isSelected = !widget.account[widget.index].isSelected;
widget.account[widget.index].isSelected ? widget.circleAvatarController.forward() : widget.circleAvatarController.reverse();
setState(() {
});
},
)
],
mainAxisAlignment: MainAxisAlignment.start,
),
const SizedBox(
width: 10.0,
),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Expanded(
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
widget.account[widget.index].senderInfo.senderName,
style: TextStyle(
fontSize: 16.0,
fontWeight: widget.account[widget.index].isSeen
? FontWeight.normal
: FontWeight.bold
),
),
Text(
"10:00"
)
],
)
),
const SizedBox(
height: 5.0,
),
Text(
widget.account[widget.index].title,
overflow: TextOverflow.ellipsis,
style: TextStyle(
fontSize: 14.5,
fontWeight: widget.account[widget.index].isSeen
? FontWeight.normal
: FontWeight.bold
),
),
const SizedBox(
height: 5.0,
),
Expanded(
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
Expanded(
child: Text(
widget.account[widget.index].content,
overflow: TextOverflow.ellipsis,
),
),
GestureDetector(
child: Container(
child: widget.account[widget.index].starred ? Icon(
Icons.star,
color: Colors.orange[300],
):
Icon(
Icons.star_border
),
),
onTap: (){
setState(() {
widget.account[widget.index].starred = !widget.account[widget.index].starred;
});
},
)
],
),
),
const SizedBox(
height: 5.0,
),
],
),
)
],
),
),
),
onTap: (){
},
),
background: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Expanded(
child: dragWidget(
left: 8.0,
controller: widget.riveAnimationController1
)),
],
),
secondaryBackground: Container(
alignment: Alignment.centerRight,
color: Colors.green,
child: Padding(
padding: EdgeInsets.only(
right: 8.0
),
child: SizedBox(
height: 50.0,
width: 50.0,
child: RiveAnimation.asset(
"assets/gmail_drag_anim.riv",
controllers: [
widget.riveAnimationController2
],
animations: [
"Animation 1"
],
fit: BoxFit.fill,
//antialiasing: false,
),
),
),
),
onDismissed: (direction){
widget.account.removeAt(widget.index);
},
),
);
}
bool isSelected(int index){
return widget.selectedIndexes.contains(index);
}
selectUnSelect(int index){
if(isSelected(index)){
widget.circleAvatarController.reverse();
Future.delayed(Duration(milliseconds: 320), (){
widget.selectedIndexes.remove(index);
setState(() {
});
});
}else{
widget.selectedIndexes.add(index);
}
}
Widget dragWidget({
double? left,
double? right,
required OneShotAnimation controller
}){
return Container(
alignment: right != null ? Alignment.centerRight : Alignment.centerLeft,
color: Colors.green,
child: Padding(
padding: EdgeInsets.only(
left: left != null ? left : 0.0,
right: right != null ? right: 0.0
),
child: SizedBox(
height: 50.0,
width: 50.0,
child: RiveAnimation.asset(
"assets/gmail_drag_anim.riv",
controllers: [
controller
],
animations: [
"Animation 1"
],
fit: BoxFit.fill,
//antialiasing: false,
),
),
),
);
}
} ```
// ListView.builder:
SliverList(
delegate: SliverChildBuilderDelegate(
(context, index){
return MailList(
key: GlobalKey<_MailListState>(),
index: index,
account: account1_mail_data,
rotationAnim: rotationAnim,
circleAvatarController: circleAvatarController,
riveAnimationController1: _riveAnimationController1,
riveAnimationController2: _riveAnimationController2,
selectedIndexes: selectedIndexes
);
},
childCount: account1_mail_data.length
),
)
As you may know, the Stepper widget puts step items into a row until it overflows the screen, with no way to make it scroll.
However, I found a way found in this link - https://github.com/flutter/flutter/issues/40601
Here is the code:
Widget _buildHorizontal() {
final List children = [
for (int i = 0; i < widget.steps.length; i += 1) ...[
InkResponse(
onTap: widget.steps[i].state != StepState.disabled
? () {
if (widget.onStepTapped != null) widget.onStepTapped(i);
}
: null,
canRequestFocus: widget.steps[i].state != StepState.disabled,
child: Row(
children: [
Container(
height: 72.0,
child: Center(
child: _buildIcon(i),
),
),
Container(
margin: const EdgeInsetsDirectional.only(start: 12.0),
child: _buildHeaderText(i),
),
],
),
),
if (!_isLast(i))
Container(
margin: const EdgeInsets.symmetric(horizontal: 8.0),
height: 1.0,
color: Colors.grey.shade400,
),
],
];
return Column(
children: <Widget>[
Material(
elevation: 2.0,
child: Container(
height: 65,
margin: const EdgeInsets.symmetric(horizontal: 24.0),
child: ListView(
scrollDirection: Axis.horizontal,
children: children,
),
),
),
Expanded(
child: ListView(
padding: const EdgeInsets.all(24.0),
children: <Widget>[
AnimatedSize(
curve: Curves.fastOutSlowIn,
duration: kThemeAnimationDuration,
vsync: this,
child: widget.steps[widget.currentStep].content,
),
_buildVerticalControls(),
],
),
),
],
);
}`
My issue right now is that I cannot make the ListView autoscrollable.
I have tried to use _scrollController = ItemScrollController but did not help.
I am pretty new to flutter. I have build a landing page using grid view and added a bottom navigation bar. The navigation bar is called first after login in and I have added the screen to the navigation class. The issue am facing is that the navigation bar is on top of my grid items, when I try to scroll up, the grid items are sticky and not moving, what am I not doing right??
my home screen code
class GridDashboard extends StatelessWidget {
var services = [
"Home",
"Update",
"Bluetooth",
"Forms",
"Supervisor",
"Messages",
"Settings",
"App updates",
"Logout",
];
var images = [
"assets/home.png",
"assets/updated.png",
"assets/bluetooth.png",
"assets/todo.png",
"assets/supervisor.png",
"assets/message.png",
"assets/setting.png",
"assets/update.ico",
"assets/logout.png",
];
#override
Widget build(BuildContext context) {
List<Items> myList = [home, update, bluetooth, forms, supervisor, messages, settings, check, logout];
var color = 0xff453658;
return Padding(
padding: const EdgeInsets.all(8.0),
child: Container(
height: 500,
// margin: EdgeInsets.only(top: 10),
// padding: EdgeInsets.all(20),
child: GridView.builder(
// add this
shrinkWrap: true,
itemCount: services.length,
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 3,
childAspectRatio: MediaQuery.of(context).size.width /
(MediaQuery.of(context).size.height / 1.4),
),
itemBuilder: (BuildContext context, int index) {
return GestureDetector(
onTap: () {
Navigator.push(context, new MaterialPageRoute<Widget>(
builder: (BuildContext context) {
if(myList != null){
return myList[index].screen;
}else{
return null;
}
}));
},
child: Padding(
padding: EdgeInsets.all(3),
child: Card(
elevation: 10,
child: ListView(
children: <Widget>[
SizedBox(
height: 20,
),
Image.asset(
images[index],
height: 50.0,
width: 50.0,
),
Padding(
padding: const EdgeInsets.all(20.0),
child: Text(
services[index],
style: TextStyle(
fontSize: 16.0,
height: 1.2,
color: Colors.white,
fontWeight: FontWeight.bold),
textAlign: TextAlign.center,
),
),
],
),
color: Color(color),
),
),
);
},
),
),
);
}
}
class Items {
String title;
String subtitle;
String event;
String img;
final Widget screen;
Items({this.title, this.subtitle, this.event, this.img, this.screen});
}
my Nav bar code
class _NavSCreenState extends State<NavSCreen> {
final List<Widget> _screens = [Home()];
final List<IconData> _icons = const [
Icons.home,
Icons.settings,
MdiIcons.accountCircleOutline,
MdiIcons.accountGroupOutline,
Icons.menu,
];
int _selectedIndex = 0;
#override
Widget build(BuildContext context) {
return DefaultTabController(
length: _icons.length,
child: Scaffold(
body: IndexedStack(index: _selectedIndex, children: _screens),
bottomNavigationBar: Padding(
padding: const EdgeInsets.only(bottom: 8.0),
child: CustomTabBar(
icons: _icons,
selectedIndex: _selectedIndex,
onTap: (index) => setState(() => _selectedIndex = index),
),
),
));
}
}
Try this by adding SingleChildScrollView. Hope this will solve your problem.
#override
Widget build(BuildContext context) {
List<Items> myList = [home, update, bluetooth, forms, supervisor, messages, settings, check, logout];
var color = 0xff453658;
return Scaffold(
body: SingleChildScrollView(
child: Padding(
padding: const EdgeInsets.all(8.0),
child: Container(
height: 500,
// margin: EdgeInsets.only(top: 10),
// padding: EdgeInsets.all(20),
child: GridView.builder(
// add this
shrinkWrap: true,
itemCount: services.length,
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 3,
childAspectRatio: MediaQuery.of(context).size.width /
(MediaQuery.of(context).size.height / 1.4),
),
itemBuilder: (BuildContext context, int index) {
return GestureDetector(
onTap: () {
Navigator.push(context, new MaterialPageRoute<Widget>(
builder: (BuildContext context) {
if(myList != null){
return myList[index].screen;
}else{
return null;
}
}));
},
child: Padding(
padding: EdgeInsets.all(3),
child: Card(
elevation: 10,
child: ListView(
children: <Widget>[
SizedBox(
height: 20,
),
Image.asset(
images[index],
height: 50.0,
width: 50.0,
),
Padding(
padding: const EdgeInsets.all(20.0),
child: Text(
services[index],
style: TextStyle(
fontSize: 16.0,
height: 1.2,
color: Colors.white,
fontWeight: FontWeight.bold),
textAlign: TextAlign.center,
),
),
],
),
color: Color(color),
),
),
);
},
),
),
),
),
);
}
You need to embed the GridView into a SingleChildScrollView widget. This widget handles the scrolling for its child, which is in your case the GridView. The same applies to ListView.
See the links for detailed documentation.
// ...
child: Container(
height: 500,
child: SingleChildScrollView(
child: GridView.builder(
// ...
)
)
)
// ...
EDIT
I forgot, that you have to give a GridView a height to work inside a SingleChildScrollView. You can use a Container that wraps the GridView.
// ...
child: Container(
height: 500,
child: SingleChildScrollView(
child: Container(
height: 500,
child: GridView.builder(
// ...
)
)
)
)
// ...
But with that approach you have to give your GridView a predefined height. An alternative is the CustomScrollView but you have to use a SliverGrid for that.
CustomScrollView(
slivers: [
SliverGrid(
// ...
)
]
)