I am building a FutureBuilder with a Listview within a SliverFillRemaining. And I think since Sliver is already within a CustomScrollView the scroll function doesn't work properly. Once it scrolls down it doesn't scroll back up.
Below is the code.
#override
Widget build(BuildContext context) {
return Scaffold(
body: CustomScrollView(
slivers: [
SliverAppBar(
expandedHeight: 200.0,
floating: false,
//pinned: false,
flexibleSpace: FlexibleSpaceBar(
background: Image.network("https://i.imgur.com/p3CfZBS.png",
fit: BoxFit.cover),
),
),
SliverFillRemaining(
child: Container(
child: FutureBuilder(
future: _getData(),
builder: (context, snapshot) {
if (snapshot.data == null) {
return Center(child: CircularProgressIndicator());
} else {
return ListView.builder(
itemCount: snapshot.data.length,
itemBuilder: (context, index) {
return ListView(
shrinkWrap: true,
children: [
buildlink(
imageName: snapshot.data[index].image,
page: snapshot.data[index].title)
],
);
},
);
}
},
),
),
),
],
),
);
}
}
most likely the 2nd listView is superfluous and probably you want to use physics: NeverScrollableScrollPhysics() with list view, if you use CustomScrollView. Check NestedScrollView may be it would work better in this situation.
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: Scaffold(
body: SafeArea(
child: MyHomePage(),
),
),
);
}
}
class MyHomePage extends StatefulWidget {
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
#override
Widget build(BuildContext context) {
return Scaffold(
body: CustomScrollView(
slivers: [
SliverAppBar(
expandedHeight: 200.0,
floating: false,
pinned: false,
flexibleSpace: FlexibleSpaceBar(
background: Image.network("https://i.imgur.com/p3CfZBS.png",
fit: BoxFit.cover),
),
),
SliverFillRemaining(
child: Container(
child: FutureBuilder(
future: _getData(),
builder: (context, snapshot) {
if (snapshot.data == null) {
return Center(child: CircularProgressIndicator());
} else {
return ListView.builder(
physics: NeverScrollableScrollPhysics(),
itemCount: snapshot.data.length,
itemBuilder: (context, index) {
return buildlink(
imageName: snapshot.data[index].image,
page: snapshot.data[index].title,
);
},
);
}
},
),
),
),
],
),
);
}
Future<List<LinkData>> _getData() async {
await Future.delayed(Duration(seconds: 1));
return [
LinkData(image: "https://i.imgur.com/p3CfZBS.png", title: 'First'),
LinkData(image: "https://i.imgur.com/p3CfZBS.png", title: 'Second'),
LinkData(image: "https://i.imgur.com/p3CfZBS.png", title: 'Third'),
];
}
Widget buildlink({String imageName, String page}) {
return Card(
child: Container(
child: Text(page),
height: 400,
),
);
}
}
class LinkData {
const LinkData({
this.image,
this.title,
});
final String image;
final String title;
}
Related
The ListView.builder of my widget is really laggy and slow when scrolling, especially in debug mode.
class SongsScreen extends StatelessWidget {
const SongsScreen({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
final songsProvider = Provider.of<Songs>(context, listen: false);
final songs = songsProvider.getSongs();
return Scaffold(
body: Center(
child: ListView.builder(
primary: true,
itemCount: songs.length,
itemBuilder: (context, index) {
return FutureBuilder<List<dynamic>>(
future: Future.wait([
songsProvider.getTags(songs[index]),
songsProvider.getArtwork(songs[index]),
]),
builder: (ctx, snapshot) {
if (snapshot.hasData) {
return snapshot.data![1] != null
? ListTile(
title: Text(snapshot.data![0].title ?? 'Unknown'),
subtitle: Text(snapshot.data![0].artist ?? 'Unknown'),
leading: ClipRRect(
borderRadius: BorderRadius.circular(4.0),
child: Image.memory(
snapshot.data![1],
fit: BoxFit.fill,
)),
)
: ListTile(
title: Text(songs[index]),
subtitle: const Text('Unknown'),
leading: const CircleAvatar(
child: Icon(Iconsax.music5),
),
);
} else {
return ListTile(
title: Text(songs[index]),
subtitle: const Text('Unknown'),
leading: const CircleAvatar(
child: Icon(Iconsax.music5),
),
);
}
},
);
},
),
),
);
}
}
Is there a possible way to improve the performance? My guess is that the FutureBuilders are slowing down the performance but I could be wrong.
EDIT: I've rearranged the code and now I see a small improvement. But it's still not so smooth.
You should call the future function to wait for the result, and as soon as it produces the result it calls the builder function where you build the widget.
import 'dart:io';
import 'dart:typed_data';
import 'package:audiotagger/models/tag.dart';
import 'package:flutter/material.dart';
import 'package:iconsax/iconsax.dart';
import 'package:provider/provider.dart';
import 'package:taggr/providers/songs.dart';
class SongsScreen extends StatelessWidget {
const SongsScreen({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
final songs = Provider.of<Songs>(context);
return Scaffold(
body: Center(
child: FutureBuilder(
future: songs.getTags(songs.getSongs()),
builder: (ctx, AsyncSnapshot<Tag?> snapshot) {
if (snapshot.hasData) {
return Center(
child: ListView.builder(
itemCount: snapshot.data.length,
itemBuilder: (context, index) {
return ListTile(
title: Text(snapshot.data[index]!.title!),
subtitle: Text(snapshot.data[index]!.artist!),
leading: FutureBuilder(
future: songs.getArtwork(songs.getSongs()[index]
.path),
builder: (ctx, AsyncSnapshot<
Uint8List?> artworkSnapshot) {
if (artworkSnapshot.hasData) {
return ClipRRect(
borderRadius: BorderRadius.circular(4.0),
child: Image.memory(artworkSnapshot.data!),
);
} else {
return const CircleAvatar(
child: Icon(Iconsax.music5),
);
}
},
),
);
}
),
);
} else {
return const Text('n');
}
},
),
)
);
}
}
Parent Code
how to insert my listviewBuilder in listViewBuilder item
this is my parent code
Expanded(
child: Container(
padding: EdgeInsets.only(top: 10.0),
decoration: Ui.getBoxDecoration(),
child: ListView.separated(
itemBuilder: (context, index) {
var _comment =
controller.commentWithDetailDtoList.elementAt(index);
return TimelineCommentItemWidget(comment: _comment);
},
separatorBuilder: (context, index) => SizedBox(height: 10.0),
itemCount: controller.commentWithDetailDtoList.length,
),
),
)
Child Code
and this is my child code
Visibility(
child: ListView.separated(
itemBuilder: (context, index) {
var reply = comment.replies.elementAt(index);
return TimelineCommentRepliesItemWidget(comment: reply);
},
separatorBuilder: (context, index) => SizedBox(height: 10.0),
itemCount: comment.replies.length,
),
),
Here is the code.
Please check this out.
import 'package:flutter/material.dart';
const Color darkBlue = Color.fromARGB(255, 18, 32, 47);
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
theme: ThemeData.dark().copyWith(
scaffoldBackgroundColor: darkBlue,
),
debugShowCheckedModeBanner: false,
home: Scaffold(
body: Center(
child: MyWidget(),
),
),
);
}
}
class MyWidget extends StatelessWidget {
#override
Widget build(BuildContext context) {
return ListView.separated(itemBuilder : (BuildContext context , int i) {
return _innerWidget();
} , itemCount : 2 , shrinkWrap: true,separatorBuilder: (context, index) => SizedBox(height: 10.0), );
}
}
Widget _innerWidget(){
return ListView.builder(itemBuilder : (BuildContext context , int i) {
return Text('hello');
}, itemCount: 2,shrinkWrap: true, physics: NeverScrollableScrollPhysics(),);
}
Hoping someone can help with this and it's not a bug and it's just me being silly.
There is very strange behavior from listview when it's not taking the full length of the screen and in a column.
When you scroll down, the animation at max extent persists and overlaps. I'm assuming this is a bug and not by design.
Here's the simple code to reproduce.
import 'package:flutter/material.dart';
void main() {
runApp(MyApp(
items: List<MessageItem>.generate(
33,
(i) => MessageItem("Sender $i", "Message body $i"),
),
));
}
class MyApp extends StatelessWidget {
final List<MessageItem> items;
MyApp({Key key, #required this.items}) : super(key: key);
#override
Widget build(BuildContext context) {
final title = 'Mixed List';
return MaterialApp(
title: title,
home: Scaffold(
appBar: AppBar(
title: Text(title),
),
body: Column(
children: [
Expanded(
child: Container(),
),
Expanded(
child: ListView.builder(
itemCount: items.length,
itemBuilder: (context, index) {
final item = items[index];
return ListTile(
title: item.buildTitle(context),
subtitle: item.buildSubtitle(context),
);
},
),
),
],
),
),
);
}
}
/// A ListItem that contains data to display a message.
class MessageItem {
final String sender;
final String body;
MessageItem(this.sender, this.body);
Widget buildTitle(BuildContext context) => Text(sender);
Widget buildSubtitle(BuildContext context) => Text(body);
}
So final code will be. I have added the scroll phisycs BouncingScrollPhysics.
import 'package:flutter/material.dart';
void main() {
runApp(MyApp(
items: List<MessageItem>.generate(
33,
(i) => MessageItem("Sender $i", "Message body $i"),
),
));
}
class MyApp extends StatelessWidget {
final List<MessageItem> items;
MyApp({Key key, #required this.items}) : super(key: key);
#override
Widget build(BuildContext context) {
final title = 'Mixed List';
return MaterialApp(
title: title,
home: Scaffold(
appBar: AppBar(
title: Text(title),
),
body: Column(
children: [
Expanded(
child: Container(
),
),
Expanded(
child: ListView.builder(
physics: BouncingScrollPhysics(
parent: AlwaysScrollableScrollPhysics()),
itemCount: 50,
itemBuilder: (context, index) {
return ListTile(
title: Text("${index + 1}"),
subtitle: Text("${index + 1}"),
);
},
),
),
],
),
),
);
}
}
/// A ListItem that contains data to display a message.
class MessageItem {
final String sender;
final String body;
MessageItem(this.sender, this.body);
Widget buildTitle(BuildContext context) => Text(sender);
Widget buildSubtitle(BuildContext context) => Text(body);
}
I'm not sure if this is a bug or not. Or if my solution is the correct way of doing it, or not. But this work
#override
Widget build(BuildContext context) {
return NestedScrollView(
headerSliverBuilder: (BuildContext context, bool innerBoxIsScrolled) {
return <Widget>[
SliverOverlapAbsorber(
handle: NestedScrollView.sliverOverlapAbsorberHandleFor(context),
sliver: SliverAppBar(
backgroundColor: Colors.white,
toolbarHeight: 200,
pinned: true,
forceElevated: innerBoxIsScrolled,
),
),
];
},
body: Container(
child: SafeArea(
top: false,
bottom: false,
child: Builder(
builder: (BuildContext context) {
return CustomScrollView(
key: PageStorageKey<String>("name"),
slivers: <Widget>[
SliverOverlapInjector(
handle:
NestedScrollView.sliverOverlapAbsorberHandleFor(context),
),
SliverPadding(
padding: const EdgeInsets.all(8.0),
sliver: SliverFixedExtentList(
itemExtent: 48.0,
delegate: SliverChildBuilderDelegate(
(BuildContext context, int index) {
return ListTile(
title: Text('Item $index'),
);
},
childCount: 30,
),
),
),
],
);
},
),
)),
);
}
The reason I don't like this is that I'm putting non-bar content in an AppBar.
If anyone has a better solution please let me know.
Tried to fix this reading some documentation and some open issues but was not lucky.. Could someone please help?
I am getting this error:
type 'bool' is not a subtype of type 'double' in type cast
Not sure why though I tried adding container wrapping the component, adding height, adding flexible box etc...
No lucky
`import 'package:flutter/material.dart';
class SampleData {
SampleData(this.title, [this.children = const <SizedBox>[]]);
final String title;
final List<SizedBox> children;
}
final List<SampleData> data = <SampleData>[
SampleData("IT", [
SizedBox(
height: 300,
width: 300,
child: CustomScrollView(
scrollDirection: Axis.horizontal,
slivers: <Widget>[
new SliverToBoxAdapter(
child: Text('fesfefes'),
),
],
),
),
]),
];
class Branch extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Test 123'),
),
body: Container(
width: 500,
child: ListView.builder(
scrollDirection: Axis.horizontal,
itemBuilder: (BuildContext context, int index) => Item(data[index]),
itemCount: data.length,
),
),
);
}
}
// Displays one Entry. If the entry has children then it's displayed
// with an ExpansionTile.
class Item extends StatelessWidget {
const Item(this.sample);
final SampleData sample;
Widget _buildTiles(SampleData root) {
return SizedBox(
width: 500,
child: ExpansionTile(
key: PageStorageKey<SampleData>(root),
title: Text(root.title),
children: root.children,
),
);
}
#override
Widget build(BuildContext context) {
return _buildTiles(sample);
}
}
`
You can copy paste run full code blow
You can remove
//key: PageStorageKey<SampleData>(root),
working demo
full code
import 'package:flutter/material.dart';
class SampleData {
SampleData(this.title, [this.children = const <SizedBox>[]]);
final String title;
final List<SizedBox> children;
}
final List<SampleData> data = <SampleData>[
SampleData("IT", [
SizedBox(
height: 300,
width: 300,
child: CustomScrollView(
scrollDirection: Axis.horizontal,
slivers: <Widget>[
new SliverToBoxAdapter(
child: Text('fesfefes'),
),
],
),
),
]),
];
class Branch extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Test 123'),
),
body: Container(
width: 500,
child: ListView.builder(
scrollDirection: Axis.horizontal,
itemBuilder: (BuildContext context, int index) => Item(data[index]),
itemCount: data.length,
),
),
);
}
}
// Displays one Entry. If the entry has children then it's displayed
// with an ExpansionTile.
class Item extends StatelessWidget {
const Item(this.sample);
final SampleData sample;
Widget _buildTiles(SampleData root) {
return SizedBox(
width: 500,
child: ExpansionTile(
//key: PageStorageKey<SampleData>(root),
title: Text(root.title),
children: root.children,
),
);
}
#override
Widget build(BuildContext context) {
return _buildTiles(sample);
}
}
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
visualDensity: VisualDensity.adaptivePlatformDensity,
),
home: Branch(),
);
}
}
This is probably a really easy-to-fix question, but I figured that I might as well ask it here anyways: Is there any way to anchor a widget within a CustomScrollView? I want to use the CustomScrollView to support a flexible space in the app bar, but I need to have an input widget stay fixed at the bottom of the screen. I tried nesting the CustomScrollView into a Column with the given widget, but it doesn't seem to be working:
#override
Widget build(BuildContext context) {
return Column(
children: <Widget>[
CustomScrollView(
slivers: <Widget>[
_buildAppBar(), // Returns a SliverAppBar
_buildMessages(), // Returns a StreamBuilder that returns a SliverList
],
),
MessageInputWidget(), // Input that needs to stay fixed
],
);
}
And here's that _buildMessages() method:
Widget _buildMessages(BuildContext context) {
return StreamBuilder<List<Message>>(
stream: widget.classroom.messages(),
builder: (context, snapshot) {
print('[DEBUG] Building chat with updated message stream...');
if (!snapshot.hasData || snapshot.data == null) {
return Center(
child: CircularProgressIndicator(),
);
}
_messages = snapshot.data;
print('[DEBUG] Building ${_messages.length} messages...');
return SliverList(
delegate: SliverChildBuilderDelegate(
(BuildContext context, int index) {
if (index == _messages.length) {
return _buildHeader(context);
}
return MessageWidget(
message: _messages[index],
isReceived: _user.id != _messages[index].sentBy.id,
showUser: (index ==
0) || // Show _avatar if it's the first msg
(index >=
1 && // Or if it's a different _user than the last
!(_messages[index].sentBy.id ==
_messages[index - 1].sentBy.id)),
);
},
childCount: _messages.length,
),
);
});
}
Any suggestions? I've found some examples but that builds the whole CustomScrollView while I only want to build the SliverList whenever I get a new snapshot.
Any suggestions?
Yes, but for that you don't put it in the custom scroll, you use a stack widget. It puts the layered widget on the screen. What comes below you put before. In order for your widget to be positioned at the bottom, you must use a column with an expanded.
Stack(
children: <Widget>[
yourStreamBuilder(),
Column(
children: <Widget>[
Expanded(child: Container()),
Container(
width: MediaQuery.of(context).size.width,
height: 44,
color: Colors.red,
child: Text("Your bottom container"),
)
],
),
],
)
Complete exemple:
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
Map<String, dynamic> tocando = {};
String ultimoTocando;
Future<List<Map<String, dynamic>>> listaDeMusicas() async {
return List<Map<String, dynamic>>.generate(
1200,
(i) => {"audio": "musica $i", "idUnico": "$i"},
);
}
tocar(String musica) {
print("tocando $musica ");
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Stack(
children: <Widget>[
yourStreamBuilder(),
Column(
children: <Widget>[
Expanded(child: Container()),
Container(
width: MediaQuery.of(context).size.width,
height: 44,
color: Colors.red,
child: Text("Your bottom container"),
)
],
),
],
));
}
Widget yourStreamBuilder() {
return FutureBuilder<List<Map<String, dynamic>>>(
future: listaDeMusicas(),
initialData: [],
builder: (context, snapshot) {
return ListView.builder(
itemCount: snapshot.data.length,
itemBuilder: (context, index) {
var item = snapshot.data[index];
if (tocando[item["idUnico"]] == null)
tocando[item["idUnico"]] = false;
return Row(
children: <Widget>[
IconButton(
icon: Icon(
tocando[item["idUnico"]] ? Icons.stop : Icons.play_arrow),
onPressed: () {
setState(() {
if (ultimoTocando != null) {
tocando[ultimoTocando] = false;
}
if (ultimoTocando != item["idUnico"]) {
tocando[item["idUnico"]] = !tocando[item["idUnico"]];
}
if (tocando[item["idUnico"]]) {
tocar(item["audio"]);
}
});
ultimoTocando = item["idUnico"];
},
),
Text(item["audio"]),
],
);
},
);
},
);
}
}