Swipe List Item for more options (Flutter) - android

Somedays ago I decided to choose an Ui for an app from Pinterest to practice building apps with Flutter but I'm stuck with the Slider which shows an "more" and "delete" button on horizontal drag. Picture on the right.
I don't have enough knowledge to use Gestures combined with Animations to create something like this in flutter. Thats why I hope that someone of you can make an example for everyone like me that we can understand how to implement something like this in a ListView.builder.
(Source)
An gif example from the macOS mail App:

I created a package for doing this kind of layout: flutter_slidable (Thanks Rémi Rousselet for the based idea)
With this package it's easier to create contextual actions for a list item. For example if you want to create the kind of animation you described:
You will use this code:
new Slidable(
delegate: new SlidableDrawerDelegate(),
actionExtentRatio: 0.25,
child: new Container(
color: Colors.white,
child: new ListTile(
leading: new CircleAvatar(
backgroundColor: Colors.indigoAccent,
child: new Text('$3'),
foregroundColor: Colors.white,
),
title: new Text('Tile n°$3'),
subtitle: new Text('SlidableDrawerDelegate'),
),
),
actions: <Widget>[
new IconSlideAction(
caption: 'Archive',
color: Colors.blue,
icon: Icons.archive,
onTap: () => _showSnackBar('Archive'),
),
new IconSlideAction(
caption: 'Share',
color: Colors.indigo,
icon: Icons.share,
onTap: () => _showSnackBar('Share'),
),
],
secondaryActions: <Widget>[
new IconSlideAction(
caption: 'More',
color: Colors.black45,
icon: Icons.more_horiz,
onTap: () => _showSnackBar('More'),
),
new IconSlideAction(
caption: 'Delete',
color: Colors.red,
icon: Icons.delete,
onTap: () => _showSnackBar('Delete'),
),
],
);

There's already a widget for this kind of gesture. It's called Dismissible.
You can find it here. https://docs.flutter.io/flutter/widgets/Dismissible-class.html
EDIT
If you need the exact same transtion, you'd probably have to implement if yourself.
I made a basic example. You'd probably want to tweak the animation a bit, but it's working at least.
class Test extends StatefulWidget {
#override
_TestState createState() => new _TestState();
}
class _TestState extends State<Test> {
double rating = 3.5;
#override
Widget build(BuildContext context) {
return new Scaffold(
body: new ListView(
children: ListTile
.divideTiles(
context: context,
tiles: new List.generate(42, (index) {
return new SlideMenu(
child: new ListTile(
title: new Container(child: new Text("Drag me")),
),
menuItems: <Widget>[
new Container(
child: new IconButton(
icon: new Icon(Icons.delete),
),
),
new Container(
child: new IconButton(
icon: new Icon(Icons.info),
),
),
],
);
}),
)
.toList(),
),
);
}
}
class SlideMenu extends StatefulWidget {
final Widget child;
final List<Widget> menuItems;
SlideMenu({this.child, this.menuItems});
#override
_SlideMenuState createState() => new _SlideMenuState();
}
class _SlideMenuState extends State<SlideMenu> with SingleTickerProviderStateMixin {
AnimationController _controller;
#override
initState() {
super.initState();
_controller = new AnimationController(vsync: this, duration: const Duration(milliseconds: 200));
}
#override
dispose() {
_controller.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
final animation = new Tween(
begin: const Offset(0.0, 0.0),
end: const Offset(-0.2, 0.0)
).animate(new CurveTween(curve: Curves.decelerate).animate(_controller));
return new GestureDetector(
onHorizontalDragUpdate: (data) {
// we can access context.size here
setState(() {
_controller.value -= data.primaryDelta / context.size.width;
});
},
onHorizontalDragEnd: (data) {
if (data.primaryVelocity > 2500)
_controller.animateTo(.0); //close menu on fast swipe in the right direction
else if (_controller.value >= .5 || data.primaryVelocity < -2500) // fully open if dragged a lot to left or on fast swipe to left
_controller.animateTo(1.0);
else // close if none of above
_controller.animateTo(.0);
},
child: new Stack(
children: <Widget>[
new SlideTransition(position: animation, child: widget.child),
new Positioned.fill(
child: new LayoutBuilder(
builder: (context, constraint) {
return new AnimatedBuilder(
animation: _controller,
builder: (context, child) {
return new Stack(
children: <Widget>[
new Positioned(
right: .0,
top: .0,
bottom: .0,
width: constraint.maxWidth * animation.value.dx * -1,
child: new Container(
color: Colors.black26,
child: new Row(
children: widget.menuItems.map((child) {
return new Expanded(
child: child,
);
}).toList(),
),
),
),
],
);
},
);
},
),
)
],
),
);
}
}
EDIT
Flutter no longer allows type Animation<FractionalOffset> in SlideTransition animation property. According to this post https://groups.google.com/forum/#!topic/flutter-dev/fmr-C9xK5t4 it should be replaced with AlignmentTween but this also doesn't work. Instead, according to this issue: https://github.com/flutter/flutter/issues/13812 replacing it instead with a raw Tween and directly creating Offset object works instead. Unfortunately, the code is much less clear.

Updated Code with Null Safety: Flutter: 2.x
Firstly you need to add the flutter_slidable package in your project and add below code then Let's enjoy...
Slidable(
actionPane: SlidableDrawerActionPane(),
actionExtentRatio: 0.25,
child: Container(
color: Colors.white,
child: ListTile(
leading: CircleAvatar(
backgroundColor: Colors.indigoAccent,
child: Text('$3'),
foregroundColor: Colors.white,
),
title: Text('Tile n°$3'),
subtitle: Text('SlidableDrawerDelegate'),
),
),
actions: <Widget>[
IconSlideAction(
caption: 'Archive',
color: Colors.blue,
icon: Icons.archive,
onTap: () => _showSnackBar('Archive'),
),
IconSlideAction(
caption: 'Share',
color: Colors.indigo,
icon: Icons.share,
onTap: () => _showSnackBar('Share'),
),
],
secondaryActions: <Widget>[
IconSlideAction(
caption: 'More',
color: Colors.black45,
icon: Icons.more_horiz,
onTap: () => _showSnackBar('More'),
),
IconSlideAction(
caption: 'Delete',
color: Colors.red,
icon: Icons.delete,
onTap: () => _showSnackBar('Delete'),
),
],
);

I have a task that needs the same swipeable menu actions I tried answeres of Romain Rastel and Rémi Rousselet. but I have complex widget tree. the issue with that slideable solutions is they go on other widgets(to left widgets of listview). I found a batter solution here someone wrote a nice article medium and GitHub sample is here.

I look at a lot of articles and answers, and find #Rémi Rousselet answer the best fitted to use without third party libraries.
Just put some improvements to #Rémi's code to make it usable in modern SDK without errors and null safety.
Also I smooth a little bit movement, to make the speed of buttons appeared the same as finger movement.
And I put some comments into the code:
import 'package:flutter/material.dart';
class SlidebleList extends StatefulWidget {
const SlidebleList({Key? key}) : super(key: key);
#override
State<SlidebleList> createState() => _SlidebleListState();
}
class _SlidebleListState extends State<SlidebleList> {
#override
Widget build(BuildContext context) {
return Scaffold(
body: ListView(
children: ListTile.divideTiles(
context: context,
tiles: List.generate(42, (index) {
return SlideMenu(
menuItems: <Widget>[
Container(
color: Colors.black12,
child: IconButton(
icon: const Icon(Icons.more_horiz),
onPressed: () {},
),
),
Container(
color: Colors.red,
child: IconButton(
color: Colors.white,
icon: const Icon(Icons.delete),
onPressed: () {},
),
),
],
child: const ListTile(
title: Text("Just drag me"),
),
);
}),
).toList(),
),
);
}
}
class SlideMenu extends StatefulWidget {
final Widget child;
final List<Widget> menuItems;
const SlideMenu({Key? key,
required this.child, required this.menuItems
}) : super(key: key);
#override
State<SlideMenu> createState() => _SlideMenuState();
}
class _SlideMenuState extends State<SlideMenu> with SingleTickerProviderStateMixin {
late AnimationController _controller;
#override
initState() {
super.initState();
_controller = AnimationController(
vsync: this, duration: const Duration(milliseconds: 200));
}
#override
dispose() {
_controller.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
//Here the end field will determine the size of buttons which will appear after sliding
//If you need to appear them at the beginning, you need to change to "+" Offset coordinates (0.2, 0.0)
final animation =
Tween(begin: const Offset(0.0, 0.0),
end: const Offset(-0.2, 0.0))
.animate(CurveTween(curve: Curves.decelerate).animate(_controller));
return GestureDetector(
onHorizontalDragUpdate: (data) {
// we can access context.size here
setState(() {
//Here we set value of Animation controller depending on our finger move in horizontal axis
//If you want to slide to the right, change "-" to "+"
_controller.value -= (data.primaryDelta! / (context.size!.width*0.2));
});
},
onHorizontalDragEnd: (data) {
//To change slide direction, change to data.primaryVelocity! < -1500
if (data.primaryVelocity! > 1500)
_controller.animateTo(.0); //close menu on fast swipe in the right direction
//To change slide direction, change to data.primaryVelocity! > 1500
else if (_controller.value >= .5 || data.primaryVelocity! < -1500)
_controller.animateTo(1.0); // fully open if dragged a lot to left or on fast swipe to left
else // close if none of above
_controller.animateTo(.0);
},
child: LayoutBuilder(builder: (context, constraint) {
return Stack(
children: [
SlideTransition(
position: animation,
child: widget.child,
),
AnimatedBuilder(
animation: _controller,
builder: (context, child) {
//To change slide direction to right, replace the right parameter with left:
return Positioned(
right: .0,
top: .0,
bottom: .0,
width: constraint.maxWidth * animation.value.dx * -1,
child: Row(
children: widget.menuItems.map((child) {
return Expanded(
child: child,
);
}).toList(),
),
);
})
],
);
})
);
}
}

i had the same problem and and as the accepted answer suggests, i used flutter_slidable
but i needed a custom look for the actions and also i wanted them to be vertically aligned not horizontal.
i noticed that actionPane() can take a list of widgets as children not only
SlidableAction.
so i was able to make my custom actions,and wanted to share the code and results with you here.
this is the layout
this is the code i used :
ListView.builder(
itemBuilder: (context, index) {
return Slidable(
startActionPane: ActionPane(
motion: const ScrollMotion(),
extentRatio: 0.25,
// A pane can dismiss the Slidable.
// All actions are defined in the children parameter.
children: [
Expanded(
flex: 1,
child: Card(
margin: const EdgeInsets.symmetric(
horizontal: 8, vertical: 16),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(10),
),
child: Column(
children: [
Expanded(
child: InkWell(
child: Container(
width: double.infinity,
child: Column(
mainAxisAlignment:
MainAxisAlignment.center,
children: [
Icon(Icons.edit,
color:
Colors.deepPurple),
Text(
LocalizationKeys.edit.tr,
style: TextStyle(
color:
Colors.deepPurple,
fontSize: 16),
),
],
),
),
onTap: () {},
),
),
Container(
height: 1,
color: Colors.deepPurple,
),
Expanded(
child: InkWell(
child: Container(
width: double.infinity,
child: Column(
mainAxisAlignment:
MainAxisAlignment.center,
children: [
Icon(Icons.delete,
color: Colors.red),
Text(
LocalizationKeys
.app_delete.tr,
style: TextStyle(
color: Colors.red,
fontSize: 16),
),
],
),
),
onTap: () {},
),
),
],
),
),
),
]),
child: Card(
margin: EdgeInsets.all(16),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(16),
),
elevation: 0,
child: Column(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
SizedBox(height: 16),
Text(_lecturesViewModel
.lectures.value[index].centerName),
SizedBox(height: 16),
Row(
mainAxisSize: MainAxisSize.min,
children: [
Text(_lecturesViewModel
.lectures.value[index].classLevel),
Text(_lecturesViewModel
.lectures.value[index].material),
],
),
SizedBox(height: 16),
Row(
mainAxisSize: MainAxisSize.min,
children: [
Icon(Icons.location_pin),
Text(_lecturesViewModel
.lectures.value[index].city),
Text(_lecturesViewModel
.lectures.value[index].area),
],
),
SizedBox(height: 16),
Row(
mainAxisAlignment:
MainAxisAlignment.spaceEvenly,
children: [
Column(
children: [
Icon(Icons.calendar_today),
Text(_lecturesViewModel
.lectures.value[index].day),
],
),
Container(
height: 1,
width: 60,
color: Colors.black,
),
Column(
children: [
Icon(Icons.punch_clock),
Text(_lecturesViewModel
.lectures.value[index].time),
],
),
Container(
height: 1,
width: 60,
color: Colors.black,
),
Column(
children: [
Icon(Icons.money),
Text(
"${_lecturesViewModel.lectures.value[index].price.toString()}ج "),
],
)
]),
SizedBox(height: 16),
]),
),
);
},
itemCount: _lecturesViewModel.lectures.length,
physics: BouncingScrollPhysics(),
)

Related

How to have a ListView without scrolling the parent column view in flutter?

Please look at this image Home Page
Now when I scroll the ListView it becomes like this -
Home Page
Now I know the reason why this is happening, it is because I used ListView as a parent to this entire view and added ListView.builder() and other widgets as its child.
What I want is to scroll the ListView.builder() without scrolling the entire page.
For this I first tried to use Column as parent but that ended up giving the overflow pixels error.
And then I set the physics: const NeverScrollableScrollPhysics() inside the parent ListView but after that it made my ListView.builder() to show all of its list items.
Here is my Code for Home Screen
Widget build(BuildContext context) {
return Scaffold(
body: Column(
children: [
Expanded(
child: ListView(
padding: const EdgeInsets.only(top: 45, bottom: 24),
children: [
header(),
const SizedBox(height: 36),
const BalanceCard(),
const SizedBox(height: 36),
Recent()
],
),
),
Align(
alignment: Alignment.bottomCenter,
child: bottomNavigationBar(),
),
],
),
); }
Recent List Code
class RecentItems extends StatefulWidget {
final List<Transaction> transactions;
RecentItems({required this.transactions});
#override
State<RecentItems> createState() => _RecentItemsState();
}
class _RecentItemsState extends State<RecentItems> {
#override
Widget build(BuildContext context) {
return SizedBox(
height: 450,
child: Expanded(child: ListView.builder(
itemBuilder: (context, index) {
final item = widget.transactions[index].toString();
return Dismissible(
direction: DismissDirection.endToStart,
key: UniqueKey(),
onDismissed: (direction) {
setState(() {
widget.transactions.removeAt(index);
});
// Then show a snackbar.
ScaffoldMessenger.of(context)
.showSnackBar(const SnackBar(content: Text('Transaction Deleted')));
},
background: Container(
color: Colors.red,
alignment: AlignmentDirectional.centerEnd,
child: const Padding(
padding: EdgeInsets.fromLTRB(0.0, 0.0, 15.0, 0.0),
child: Icon(
EvaIcons.trash2,
color: Colors.white,
),
),
),
child: Card(
elevation: 5,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(20.0),
),
child: ListTile(
leading: CircleAvatar(
radius: 30,
foregroundImage: widget.transactions[index].Image,
backgroundColor: primaryColor,
),
title: Text(
widget.transactions[index].title,
style: const TextStyle(color: secondaryColor),
),
subtitle: Text(
DateFormat.yMMMd().format(widget.transactions[index].date),
),
trailing: Text(
'\$${widget.transactions[index].amount}',
style: const TextStyle(color: secondaryColor),
),
),
),
);
},
itemCount: widget.transactions.length,
),)
);
}
}
Recent Widget -
#override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.symmetric(horizontal: 24),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text(
'Recent Transactions',
style: TextStyle(
fontSize: 15,
fontWeight: FontWeight.bold,
color: secondaryColor),
),
const SizedBox(height: 5),
RecentItems(transactions: _userTransactions),
],
),
)
The entire screen scrolls because Recent() is included in the same ListView as header() and balanceCard().
Try something like this:
Scaffold(
body: Column(
children: [
Expanded(
child: ListView(
padding: const EdgeInsets.only(top: 45, bottom: 24),
children: [
header(),
const SizedBox(height: 36),
const BalanceCard(),
const SizedBox(height: 36),
],
),
),
// Recent items removed from ListView
Recent(),
Align(
alignment: Alignment.bottomCenter,
child: bottomNavigationBar(),
),
],
),
)
I hope this helps.
By simplifying your code, this is an example of a layout where you have a single Column in the Scaffold. The Column contains some sized, unsized and aligned children.
One child, Recent is a ListView, without explicit height, but wrapped into an Expanded widget. This way it will occupy all the remaining area left by the other children, and it will be scrollable.
(You will run into trouble with this if the children without Recent occupy all the available area.)
Please have a look at this code, you can copy-paste it into a DartPad fiddle:
import 'package:flutter/material.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
body: Center(
child: MyWidget(),
),
),
);
}
}
class MyWidget extends StatelessWidget {
#override
Widget build(BuildContext context) => Scaffold(
body: Column(
children: [
const Text('header()'),
const SizedBox(height: 36),
const Text('BalanceCard()'),
const SizedBox(height: 36),
Expanded(child: Recent()),
const Align(
alignment: Alignment.bottomCenter,
child: Text('bottomNavigationBar()'),
),
],
),
);
}
class Recent extends StatelessWidget {
#override
Widget build(BuildContext context) => ListView.builder(
itemCount: 100, itemBuilder: (context, index) => Text('Item $index'));
}

Flutter DragTarget onAccept is not being called

I am developing a feature where the user enters a sentence, in the next screen the words of that sentence get shuffled randomly, then the user has to drag the words to a drag target to form the original sentence.
You can get an idea from the screenshots below.
First screen
Second screen
Now the problem I am having is, when dragging the words to the target I can see the DragTarget is calling onWillAccept as I added a print() statement there, if it is doing so then it should call onAccept eventually but it is not doing so. This is why my codes that deal with Bloc are not getting called and the words are not showing up in the target spot.
Code
class SentenceMakeScreen extends StatefulWidget {
String inputSentence;
SentenceMakeScreen(this.inputSentence);
#override
State<SentenceMakeScreen> createState() => _SentenceMakeScreenState();
}
class _SentenceMakeScreenState extends State<SentenceMakeScreen> {
List<String> sentence = [];
List<Widget> wordWidgets = [];
bool isDragSuccessful = false;
final ButtonStyle _buttonStyle = ElevatedButton.styleFrom(
textStyle: TextStyle(fontSize: 20)
);
_getTextWidgets(List<String> sentence) {
for(var i = 0; i < sentence.length; i++){
wordWidgets.add(
Draggable<WordWidget>(
data: WordWidget(sentence[i]),
child: WordWidget(sentence[i]),
feedback: WordWidget(sentence[i]),
childWhenDragging: Container(),
)
);
}
}
_randomlyOrganizeSentence(String inputString) {
sentence = inputString.split(new RegExp(r" "));
sentence.shuffle();
print(sentence);
}
#override
void initState() {
// TODO: implement initState
_randomlyOrganizeSentence(widget.inputSentence);
_getTextWidgets(sentence);
super.initState();
}
#override
Widget build(BuildContext context) {
final _dragDropBloc = DragDropBloc();
return Scaffold(
appBar: AppBar(
leading: IconButton(
icon: Icon(Icons.arrow_back),
onPressed: () {
Navigator.pop(context);
},
),
),
body: Container(
alignment: Alignment.center,
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [
DragTarget<WordWidget>(
builder: (context, data, rejectedData) {
return Center(
child: this.isDragSuccessful
?
Container(
width: double.maxFinite,
margin: EdgeInsets.all(10),
decoration: BoxDecoration(
color: Colors.white,
border: Border(
bottom: BorderSide(width: 1.0, color: Colors.black),
),
),
child: StreamBuilder<List<WordWidget>>(
stream: _dragDropBloc.widgetStream,
initialData: [],
builder: (BuildContext context, AsyncSnapshot<List<WordWidget>> snapshot) {
print("Here ${snapshot.data}");
return Wrap(
direction: Axis.horizontal,
children: [
//correctly ordered words
],
);
},
),
)
:
Container(
width: double.maxFinite,
margin: EdgeInsets.all(10),
decoration: BoxDecoration(
color: Colors.white,
border: Border(
bottom: BorderSide(width: 1.0, color: Colors.black),
),
),
child: Text("Drag here")
),
);
},
onWillAccept: (data) {
print("true");
return true;
},
onAccept: (data) {
print(data.toString());
_dragDropBloc.dragDropEventSink.add(
DropEvent(WordWidget(data.toString()))
);
setState(() {
this.isDragSuccessful = true;
//draggedWords.add(data.toString());
});
},
),
Wrap(
direction: Axis.horizontal,
children: wordWidgets
),
Container(
child: ElevatedButton(
style: _buttonStyle,
onPressed: () {
},
child: Text("Check"),
),
),
],
),
),
);
}
}
WordWidget
import 'package:flutter/material.dart';
class WordWidget extends StatelessWidget {
final String word;
const WordWidget(this.word);
#override
Widget build(BuildContext context) {
return Container(
margin: EdgeInsets.all(10),
decoration: BoxDecoration(
color: Colors.red[900],
border: Border.all(
width: 4,
color: Colors.black
),
borderRadius: BorderRadius.circular(10),
),
child: Padding(
padding: EdgeInsets.all(5),
child: Text(
word,
style: TextStyle(
color: Colors.white
),
)
),
);
}
}
I tried adding the type of data I am passing from Draggable to DragTarget, this is what was advised here. It did not work.
I was also getting the same error earlier today. I then upgraded my flutter to the latest version and wrote the DragTarget code again from scratch. I don't know what worked for me but you can try doing the same.

How to pass onTap Function to ListTile in Flutter?

My Drawer Content:
drawer: Drawer(
elevation: 0.0,
child: Container(
color: kBackgroundColor,
child: ListView(
padding: EdgeInsets.zero,
children: <Widget>[
DrawerHeader(
decoration: BoxDecoration(
color: Colors.grey,
image: DecorationImage(
alignment: Alignment.topRight,
image: AssetImage('images/banner.png'),
fit: BoxFit.cover,
),
),
child: Text('App Test'),
),
ReusableOptionTile(
textData: 'Home',
icon: Icons.home,
onTouch: () {
print('Home Clicked');
Navigator.pop(context);
},
),
]
),
),
),
Extracted List Tile Content:
class ReusableOptionTile extends StatelessWidget {
const ReusableOptionTile(
{required this.icon, required this.textData, required this.onTouch});
final IconData icon;
final String textData;
final Function onTouch;
#override
Widget build(BuildContext context) {
return ListTile(
title: Row(
children: [
Icon(icon),
SizedBox(
width: 5.0,
),
Text('$textData'),
],
),
onTap: onTouch, // ERROR POINT
);
}
}
I Extracted the List Tile from Drawer for making the code more reusable but now unable to pass the on tap function for different tile.
at error point it is show error "The argument type 'Function' can't be assigned to the parameter type 'void Function()?'."
How to fix this issue?
onTouch should be extracted as a void function
void onTouch(){
print('Home Clicked');
Navigator.pop(context);
}
then
ReusableOptionTile(
textData: 'Home',
icon: Icons.home,
onTouch: onTouch,
),
and called
ListTile(
title: Row(
children: [
Icon(icon),
SizedBox(
width: 5.0,
),
Text('$textData'),
],
),
onTap: onTouch(),
);

How to slide pages under appbar flutter

I've been trying so hard to build page view system for quotes app. I want the page to flow full screen from top to bottom and bottom to top scrollable/swipe able to navigate between different quotes each time
like this. The scroll will bring new page each time its not casual scroll. I haven't found any guide regarding this on internet so far.
I don't know about how to build it, nothing is popping in my mind for days now. I've tried pageview with gesture detector for swiping up and down, it doesn't works as desired and appbar is static too and the bottom containers as well I don't want this. What I want is the page/screen to flow under the appbar or even a button on top right corner and under 2 buttons on the bottom.
Create a column of Containers where each Container's height and width equals the height and width of the screen. You can do that by using:
height: MediaQuery.of(context).size.height,
width: MediaQuery.of(context).size.width,
Also, make sure to wrap your code with a SingleChildScrollView widget to scroll vertically.
Here's the code:
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: SingleChildScrollView(
child: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Container(
height: MediaQuery.of(context).size.height,
width: MediaQuery.of(context).size.width,
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Center(child: Text('Quote number one')),
Center(
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
IconButton(icon: Icon(Icons.share), onPressed: () {}),
IconButton(
icon: Icon(Icons.favorite), onPressed: () {})
],
),
)
],
),
),
Container(
height: MediaQuery.of(context).size.height,
width: MediaQuery.of(context).size.width,
child: Column(
children: [
Text('Quote number two'),
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
IconButton(icon: Icon(Icons.share), onPressed: () {}),
IconButton(icon: Icon(Icons.favorite), onPressed: () {})
],
)
],
),
)
],
),
),
),
);
}
}
You can use PageView to get this type of view
Drive Video link
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
PageController pageController;
PageView pageView;
List<String> quotes = [
'#Quote 1\n\n“I\'m selfish, impatient and a little insecure. I make mistakes, I am out of control and at times hard to handle. But if you can\'t handle me at my worst, then you sure as hell don\'t deserve me at my best.” ',
'#Qoute 2\n\nSo many books, so little time.',
'#Quote 3\n\n A room without books is like a body without a soul'
];
#override
void initState() {
super.initState();
pageController = PageController();
final _quotes = List.generate(quotes.length, (index) => quoteWidget(index));
pageView = PageView(
children: _quotes,
controller: pageController,
scrollDirection: Axis.vertical,
);
}
Widget quoteWidget(int index) {
return Scaffold(
backgroundColor: Colors.black,
body: Column(
children: [
Expanded(
flex: 3,
child: Padding(
padding: const EdgeInsets.all(20.0),
child: Center(
child: Text(
quotes[index],
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 20,
fontWeight: FontWeight.w300,
color: Colors.white,
),
),
),
),
),
Expanded(
flex: 1,
child: Row(
mainAxisSize: MainAxisSize.max,
mainAxisAlignment: MainAxisAlignment.center,
children: [
FloatingActionButton(
onPressed: () {},
child: Icon(Icons.share),
),
const SizedBox(width: 20),
FloatingActionButton(
onPressed: () {},
child: Icon(Icons.favorite_border),
),
],
),
),
Expanded(child: Container())
],
),
);
}
#override
void dispose() {
pageController.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.black,
appBar: AppBar(
brightness: Brightness.dark,
title: Text(widget.title),
),
body: SafeArea(
child: Stack(
alignment: Alignment.bottomCenter,
children: [
pageView,
Padding(
padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 10),
child: Row(
children: [
RaisedButton.icon(
icon: Icon(Icons.menu_book),
label: Text('General'),
onPressed: () {},
),
const Spacer(),
Container(
width: 50,
height: 50,
margin: const EdgeInsets.symmetric(horizontal: 10),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(5),
),
child: Icon(Icons.color_lens),
),
Container(
width: 50,
height: 50,
margin: const EdgeInsets.symmetric(horizontal: 10),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(5),
),
child: Icon(Icons.person),
),
],
),
)
],
),
),
);
}
}

Navigate using bottom app bar tabs - flutter

I am using bottom app bar for bottomnavigation in flutter. when tapped on one of the tab in the bottom app bar, i would still want the bottom app bar and app bar to remain as it is in it's fixed position but only the body content changes based on what is tapped.
I have tried to use push() method but it gives me a new page instead with a back button.
Navigation_tabs.dart:
import 'package:flutter/material.dart';
class NavigationTabs extends StatelessWidget {
#override
Widget build(BuildContext context) {
return new Scaffold(
floatingActionButtonLocation: FloatingActionButtonLocation.centerDocked,
floatingActionButton: FloatingActionButton(
child: const Icon(Icons.add),
onPressed: () {},
),
appBar: AppBar(
title: Text('Dashboard'),
),
bottomNavigationBar: BottomAppBar(
shape: CircularNotchedRectangle(),
notchMargin: 4.0,
child: new Row(
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[
IconButton(
icon: Icon(
Icons.home,
color: Colors.cyan[700],
),
onPressed: () {},
),
new Container(
padding: EdgeInsets.only(left: 20),
child: IconButton(
icon: Icon(
Icons.list,
color: Colors.cyan[700],
),
onPressed: () => Navigator.pushNamed(context, '/login'),
)),
new Container(
padding: EdgeInsets.only(left: 120),
child: IconButton(
icon: Icon(
Icons.explore,
color: Colors.cyan[700],
),
onPressed: () {},
)),
new Container(
height: 22.0,
child: new RawMaterialButton(
onPressed: () {},
child: new Icon(
Icons.person,
color: Colors.white,
size: 20.0,
),
shape: new CircleBorder(),
elevation: 1.0,
fillColor: Colors.cyan[700],
))
],
),
));
}
}
I want to be able to only make the page content change without a back button instead of going to a completely new page when one of the tabs is pressed.
You can use a BottomNavigationBarItem instead of creating buttons and use the ontap of the bottomNavigationBar.
class _MyHomePageState extends State<MyHomePage> {
int _index = 0;
final List<Widget> _children = [
Center(child: Text("First Page"),),
Center(child: Text("Second Page"),),
Center(child: Text("Third Page"),)
];
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("Bottom Navigation"),
),
body: Center(
child: (_children[_index ]),
),
bottomNavigationBar: BottomNavigationBar(
onTap: onTabTapped,
currentIndex: _currentIndex,
items: [
BottomNavigationBarItem(
icon: Icon(Icons.looks_one),
title: Text('First'),
),
BottomNavigationBarItem(
icon: Icon(Icons.looks_two),
title: Text('Second'),
),
BottomNavigationBarItem(
icon: Icon(Icons.looks_3),
title: Text('Third'),
)
],
),
);
}
void onTabTapped(int index) {
setState(() {
_index = index;
});
}
}
For more detailed explanation:
Flutter documentation

Categories

Resources