I have a problem with listview inside PageView, when i scroll to the bottom, two rows are missing.
I can correct that by updating the height of the Container where the ListView is located but the screen rendering is not optimized for all the screen sizes.
Screenshot of the match results list
We can scroll 8 or 150 matches successfully, we will always miss the last 2 matches when we finish the scrolling action.
Here is the code where the pageview is managed :
class Results extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
theme: ThemeData(
primarySwatch: Colors.blue,
fontFamily: "Josefin",
visualDensity: VisualDensity.adaptivePlatformDensity,
),
home: HomeResults(),
);
}
}
class HomeResults extends StatefulWidget {
@override
_HomeResultsState createState() => _HomeResultsState();
}
class _HomeResultsState extends State<HomeResults> {
PageController _controller = PageController(
initialPage: 0,
);
int currentPage = 0;
@override
Widget build(BuildContext context) {
return Scaffold(
body: Container(
width: MediaQuery.of(context).size.width,
height: MediaQuery.of(context).size.height,
child: Column(
children: [
Container(
height: 45,
child: ListView.builder(
shrinkWrap: true,
itemBuilder: (ctx, i) {
return Container(
child: FlatButton(
onPressed: () {
_controller.animateToPage(i,
duration: Duration(milliseconds: 300),
curve: Curves.easeIn);
},
child: Text(
resultscateg[i],
style: TextStyle(
color: currentPage == i
? Colors.black
: Colors.black.withOpacity(0.4),
),
),
),
);
},
itemCount: resultscateg.length,
scrollDirection: Axis.horizontal,
),
),
Expanded(
child: PageView(
controller: _controller,
onPageChanged: (value) {
setState(() {
currentPage = value;
});
},
children: [
FutureBuilder<List<Match>>(
future: MatchRepository().getResults(1),
builder: (context, snapshot) {
if (snapshot.hasError) print(snapshot.error);
return snapshot.hasData
? ResultsWidget(resultsList: snapshot.data)
: Center(
child: CircularProgressIndicator(
valueColor:
new AlwaysStoppedAnimation<Color>(Colors.red),
));
},
),
FutureBuilder<List<Match>>(
future: MatchRepository().getResults(2),
builder: (context, snapshot) {
if (snapshot.hasError) print(snapshot.error);
return snapshot.hasData
? ResultsWidget(resultsList: snapshot.data)
: Center(
child: CircularProgressIndicator(
valueColor:
new AlwaysStoppedAnimation<Color>(Colors.red),
));
},
),
],
),
)
],
),
),
);
}
}
And here is the code of the results widget :
class ResultsWidget extends StatelessWidget {
final List<Match> resultsList;
ResultsWidget({Key key, this.resultsList}) : super(key: key);
@override
Widget build(BuildContext context) {
return Stack(
children: [
Container(
width: MediaQuery.of(context).size.width,
child: ListView(
children: [
//last results
Container(
// Pour gérer le padding entre les scores
height: MediaQuery.of(context).size.height,
child: resultsList.isEmpty
? Center(child: Text('Aucun résultat actuellement.'))
: ListView.builder(
itemCount: resultsList.length,
scrollDirection: Axis.vertical,
shrinkWrap: true,
itemBuilder: (ctx, i) => Padding(
padding: EdgeInsets.all(10),
child: Material(
color: Colors.white,
elevation: 5,
borderRadius: BorderRadius.circular(15),
clipBehavior: Clip.hardEdge,
child: Container(
width: MediaQuery.of(context).size.width,
height: 110,
padding: EdgeInsets.all(5),
child: Column(
mainAxisAlignment:
MainAxisAlignment.spaceAround,
children: <Widget>[
Container(
height: 30,
decoration: BoxDecoration(
image: DecorationImage(
image: AssetImage(
"assets/images/bgheader.png"),
fit: BoxFit.cover),
// Gère l'arrondi des bords de chaque score
borderRadius: BorderRadius.circular(10),
),
child: Row(
children: [
Expanded(
child: Padding(
padding: const EdgeInsets.all(6.0),
child: Text(
resultsList[i]
.etapecompetition
.libelle ??
"",
maxLines: 1,
textAlign: TextAlign.left,
overflow: TextOverflow.fade,
style: TextStyle(
fontSize: 14,
fontWeight: FontWeight.bold,
color: Colors.yellow)),
),
),
Expanded(
child: Padding(
padding: const EdgeInsets.all(6.0),
child: Text(
resultsList[i].roundLibelle ??
"",
maxLines: 1,
textAlign: TextAlign.right,
overflow: TextOverflow.fade,
style: TextStyle(
fontSize: 14,
color: Colors.white)),
),
),
],
),
),
Row(
mainAxisAlignment:
MainAxisAlignment.spaceEvenly,
children: <Widget>[
Container(
height: 50,
width: 50,
decoration: BoxDecoration(
shape: BoxShape.circle,
),
child: ClipRRect(
borderRadius:
BorderRadius.circular(10),
child: Image.network(
'https://ruiznicolas.ovh/img/logos/${resultsList[i].equipe1.logo ?? "asa.png"}',
height: 95,
fit: BoxFit.fitHeight,
),
),
),
SizedBox(
//Marge du logo de gauche par rapport au reste
width: 5,
),
Expanded(
//Colonne de l'equipe 1
child: Column(
//Gère le centrage du texte
mainAxisAlignment:
MainAxisAlignment.spaceEvenly,
crossAxisAlignment:
CrossAxisAlignment.start,
children: [
Text(
'',
style: TextStyle(
color: Colors.white,
),
),
Text(
resultsList[i].equipe1.nom ?? "",
maxLines: 2,
overflow: TextOverflow.fade,
style: TextStyle(
color: Colors.black,
fontSize: 17,
fontWeight: FontWeight.bold,
),
),
Text(
"",
style: TextStyle(
color: Colors.white
.withOpacity(0.7),
),
)
],
),
),
Expanded(
//Colonne du score
child: Column(
//Gère le centrage du texte
mainAxisAlignment:
MainAxisAlignment.spaceEvenly,
crossAxisAlignment:
CrossAxisAlignment.center,
children: [
Text(
'',
style: TextStyle(
color: Colors.white,
),
),
Text(
'${resultsList[i].score1 ?? ""} - ${resultsList[i].score2 ?? ""}',
maxLines: 2,
textAlign: TextAlign.center,
overflow: TextOverflow.fade,
style: TextStyle(
color: Colors.grey,
fontSize: 17,
fontWeight: FontWeight.bold,
),
),
resultsList[i].tabs1 != null
? Text(
"(TAB : ${resultsList[i].tabs1 ?? ""} - ${resultsList[i].tabs2 ?? ""})",
style: TextStyle(
color: Colors.grey
.withOpacity(0.7),
fontSize: 12,
),
)
: Text(
"",
style: TextStyle(
color: Colors.grey
.withOpacity(0.7),
),
)
],
),
),
Expanded(
//Colonne de l'equipe 2
child: Column(
//Gère le centrage du texte
mainAxisAlignment:
MainAxisAlignment.spaceEvenly,
crossAxisAlignment:
CrossAxisAlignment.end,
children: [
Text(
'',
style: TextStyle(
color: Colors.white,
),
),
Text(
resultsList[i].equipe2.nom ?? "",
maxLines: 2,
textAlign: TextAlign.center,
overflow: TextOverflow.fade,
style: TextStyle(
color: Colors.black,
fontSize: 17,
fontWeight: FontWeight.bold,
),
),
Text(
"",
style: TextStyle(
color: Colors.white
.withOpacity(0.7),
),
)
],
),
),
Container(
height: 50,
width: 50,
decoration: BoxDecoration(
shape: BoxShape.circle,
),
child: ClipRRect(
borderRadius:
BorderRadius.circular(10),
child: Image.network(
'https://ruiznicolas.ovh/img/logos/${resultsList[i].equipe2.logo ?? ""}',
height: 95,
fit: BoxFit.fitHeight,
),
),
),
SizedBox(
//Marge du logo de gauche par rapport au reste
width: 5,
),
],
)
],
),
),
),
),
),
),
],
),
)
],
);
}
}
I think the nested ListView.builder
inside a ListView
might be the issue.
With the example below, where I tried to replicate your widget tree, I couldn't scroll the results list at all without physics: ClampingScrollPhysics()
on the inner ListView.builder
.
import 'package:flutter/material.dart';
class ListViewScrollastPage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('ListView Scrollast'),
),
body: ResultsWidget(),
);
}
}
class ResultsData {
List<int> results = List.generate(20, (index) => index + 1);
}
class ResultsWidget extends StatelessWidget {
final List<int> results = List.generate(20, (index) => index + 1);
@override
Widget build(BuildContext context) {
return Stack(
children: [
Container(
child: ListView(
children: [
Container(
child: ListView.builder(
itemCount: results.length,
shrinkWrap: true,
// ************************************************************
physics: ClampingScrollPhysics(), // ← can't scroll nested list without
// ************************************************************
itemBuilder: (context, index) {
return ListTile(
title: Text('Match ${results[index]}'),
subtitle: Text('Win Draw Loss'),
);
},
),
)
],
),
)
],
);
}
}
J'espère que ça t'aide ;)