I want to use DraggableScrollableSheet widget on my application, when I use that like with below code, that can show without problem:
class HomePage extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('DraggableScrollableSheet'),
),
body: SizedBox.expand(
child: DraggableScrollableSheet(
builder: (BuildContext context, ScrollController scrollController) {
return Container(
color: Colors.blue[100],
child: ListView.builder(
controller: scrollController,
itemCount: 25,
itemBuilder: (BuildContext context, int index) {
return ListTile(title: Text('Item $index'));
},
),
);
},
),
),
);
}
}
but when i want to show that by pressing for example floatingActionButton that don't show
floatingActionButton: GestureDetector(
child: FloatingActionButton(
child: Icon(
Icons.add,
size: 35.0,
),
elevation: 5,
backgroundColor: Colors.deepOrange,
foregroundColor: Colors.white,
onPressed: (){
SizedBox.expand(child: DraggableScrollableSheet(
builder: (BuildContext context, ScrollController scrollController) {
return Container(
color: Colors.blue[100],
child: ListView.builder(
controller: scrollController,
itemCount: 25,
itemBuilder: (BuildContext context, int index) {
return ListTile(title: Text('Item $index'));
},
),
);
},
));
},
),
),
If you want to use DraggableScrollableSheet inside showModalBottomSheet, you can simply call this function.
void _showSheet() {
showModalBottomSheet(
context: context,
isScrollControlled: true, // set this to true
builder: (_) {
return DraggableScrollableSheet(
expand: false,
builder: (_, controller) {
return Container(
color: Colors.blue[500],
child: ListView.builder(
controller: controller, // set this too
itemBuilder: (_, i) => ListTile(title: Text('Item $i')),
),
);
},
);
},
);
}
If you want to do it with Animation, here is the solution.
class HomePage extends StatefulWidget {
#override
_HomePageState createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> with SingleTickerProviderStateMixin {
AnimationController _controller;
Duration _duration = Duration(milliseconds: 500);
Tween<Offset> _tween = Tween(begin: Offset(0, 1), end: Offset(0, 0));
#override
void initState() {
super.initState();
_controller = AnimationController(vsync: this, duration: _duration);
}
#override
Widget build(BuildContext context) {
return Scaffold(
floatingActionButton: GestureDetector(
child: FloatingActionButton(
child: AnimatedIcon(icon: AnimatedIcons.menu_close, progress: _controller),
elevation: 5,
backgroundColor: Colors.deepOrange,
foregroundColor: Colors.white,
onPressed: () async {
if (_controller.isDismissed)
_controller.forward();
else if (_controller.isCompleted)
_controller.reverse();
},
),
),
body: SizedBox.expand(
child: Stack(
children: <Widget>[
FlutterLogo(size: 500),
SizedBox.expand(
child: SlideTransition(
position: _tween.animate(_controller),
child: DraggableScrollableSheet(
builder: (BuildContext context, ScrollController scrollController) {
return Container(
color: Colors.blue[800],
child: ListView.builder(
controller: scrollController,
itemCount: 25,
itemBuilder: (BuildContext context, int index) {
return ListTile(title: Text('Item $index'));
},
),
);
},
),
),
),
],
),
),
);
}
}
If you want to show DraggableScrollableSheet as modal you can use material showModalBottomSheet method to wrap it. Your sheet will be shown as modal, and you do not have to include it in the widget tree. Please note that under the hood it's pushed as new Route to Navigator, with all its consequences.
onPressed: (){
showModalBottomSheet(
context: context,
isScrollControlled: true,
expand: false,
backgroundColor: Colors.transparent,
builder: (context) =>
DraggableScrollableSheet(
initialChildSize: 0.64,
minChildSize: 0.2,
maxChildSize: 1,
builder: (context, scrollController) {
return Container(
color: Colors.white,
child: ListView.builder(
controller: scrollController,
itemBuilder: (context, index) {
return ListTile(
title: Text('Item $index'),
);
},
itemCount: 20,
),
);
},
),
);
}
Related
I am trying to implement Shared Axis Transition between two tabs.
When the bottomNavigationBar is tapped, the selectedIndex goes from 0 to 1, and vice versa. During that change, the transition should animate. My code does not have any errors but no animations occurs.
Appreciate any advice.
Thanks.
body: PageTransitionSwitcher(
duration: const Duration(milliseconds: 500),
transitionBuilder: (Widget child, Animation<double> primaryAnimation,
Animation<double> secondaryAnimation) {
return SharedAxisTransition(
animation: primaryAnimation,
secondaryAnimation: secondaryAnimation,
transitionType: SharedAxisTransitionType.horizontal,
child: child);
},
child: selectedIndex == 0
? AnimationLimiter(
child: AnimatedList(
key: _key,
initialItemCount: db.dailyList.length,
itemBuilder: (context, index, animation) {
return AnimationConfiguration.staggeredList(
position: index,
duration: const Duration(milliseconds: 375),
child: SlideAnimation(
verticalOffset: 50.0,
child: FadeInAnimation(
child: SizeTransition(
sizeFactor: animation,
child: Tile(
title: db.dailyList[index][0],
goalAchieved: db.dailyList[index][1],
onChanged: (value) =>
{checkBoxChanged(value, index)},
deleteFunction: (context) => deleteGoal(index),
),
),
),
),
);
},
),
)
: AnimationLimiter(
child: ListView.builder(
itemCount: dbAchieved.dailyAchievedList.length,
itemBuilder: (context, index) {
return AchievedTile(
title: dbAchieved.dailyAchievedList[index][0],
deleteFunction: (context) => deleteAchievedGoal(index),
);
},
),
),
),
bottomNavigationBar: BottomNavigationBar(
backgroundColor: Theme.of(context).backgroundColor,
unselectedItemColor: Colors.black.withOpacity(0.7),
selectedItemColor: Colors.black,
currentIndex: selectedIndex,
onTap: (index) => setState(() {
selectedIndex = index;
}),
items: const [
BottomNavigationBarItem(
icon: Icon(Icons.fact_check_outlined), label: 'Carpe Diem'),
BottomNavigationBarItem(icon: Icon(Icons.done), label: 'Achieved')
],
),
turns out, i just need to declare a key for each of the AnimationLimiter for it to work. I just added the following:
static const dailyKey = Key('dailyKey');
static const dailyAcheivedKey = Key('dailyAchievedKey');
and then adding it in the AnimationLimiter
key: dailyKey
key: dailyAchievedKey
I try to manage a state "playerList" in two screens:
The code below shows how the provider was built
class PlayerList with ChangeNotifier {
List<Player> _playerList = [];
List<Player> get playerList {
return [..._playerList];
}
void addPlayer(Player player) {
_playerList.add(player);
notifyListeners();
}
void deletePlayer(int index) {
_playerList.removeAt(index);
notifyListeners();
}
}
In screen 1, everything works perfect. However, when I do the same, but then within an AlertDialog, the updated value only shows after closing the dialog and opening it again.
The code below shows the relevant part of the build method.
#override
Widget build(BuildContext context) {
PlayerList _playerListData = Provider.of<PlayerList>(context);
List<Player> _playerList = _playerListData.playerList;
return WillPopScope(
onWillPop: () async => showDialog(
context: context,
builder: (context) => AlertDialog(
title: Text(
'Are you sure you want to leave? The current game will be lost.'),
actions: <Widget>[
ElevatedButton(
child: Text('Yes'),
onPressed: () => Navigator.of(context).pop(true)),
ElevatedButton(
child: Text('No'),
onPressed: () => Navigator.of(context).pop(false)),
])),
child: Scaffold(
backgroundColor: Color(0xFF6ca0dc),
body: Center(
child: Column(children: [
_playChallenge(_playerList),
]),
),
floatingActionButton: FloatingActionButton(
onPressed: () {
showDialog(
context: context,
builder: (BuildContext context) {
return AlertDialog(
title: Text('Player List'),
content: Column(mainAxisSize: MainAxisSize.min, children: [
Container(
height: MediaQuery.of(context).size.height *
0.5, // Change as per your requirement
width: MediaQuery.of(context).size.width *
0.5, // Change as per your requirement
child: ListView.builder(
shrinkWrap: true,
itemCount: _playerList.length,
itemBuilder: (BuildContext context, int index) {
return ListTile(
title: Text(
_playerList[index].name,
style: Theme.of(context).textTheme.headline6,
),
trailing: IconButton(
icon: Icon(Icons.delete),
color: Theme.of(context).errorColor,
onPressed: () =>
_playerListData.deletePlayer(index),
));
},
),
),
How can I solve this issue?
I solved the problem by adding a StatefulBuilder and using the Provider.of inside the statefulbuilder. See:
#override
Widget build(BuildContext context) {
return WillPopScope(
onWillPop: () async => showDialog(
context: context,
builder: (context) => AlertDialog(
title: Text(
'Are you sure you want to leave? The current game will be lost.'),
actions: <Widget>[
ElevatedButton(
child: Text('Yes'),
onPressed: () => Navigator.of(context).pop(true)),
ElevatedButton(
child: Text('No'),
onPressed: () => Navigator.of(context).pop(false)),
])),
child: Scaffold(
backgroundColor: Color(0xFF6ca0dc),
body: Center(
child: Column(children: [
_playChallenge(Provider.of<PlayerList>(context).playerList),
]),
),
floatingActionButton: FloatingActionButton(
onPressed: () {
showDialog(
context: context,
builder: (BuildContext context) {
return StatefulBuilder(builder: (context, setState) {
PlayerList _playerListData =
Provider.of<PlayerList>(context);
List<Player> _playerList = _playerListData.playerList;
return AlertDialog(
title: Text('Player List'),
content:
Column(mainAxisSize: MainAxisSize.min, children: [
Container(
height: MediaQuery.of(context).size.height *
0.5, // Change as per your requirement
width: MediaQuery.of(context).size.width *
0.5, // Change as per your requirement
child: ListView.builder(
shrinkWrap: true,
itemCount: _playerList.length,
itemBuilder: (BuildContext context, int index) {
return ListTile(
title: Text(
_playerList[index].name,
style:
Theme.of(context).textTheme.headline6,
),
trailing: IconButton(
icon: Icon(Icons.delete),
color: Theme.of(context).errorColor,
onPressed: () =>
_playerListData.deletePlayer(index),
));
},
),
),
import 'package:flutter_event_app/constant/color.dart';
import 'package:flutter_event_app/network/models/categories.dart';
import 'package:flutter_event_app/network/models/event_model.dart';
import 'package:flutter_event_app/network/models/time.dart';
import 'package:flutter_event_app/network/services/event_api.dart';
import 'package:flutter_event_app/pages/event_detail_page.dart';
import 'package:flutter_event_app/pages/search/home_search.dart';
import 'package:flutter_event_app/widgets/event_card.dart';
import 'package:flutter_event_app/widgets/no_events.dart';
import 'package:flutter_event_app/widgets/onload.dart';
class SelectedCategory extends StatefulWidget {
// SelectedCategory(Categories categories);
final Categories categories;
final Time time;
SelectedCategory(this.categories, [this.time]);
#override
_SelectedCategoryState createState() => _SelectedCategoryState();
}
class _SelectedCategoryState extends State<SelectedCategory> {
Categories categories;
Time timing;
String timeselect;
// Event event;
void viewEventDetail(Events event) {
Navigator.of(context).push(
PageRouteBuilder(
opaque: false,
barrierDismissible: true,
transitionDuration: Duration(milliseconds: 300),
pageBuilder: (BuildContext context, animation, __) {
return FadeTransition(
opacity: animation,
child: EventDetailPage(event),
);
},
),
);
}
bool isLoading = false;
List<Events> upcomingEvents;
List categorizedupcomingEvents = [];
List categorizedPaidupcomingEvents = [];
List categorizedFreeupcomingEvents = [];
#override
void initState() {
_fetchData();
categories = widget.categories;
timing = widget.time;
// print(timing.id);
super.initState();
}
Future _fetchData() async {
setState(() => isLoading = true);
upcomingEvents = await getEventss();
categorizedupcomingEvents = upcomingEvents
.where((category) => category.category == categories.id)
.toList();
categorizedPaidupcomingEvents = categorizedupcomingEvents
.where((paid) => paid.is_paid == true)
.toList();
categorizedFreeupcomingEvents = categorizedupcomingEvents
.where((free) => free.is_paid == false)
.toList();
setState(() => isLoading = false);
}
#override
Widget build(BuildContext context) {
return DefaultTabController(
length: 3,
child: Scaffold(
appBar: PreferredSize(
preferredSize: Size.fromHeight(
MediaQuery.of(context).size.height / 9.5,
),
child: AppBar(
title: Text(categories.categoryName),
centerTitle: true,
actions: <Widget>[
IconButton(
icon: Icon(
Icons.sort,
),
onPressed: () {
// Navigator.push(
// context,
// MaterialPageRoute(
// builder: (context) =>
showFilterByTimeDialog(context);
// )
// );
}),
IconButton(
icon: Icon(
Icons.more_vert,
),
onPressed: () {})
],
bottom: TabBar(
tabs: [
Text('All'),
Text('Paid'),
Text('Free'),
],
),
),
),
body: TabBarView(
children: <Widget>[
// All
isLoading
? OnloadingCards()
: Column(
children: <Widget>[
Expanded(
child: Padding(
padding: const EdgeInsets.all(8.0),
child: categorizedupcomingEvents.isEmpty
? NoItems()
: ListView.builder(
itemCount: categorizedupcomingEvents.length,
shrinkWrap: true,
primary: false,
physics: BouncingScrollPhysics(),
// scrollDirection: Axis.horizontal,
itemBuilder: (context, index) {
final event =
categorizedupcomingEvents[index];
return EventCard(event,
onTap: () => viewEventDetail(event));
},
),
),
),
],
),
// Paid
isLoading
? OnloadingCards()
: Column(
children: <Widget>[
Expanded(
child: Padding(
padding: const EdgeInsets.all(8.0),
child: categorizedPaidupcomingEvents.isEmpty
? NoItems()
: ListView.builder(
itemCount:
categorizedPaidupcomingEvents.length,
shrinkWrap: true,
primary: false,
physics: BouncingScrollPhysics(),
// scrollDirection: Axis.horizontal,
itemBuilder: (context, index) {
final event =
categorizedPaidupcomingEvents[index];
return EventCard(event,
onTap: () => viewEventDetail(event));
},
),
),
),
],
),
// Free
isLoading
? OnloadingCards()
: Column(
children: <Widget>[
Expanded(
child: Padding(
padding: const EdgeInsets.all(8.0),
child: categorizedFreeupcomingEvents.isEmpty
? NoItems()
: ListView.builder(
itemCount:
categorizedFreeupcomingEvents.length,
shrinkWrap: true,
primary: false,
physics: BouncingScrollPhysics(),
// scrollDirection: Axis.horizontal,
itemBuilder: (context, index) {
final event =
categorizedFreeupcomingEvents[index];
return EventCard(event,
onTap: () => viewEventDetail(event));
},
),
),
),
],
),
],
),
));
}
void showFilterByTimeDialog(BuildContext context) {
Dialog fancyDialog = Dialog(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12.0),
),
child: SingleChildScrollView(
child: Container(
width: double.infinity,
height: MediaQuery.of(context).size.height * 0.5,
// alignment: Alignment.bottomCenter,
decoration: BoxDecoration(
// color: Colors.greenAccent,
borderRadius: BorderRadius.only(
topLeft: Radius.circular(12),
topRight: Radius.circular(12),
bottomLeft: Radius.circular(12),
bottomRight: Radius.circular(12),
),
),
child: Column(
children: <Widget>[
Container(
height: MediaQuery.of(context).size.height * 0.05,
child: Text(
"Time",
style: TextStyle(
color: Colors.deepPurple,
fontSize: 20,
fontWeight: FontWeight.w600),
),
),
Expanded(
child: Padding(
padding: const EdgeInsets.all(8.0),
child: Container(
// color: Colors.red,
width: double.infinity,
child: ListView.builder(
shrinkWrap: true,
primary: false,
physics: BouncingScrollPhysics(),
itemCount: times.length,
itemBuilder: (context, int index) {
Time time = times[index];
return RaisedButton(
onPressed: () {
debugPrint('I am Awesome');
},
textColor: Colors.red,
// color: Colors.blueAccent,
disabledColor: Colors.grey,
disabledTextColor: Colors.white,
highlightColor: Colors.orangeAccent,
child: Text(time.name),
);
}),
),
),
),
],
),
),
),
);
showDialog(
context: context, builder: (BuildContext context) => fancyDialog);
}
}
Within the same page I have a dialog box as shown below
On the method showFilterByTimeDialog where I select an item and have to go back to the same page below the dialogue .Am still learning flutter and my issue is I need help when I select an item from the dialogue box,i refresh the same page and display a new filtered lst from the current list displayed on that page with a condition of the item selected from the dialogue box.
i have a problem with navigator in my flutter file.
The problem is in my GestureDetector in _listItem, after tap on object, in my debug console throw me:
The following assertion was thrown while handling a gesture:
Navigator operation requested with a context that does not include a Navigator.
The context used to push or pop routes from the Navigator must be that of a widget that is a descendant of a Navigator widget.
I don't have idea why this not work for me, can abybody help me?
below is my code:
class _HomePageState extends State<HomePage> {
#override
Widget build(BuildContext context) {
return MaterialApp(
home:Scaffold(
appBar: PreferredSize(
preferredSize: Size.fromHeight(100.0),
child: AppBar(
iconTheme: IconThemeData(color: Color.fromRGBO(9, 133, 46, 100)),
backgroundColor: Colors.white,
actions: <Widget>[
IconButton(
icon: Icon(
Icons.shopping_cart,
color: Color.fromRGBO(9, 133, 46, 100),
),
onPressed: (){
print('klikniete');
},
),
],
),
),
body: Builder(
builder: (context) => Container(
child: FutureBuilder(
future: fetchOrders(),
builder: (BuildContext context, AsyncSnapshot snapshot) {
if (_ordersForDisplay.length == null) {
return Container(
child: Center(child: Text("Ćadowanie...")),
);
} else {
return ListView.builder(
itemCount: _ordersForDisplay.length + 1,
itemBuilder: (BuildContext context, int index) {
return index == 0 ? _searchBar() : _listItem(index - 1);
},
);
}
},
),
),
),
)
);
}
_listItem(index) {
return GestureDetector(
onTap: () => Navigator.of(context).push(
MaterialPageRoute(builder: (context) => DetailPage(item: _ordersForDisplay[index])),
),
child: Card(
child: Padding(
padding: const EdgeInsets.only(
top: 32.0, bottom: 32.0, left: 16.0, right: 16.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Text(
_ordersForDisplay[index].firstName,
style: TextStyle(fontSize: 22, fontWeight: FontWeight.bold),
),
Text(
_ordersForDisplay[index].lastName,
style: TextStyle(
color: Colors.grey.shade600
),
),
],
),
),
),
);
}
}
class DetailPage extends StatelessWidget {
final Order item;
const DetailPage({this.item});
#override
Widget build(BuildContext context) {
return Center(
child: Text('${item.firstName} - ${item.lastName}')
);
}
}
Try changing your methods args as.
_listItem(index, context) { //Changes here
....
}
And pass the context where you called it.
return index == 0 ? _searchBar() : _listItem(index - 1,context);
when i use DraggableScrollableSheet i have some problem of that, they are:
how can i set minimum height after dragging that to bottom
how can i set minimum height
how can i set maximum height
how can i know stick to top like with stick to appBar, detecting maximum size
SizedBox.expand(
child: SlideTransition(
position: _draggableBottomSheetTween.animate(_draggableBottomSheetController),
child: DraggableScrollableSheet(
builder: (BuildContext context, ScrollController scrollController) {
return ClipRRect(
borderRadius: BorderRadius.only(
topLeft: Radius.circular(10.0),
topRight: Radius.circular(10.0)
),
child: Container(
height: 500.0,
color: pageBackgroundColor,
child: ListView.builder(
controller: scrollController,
itemCount: 5,
itemBuilder: (BuildContext context, int index) {
return ListTile(title: Text('Item $index'));
},
),
),
);
},
),
),
),
Here is the full solution.
class HomePage extends StatefulWidget {
#override
_HomePageState createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> with SingleTickerProviderStateMixin {
AnimationController _controller;
Duration _duration = Duration(milliseconds: 500);
Tween<Offset> _tween = Tween(begin: Offset(0, 1), end: Offset(0, 0));
double _height, min = 0.1, initial = 0.3, max = 0.7;
#override
void initState() {
super.initState();
_controller = AnimationController(vsync: this, duration: _duration);
}
#override
Widget build(BuildContext context) {
return Scaffold(
floatingActionButton: GestureDetector(
child: FloatingActionButton(
child: AnimatedIcon(icon: AnimatedIcons.menu_close, progress: _controller),
elevation: 5,
backgroundColor: Colors.deepOrange,
foregroundColor: Colors.white,
onPressed: () async {
if (_controller.isDismissed)
_controller.forward();
else if (_controller.isCompleted) _controller.reverse();
},
),
),
body: SizedBox.expand(
child: Stack(
children: <Widget>[
FlutterLogo(size: 500),
SizedBox.expand(
child: SlideTransition(
position: _tween.animate(_controller),
child: DraggableScrollableSheet(
minChildSize: min, // 0.1 times of available height, sheet can't go below this on dragging
maxChildSize: max, // 0.7 times of available height, sheet can't go above this on dragging
initialChildSize: initial, // 0.1 times of available height, sheet start at this size when opened for first time
builder: (BuildContext context, ScrollController controller) {
if (controller.hasClients) {
var dimension = controller.position.viewportDimension;
_height ??= dimension / initial;
if (dimension >= _height * max * 0.9)
print("at top");
else if (dimension <= _height * min * 1.1)
print("at bottom");
}
return ClipRRect(
borderRadius: BorderRadius.only(topLeft: Radius.circular(20.0), topRight: Radius.circular(20.0)),
child: Container(
height: 500.0,
color: Colors.blue[800],
child: ListView.builder(
controller: controller,
itemCount: 5,
itemBuilder: (BuildContext context, int index) {
return ListTile(title: Text('Item $index'));
},
),
),
);
},
),
),
),
],
),
),
);
}
}