I added the RefreshIndicator to my page, but there is no indicator visible when pull to refresh. The code is below:
class HomePage extends StatefulWidget {
HomePage({Key key, this.title}) : super(key: key);
final String title;
#override
_MyHomePageState createState() => new _MyHomePageState();
}
class _MyHomePageState extends State<HomePage> {
#override
Widget build(BuildContext context) {
return new Scaffold(
appBar: new AppBar(
title: new Text(widget.title),
),
body: LocalGalleryTab(),
);
}
}
class LocalGalleryTab extends StatefulWidget {
#override
State<StatefulWidget> createState() {
return _LocalGalleryState();
}
}
class _LocalGalleryState extends State<LocalGalleryTab> {
#override
Widget build(BuildContext context) {
return new Container(child: new Center(
child: new RefreshIndicator(
child: Text("Local Gallery"),
onRefresh: _refreshLocalGallery,
),
));
}
Future<Null> _refreshLocalGallery() async{
print('refreshing stocks...');
}
}
How to use the RefreshIndicator? The flutter doc does not give much infomation.
By design, RefreshIndicator works with ListView.
But if you want to use RefreshIndicator with non-scrollable-widgets, you can wrap your widget into Stack with ListView:
RefreshIndicator(
onRefresh: () {},
child: Stack(
children: <Widget>[ListView(), YOUR_CHILD_WIDGET],
),
),
Refresh Indicator by default does not work on a short list so add the following to the ListView.builder
physics: const AlwaysScrollableScrollPhysics(),
You need to add scroll child inside RefreshIndicator
see example below
class HomePage extends StatefulWidget {
HomePage({Key key, this.title}) : super(key: key);
final String title;
#override
_MyHomePageState createState() => new _MyHomePageState();
}
class _MyHomePageState extends State<HomePage> {
#override
Widget build(BuildContext context) {
return new Scaffold(
appBar: new AppBar(
title: new Text(widget.title),
),
body: LocalGalleryTab(),
);
}
}
class LocalGalleryTab extends StatefulWidget {
#override
State<StatefulWidget> createState() {
return _LocalGalleryState();
}
}
class _LocalGalleryState extends State<LocalGalleryTab> {
#override
Widget build(BuildContext context) {
return new Container(child: new Center(
child: new RefreshIndicator(
child: ListView(
children: List.generate(50, (f) => Text("Item $f")),
),
onRefresh: _refreshLocalGallery,
),
));
}
Future<Null> _refreshLocalGallery() async{
print('refreshing stocks...');
}
}
RefreshIndicator(
onRefresh: _onRefresh,
child:SingleChildScrollView(
physics: AlwaysScrollableScrollPhysics(),
child: ListView(
padding: EdgeInsets.all(8.0),
children: _listData.map((i) {
return ListTile(
title: Text("Item $i"),
);
}).toList(),
)
)
);
Add physics in SingleChildScrollView
You can use ListView with SinglchildScrollView widget also.
RefreshIndicator(
onRefresh: () {
//HERE YOUR FUNCTION TO CALL
},
child: Stack(
children: <Widget>[
ListView(
physics: const AlwaysScrollableScrollPhysics(),
children: []
)
],
),
),
In Non-scrollable list view, RefreshIndicator does not work, so you have to wrap your widget with Stack for implementing pull down to refresh.
RefreshIndicator(
onRefresh: () {
// Refresh Functionality
},
child: Stack(
children: [
ListView(
padding: EdgeInsets.zero,
shrinkWrap: true,
children: [
SizedBox(
height: MediaQuery.of(context).size.height,
)
],
),
// Your Widget
],
);
),
I think the best approach by far is to use CustomScrollView with RefreshIndicator. See example below.
class RefreshDemo extends StatelessWidget {
const RefreshDemo({super.key});
#override
Widget build(BuildContext context) {
return RefreshIndicator(
onRefresh: () async {
// Your refresh logic goes here
await Future.delayed(Duration(seconds: 2));
return true;
},
child: CustomScrollView(
slivers: [
// Wrap your widgets with the SliverToBoxAdapter
SliverToBoxAdapter(
child: Container(
child: Column(
children: [
Text('Hello'),
Text('World'),
]
)
),
),
],
),
);
}
}
I got the solution of this after little change from one answer which was already there in current page!
Just need to change position of stack children (main widget first then listview).
updated answer which will work 100%:
RefreshIndicator(
onRefresh: () {},
child: Stack(
children: <Widget>[YOUR_CHILD_WIDGET, ListView()],
),
),
past answer from above answers:
RefreshIndicator(
onRefresh: () {},
child: Stack(
children: <Widget>[ListView(), YOUR_CHILD_WIDGET],
),
),
The most important is to implement in ListView:
physics: const AlwaysScrollableScrollPhysics(),
because otherwise items which are not covering whole screen are stuck and you cannot move/refresh them.
as mentioned above refresh indicator only works with Listview so use it else wrap Listview with Stack
Related
I have a short ListView of a maximum of 10 items. Each list item will contain a DropDownButton which will hold around 1K DropDownMenuItems for selections.
In native Android, I was able to implement one that performed very smoothly, but with Flutter it takes a while to build the ListView which causes the UI to freeze.
In my case, I will need to rebuild the ListView upon every change in one of its items, so It will be a major issue.
Is there a way to make the ListView build faster, or at least be able to display a ProgressBar till it builds?
N.B: Using --profile configuration to simulate a release version improves the performance a lot, but still there is a sensed freeze.
Here's my sample code which you can directly copy/paste if you want to test it yourself.
import 'package:flutter/material.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
home: MyHomePage(),
);
}
}
class MyHomePage extends StatefulWidget {
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
bool showList = false;
final List<DropdownMenuItem<int>> selections = List.generate(
1000,
(index) => DropdownMenuItem<int>(
value: index,
child: Text("$index"),
),
);
#override
Widget build(BuildContext context) {
return SafeArea(
child: Scaffold(
body: Container(
width: double.infinity,
child: Column(
children: [
ElevatedButton(
child: Text("toggle list visibility"),
onPressed: () {
setState(() {
showList = !showList;
});
},
),
Expanded(
child: showList
? ListView.builder(
cacheExtent: 2000,
itemCount: 10,
itemBuilder: (context, index) {
return Padding(
padding: const EdgeInsets.all(8.0),
child: Center(
child: Container(
height: 200,
color: Colors.green,
child: Column(
children: [
Text("List Item: $index"),
DropdownButton<int>(
onChanged: (i) {},
value: 1,
items: selections,
),
],
),
),
),
);
})
: Text("List Not Built"),
),
],
),
),
),
);
}
}
Load dropdown when clicking the button.
Add this widget on your main List View
InkWell(
onTap: () {
showDialog(
context: context,
builder: (_) {
return VendorListAlert(selectVendor: selectVendorTap);
});
},
child: // create a widget, looks like your drop down
),
Handle tap event
void selectVendorTap(pass your model){
// logic
}
Sample for custom Alert
No need to create a mutable widget, the immutable widget is better.
class VendorListAlert extends StatefulWidget {
final Function selectVendor;
const VendorListAlert({Key key, this.selectVendor}) : super(key: key);
#override
_VendorListAlertState createState() => _VendorListAlertState();
}
class _VendorListAlertState extends State<VendorListAlert> {
List<UserModel> _searchVendor = [];
#override
void initState() {
super.initState();
_searchVendor = List.from(ypModel);
}
#override
Widget build(BuildContext context) {
return AlertDialog(
content: Container(
width: width,
child: ListView.builder(
shrinkWrap: true,
itemCount: _searchVendor.length,
itemBuilder: (BuildContext context, int index) {
return Card(
child: InkWell(
onTap: () {
widget.selectVendor(_searchVendor[index]);
Navigator.pop(context);
},
child:
),
);
},
),
),
);
}
}
I want to hide some widgets when the drawer opens. (it's mean when user open drawer then I need to hide some widgets)
Currently, I am using
if(!_scaffoldKey.currentState.isDrawerOpen)
//hide widget
But this is not listen. Is there any way to do listen drawer callbacks?
There is no callback mechanism till now in flutter which gives events for Drawer(), but still we can apply a good solution for it.
I divided the solution using two stateful widgets,
HomeScreen (Main Widget)
MyDrawer (Drawer Widget)
1. HomeScreen:
class HomeScreen extends StatefulWidget {
#override
_HomeScreenState createState() => _HomeScreenState();
}
class _HomeScreenState extends State<HomeScreen> {
final GlobalKey<ScaffoldState> _scaffoldKey = GlobalKey<ScaffoldState>();
bool _isDrawerOpen = false;
void drawerCallback(bool isOpen) {
print('Drawer Status:' + isOpen.toString());
// Based on the bool value set visibility of your widget
WidgetsBinding.instance.addPostFrameCallback((_){
setState(() {
_isDrawerOpen = isOpen;
});
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
key: _scaffoldKey,
appBar: AppBar(title: Text('Nav Sample App')),
body: _isDrawerOpen
? Align(alignment: Alignment.centerRight, child: Text('Drawer Open'))
: Align(alignment: Alignment.centerRight, child: Text('Drawr Close')),
drawer: MyDrawer(drawerCallback));
}
}
Above you can see that based on _isDrawerOpen we are setting widgets in the body with the ternary operator.
2. MyDrawer()
class MyDrawer extends StatefulWidget {
final Function _drawerCallback;
MyDrawer(this._drawerCallback);
#override
_MyDrawerState createState() => _MyDrawerState();
}
class _MyDrawerState extends State<MyDrawer> {
#override
void initState() {
super.initState();
widget._drawerCallback(true);
}
#override
void dispose() {
widget._drawerCallback(false);
super.dispose();
}
#override
Widget build(BuildContext context) {
return Drawer(
child: ListView(
children: <Widget>[
DrawerHeader(
child: Text('Drawer Header'),
decoration: BoxDecoration(color: Colors.blue),
),
Text("Drawer Item 1"),
Text("Drawer Item 2"),
],
),
);
}
}
Heart of the logic is applied in initState() and dispose() callbacks where we are returning status.
You can use the onTap() function for drawers using a ListView:
Drawer(
child: ListView(
// Important: Remove any padding from the ListView.
padding: EdgeInsets.zero,
children: <Widget>[
DrawerHeader(
child: Text('Drawers'),
decoration: BoxDecoration(
color: Colors.blue,
),
),
ListTile(
title: Text('Drawer 1'),
onTap: () {
// Insert code to hide or delete your desired widget
// Then close the drawer
Navigator.pop(context);
},
),
],
),
),//drawer
I am attempting to put a ListView.builder widgets in a Column widget however the common solution provided by the flutter team is to place the unbounded ListView.builder in a Expanded or Flexible widget. The problem with this is the ListView.builder now fills as much space as it can in the column (as seen bellow).
Is there a way to have the ListView.builder only take up the space it requires?
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatefulWidget {
// This widget is the root of your application.
#override
_MyAppState createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
List<Text> lst = [Text('first'), Text('second')];
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter ListView',
home: SafeArea(
child: Scaffold(
body: Column(
children: <Widget>[
Flexible(
child: ListView.builder(
itemCount: lst.length,
itemBuilder: (context, index) {
return Center(
child: lst[index],
);
},
),
),
FlatButton(
color: Colors.blue,
child: Text("Add Third"),
onPressed: () {
setState(() {
lst.add(Text("Third"));
});
},
),
Text("Next Item"),
],
),
),
),
);
}
}
You can use shrinkWrap: true property of listview to limit the list view size.
child: ListView.builder(
shrinkWrap: true, //added line
itemCount: lst.length,
I'm making an app divided to two sections: one(upper section) is PageView widget area, another(lower section) is Container widget area. I want the lower section to show 'we are in X page' when I change pages in the upper section.
I tried to use index of PageView widget in Container widget, but console said "undefined name 'index'".
So I declared like int index; as a global variable, and tried again, but it doesn't work. I think this index is different from index of PageView widget.
import 'package:flutter/material.dart';
class HomePage extends StatefulWidget {
#override
_HomePageState createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> {
static final GlobalKey<ScaffoldState> _scaffoldKey = GlobalKey<ScaffoldState>();
final controller = PageController(initialPage: 0);
var scrollDirection = Axis.horizontal;
var actionIcon = Icons.swap_vert;
int index;
#override
Widget build(BuildContext context) {
return Scaffold(
key: _scaffoldKey,
appBar: AppBar(
centerTitle: true,
title: Text('it\'s a drill for page view'),
),
body: _buildBody(),
);
}
Widget _buildBody() {
return SafeArea(
child: Column(
children: <Widget>[
Expanded(
child: PageView.builder(
controller: controller,
itemCount: 5,
itemBuilder: (context, index) {
return Text('it is ${index} page');
},
)
),
Expanded(
child: FittedBox(
fit: BoxFit.fitWidth,
child: Container(
color: Colors.blue,
child: Text('we are in ${index} page!'),
),
),
)
],
),
);
}
}
I'm a beginner of programming, and doing this as a hobby.
But I really like it. Actually I gave up my own study and career and stick to programming now. I hope you help me solve this problem.
Thank you. I love you.
yes. like controller.page for the current page.
class Sample extends StatelessWidget{
final int value;
Sample(this.value);
build(context) => Text("you are in $value");
}
and use Sample(controller.page)
EDIT: your code should be
class HomePage extends StatefulWidget {
#override
_HomePageState createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> {
static final GlobalKey<ScaffoldState> _scaffoldKey = GlobalKey<ScaffoldState>();
final controller = PageController(initialPage: 0);
var scrollDirection = Axis.horizontal;
var actionIcon = Icons.swap_vert;
int currentPage=0;
#override
Widget build(BuildContext context) {
return Scaffold(
key: _scaffoldKey,
appBar: AppBar(
centerTitle: true,
title: Text('it\'s a drill for page view'),
),
body: _buildBody(),
);
}
Widget _buildBody() {
return SafeArea(
child: Column(
children: <Widget>[
Expanded(
child: PageView.builder(
controller: controller,
itemCount: 5,
itemBuilder: (context, index) {
return Text('it is ${index} page');
},
onPageChanged: (page){
setState(() {
currentPage=page;
});
},
)
),
Expanded(
child: FittedBox(
fit: BoxFit.fitWidth,
child: Container(
color: Colors.blue,
child: Text('we are in ${currentPage} page!'),
),
),
)
],
),
);
}
}
Just add listener to PageController like that:
#override
void initState() {
super.initState();
index = 0;
controller.addListener(() {
setState(() {
index = controller.page.toInt();
});
});
}
I have this code and when i click on textfield it DOES appear on middle of screen above the keyboard, but it's very tightly fit there and the text below it does not appear. How can i make it so that when I click on the textfield the scrolling is enough to show the text below it as well?
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
return MaterialApp(
home: MyHomePage(),
);
}
}
class MyHomePage extends StatefulWidget {
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
#override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: SingleChildScrollView(
child: Column(
mainAxisAlignment: MainAxisAlignment.end,
children: <Widget>[
SizedBox(height: 600,),
TextField(
),
SizedBox(height: 30,),
Text("I want this text to appear ")
],
),
),
),
);
}
}
There is no exact solution for this, however you can use ScrollController to scroll the rest of the area in your SingleChildScrollView
ScrollController _scrollController = ScrollController();
#override
Widget build(BuildContext context) {
return Scaffold(
body: SingleChildScrollView(
controller: _scrollController,
child: Column(
children: <Widget>[
SizedBox(height: 600),
TextField(
onTap: () {
Timer(Duration(milliseconds: 200), () {
_scrollController.jumpTo(_scrollController.position.maxScrollExtent);
});
},
),
SizedBox(height: 30),
Text("I want this text to appear "),
],
),
),
);
}.