I have got a DefaultTabController with a couple of tabs that the user can swipe or press an ElevatedButton to proceed to the next slide, my issue is that I don't know how to change the button's label when the user reaches the last tab using swipes.
Using a stateful widget I managed to change the label when the user presses the button but it doesn't work if the user swipes. Is it possible to change the button when the user reaches the last tab?
class SlidesWidget extends StatelessWidget {
static List<Slide> slides = [
const Slide(
text: 'Welcome to ..'),
const Slide(
text: 'Ready to discover your city?')
];
#override
Widget build(BuildContext context) {
return DefaultTabController(
length: slides.length,
child: Builder( // Builder here, otherwise `DefaultTabController.of(context)` returns null.
builder: (BuildContext context) => Padding(
padding: const EdgeInsets.all(8.0),
child: SafeArea(
child: Column(
children: [
const TabPageSelector(
selectedColor: Colors.white,
),
Expanded(
flex: 100,
child: TabBarView(
children: slides,
),
),
Padding(
padding: const EdgeInsets.all(18.0),
child: ElevatedButton(
onPressed: () {
final TabController controller =
DefaultTabController.of(context)!;
if (!controller.indexIsChanging &&
controller.index < slides.length - 1) {
// Go to next slide if exists
controller.index++;
}
},
child: Text('Next'), // <== on last slide should change label and do other things
),
)
],
),
),
),
),
);
}
}
I will recommend using StatefulWidget, also you can use inline StatefulBuilder to update the UI. And using TabController is handy instead of calling it multiple times, and there is risk of getting null for DefaultTabController.
class SlidesWidget extends StatefulWidget {
SlidesWidget({Key? key}) : super(key: key);
#override
State<SlidesWidget> createState() => _SlidesWidgetState();
}
class _SlidesWidgetState extends State<SlidesWidget>
with SingleTickerProviderStateMixin {
late TabController controller;
List<Slide> slides = [
const Slide(text: 'Welcome to ..'),
const Slide(text: 'Ready to discover your city?')
];
#override
void initState() {
super.initState();
controller = TabController(length: slides.length, vsync: this)
..addListener(() {
setState(() {});
});
}
#override
void dispose() {
controller.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: Padding(
padding: const EdgeInsets.all(8.0),
child: SafeArea(
child: Column(
children: [
TabPageSelector(
controller: controller,
selectedColor: Colors.white,
),
Expanded(
flex: 100,
child: TabBarView(
controller: controller,
children: slides,
),
),
Padding(
padding: const EdgeInsets.all(18.0),
child: ElevatedButton(
onPressed: () {
if (!controller.indexIsChanging &&
controller.index < slides.length - 1) {
// Go to next slide if exists
controller.index++;
}
},
child: Text(controller.index == 1 ? 'start' : "Next"), //
),
)
],
),
),
));
}
}
More about TabController and I think you will also like IndexedStack for this case.
Related
Imagine Facebook mobile app, where you tap on the notification about someone like your comment. The app will open the appropriate screen, scroll you down to the comment, and after you arrive there, the comment row will flash yellow for a while, rapidly turn transparent, and then it's done.
I just want to make the same flashing animation to a ListView/Column element to let users know that something is happening there as a result of their action. But from what I gathered, to create just a simple animation like that needs a complex elaborate contraption with Animation widgets.
There's a widget that does a much appreciated fade animation called FadeInImage. I just need to provide destination URL, placeholder image asset, and the widget will handle the rest. I'm wondering if there's such alternative where I can just provide a key to a widget, and then call from anywhere: rowKey.currentState.flash(color: Colors.yellow). Or perhaps a way to let me tell the ListView or Column to flash certain row like listViewKey.currentState.items[5].flash(color: Colors.yellow).
There is no a Widget like you are looking for, but you can create a custom widget if you know the Flutter basics. You will be able to build from simple animations to the most advanced ones.
I made a simple example, a list of elements where you can select any element from the list (index).
When you open the screen, you will see the scroll animation, after that, the blink animation will start.
class FlashingHome extends StatelessWidget {
const FlashingHome({Key? key}) : super(key: key);
void _goToWidget(BuildContext context, int index) {
Navigator.of(context).push(
MaterialPageRoute(
builder: (_) => FlashingList(index: index),
),
);
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: Column(
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.center,
children: [
MaterialButton(
color: Colors.greenAccent,
child: const Text('Go to element 5'),
onPressed: () => _goToWidget(context, 5),
),
MaterialButton(
color: Colors.greenAccent,
child: const Text('Go to element 10'),
onPressed: () => _goToWidget(context, 10),
),
],
),
),
);
}
}
class FlashingList extends StatefulWidget {
const FlashingList({required this.index, Key? key}) : super(key: key);
final int index;
#override
State<FlashingList> createState() => _FlashingListState();
}
class _FlashingListState extends State<FlashingList>
with SingleTickerProviderStateMixin {
final _scrollController = ScrollController();
late final AnimationController _animationController;
final _itemSize = 150.0;
Timer? _timer;
Future<void> _startScrolling() async {
await _scrollController.animateTo(
_itemSize * widget.index,
duration: const Duration(seconds: 1),
curve: Curves.easeOut,
);
// after the scroll animation finishes, start the blinking
_animationController.repeat(reverse: true);
// the duration of the blinking
_timer = Timer(const Duration(seconds: 3), () {
setState(() {
_animationController.stop();
_timer?.cancel();
});
});
}
#override
void initState() {
_animationController = AnimationController(
vsync: this,
duration: const Duration(milliseconds: 500),
);
WidgetsBinding.instance!.addPostFrameCallback((_) => _startScrolling());
super.initState();
}
#override
void dispose() {
_timer?.cancel();
_animationController.dispose();
_scrollController.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Flashing List'),
),
body: ListView.builder(
controller: _scrollController,
itemCount: 15,
itemExtent: 150,
itemBuilder: (context, index) {
final item = Padding(
padding: const EdgeInsets.all(20.0),
child: Text('My Item :$index'),
);
return Padding(
padding: const EdgeInsets.all(4.0),
child: FittedBox(
child: index == widget.index && _animationController.isDismissed
? FadeTransition(
opacity: _animationController,
child: Container(
color: Colors.yellow,
child: item,
),
)
: Container(
color: Colors.grey[200],
child: item,
),
),
);
},
),
);
}
}
Result:
Now that you know how to create an automatic scrolling list, animated item, you can customize this with a more complex animation and extract into a custom widget that you can reuse in your projects.
Reference: https://docs.flutter.dev/development/ui/animations
Try shimmer
While the data is being fetched, you can create a simple animation which will give the feel that something's loading. Here's a simple example.
I am using FAB onPress to reflect the changes.
bool isApiCallProcess = false;
#override
Widget build(BuildContext context) {
return SafeArea(
child: Scaffold(
floatingActionButton: FloatingActionButton(
onPressed: () {
setState(() {
isApiCallProcess = !isApiCallProcess;
});
},
),
backgroundColor: Colors.white,
body: (isApiCallProcess == false)
? CircleAvatar(
backgroundColor:
Colors.black12,
radius: 40,
backgroundImage: AssetImage(
'images/dosa.jpg',
),
):
Shimmer.fromColors(
baseColor: Colors.grey[300]!,
highlightColor: Colors.grey[100]!,
child: Wrap(
children: [
Column(
children: [
const CircleAvatar(
radius: 40,
backgroundImage: AssetImage(
'',
),
),
const SizedBox(
height: 10,
),
Container(
decoration: ShapeDecoration(
color: Colors.grey[400]!,
shape: const
RoundedRectangleBorder(),
),
height: 12,
width: 60,
),
],
),
],
),
),
),
);
Here's the screenshots :
I'm writing an audio player. Like most media players (Youtube, Spotify, etc), I want a "remote" overlay on the screen while media is playing. No matter what the user is doing, they should be able to control the media.
I accomplished that with a Stack under MaterialApp
MaterialApp(
title: 'MyApp',
navigatorObservers: [gRouteObserver],
routes: appRoutes,
builder: (context, child) {
return Stack(children: [
child!,
Positioned(
bottom: 55,
width: MediaQuery.of(context).size.width,
child: StatefulBuilder(builder: (context, _setState) {
gPlayer.widgetSBRefresher = _setState;
return gPlayer.started ? gPlayer.widget : const SizedBox(height: 0);
}))
]);
});
gPlayer.widget references this
class MiniPlayer extends StatefulWidget {
const MiniPlayer({Key? key}) : super(key: key);
#override
State<MiniPlayer> createState() => MiniPlayerState();
}
class MiniPlayerState extends State<MiniPlayer> with AutomaticKeepAliveClientMixin {
#override
bool get wantKeepAlive => true;
#override
Widget build(context) {
super.build(context);
return Align(
alignment: Alignment.bottomCenter,
child: Padding(
padding: const EdgeInsets.all(8.0),
child: Container(
color: Colors.black,
child: Padding(
padding: const EdgeInsets.all(2.0),
child: Column(
children: [
Material(
child: Row(
mainAxisSize: MainAxisSize.max,
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
AvatarAlone(id: gPlayer.current!.owner),
Expanded(
child: Padding(
padding: const EdgeInsets.all(5.0),
child: Text(gPlayer.playing
? "Now Playing"
: "Paused"),
),
),
// here is the code I'll
// be talking about -->
IconButton(
color: Colors.white,
iconSize: 20,
icon: const Icon(MyIcons.bookmark),
onPressed: gPlayer.bookmarkBuilder,
),
InkWell(child: Icon(gPlayer.playing ? MyIcons.pauseCircle : MyIcons.playCircle, size: 50), onTap: gPlayer.playPause)
],
),
),
],
),
),
),
));
}
refresh() {
setState(() {});
}
}
I used a code comment to point out this icon button.
IconButton(
color: Colors.white,
iconSize: 20,
icon: const Icon(MyIcons.bookmark),
onPressed: gPlayer.bookmarkBuilder,
),
So, when this widget is open and the app is on the home route ("/"), I can do
bookmarkBuilder() {
Scaffold.of(gScaffApp.currentContext!).openDrawer();
}
and it will open the drawer.
I've attached the same drawers to all my routes' scaffolds.
When other routes are up, with their own scaffolds, I want bookmarkBuilder to open the drawer on the topmost route. But I can't quite figure out how.
So I have a working solution to this, but I don't love it.
I created a global variable, gScaffs, with gScaffApp as the first element.
List<GlobalKey<ScaffoldState>> gScaffs = [gScaffApp];
My secondary routes all use the same base scaffold widget
class _CardScaffoldState extends State<CardScaffold> {
#override
initState() {
super.initState();
gScaffs.add(GlobalKey());
}
#override
Widget build(BuildContext context) {
return WillPopScope(
onWillPop: () async => false,
child: Scaffold(key: gScaffs.last,
drawer: DrawerBookmarks()
...
And the dispose method looks like this.
#override
dispose() {
super.dispose();
gScaffs.removeLast();
}
And then, in my bookmarkBuilder function, I have this.
It's not clear to me why, but gScaffApp needs the drawer triggered one way, while the CardScaffolds need the drawer triggered the other way.
bookmarkBuilder() {
if (gScaffApp == gScaffs.last) {
Scaffold.of(gScaffApp.currentContext!).openDrawer();
} else {
gScaffs.last.currentState!.openDrawer();
}
}
I want to create a website using flutter web but I'm unable to navigate to sections in the same page. Here's an example of what I want to achieve using flutter.
P.S. Navigator is not working:
I created an example with PageView
class MyHomePage extends StatelessWidget {
var list = ["Home","Services", "Work", "About"];
var colors = [Colors.orange, Colors.blue, Colors.red, Colors.green];
PageController controller = PageController();
#override
Widget build(BuildContext context) {
return Scaffold(
body: SafeArea(
child: Column(
mainAxisSize: MainAxisSize.max,
children: <Widget>[
Row(
children: <Widget>[
Container(
width: 50,
height: 50,
margin: EdgeInsets.all(8),
decoration: BoxDecoration(
color: Colors.blue,
borderRadius: BorderRadius.circular(10)
),
),
Spacer(),
Row(
children: List.generate(3, (index){
return GestureDetector(
onTap: (){
_scrollToIndex(index);
},
child: Container(
margin: EdgeInsets.all(8),
child: Text(
list[index+1]
),
),
);
}),
)
],
),
Expanded(
child : PageView(
scrollDirection: Axis.vertical,
pageSnapping: false,
controller: controller,
children: List.generate(list.length, (index){
return Container(
width: MediaQuery.of(context).size.width,
height: double.maxFinite,
color: colors[index],
child: Center(
child: Text(
list[index],
style: TextStyle(
color: Colors.white,
fontSize: 50
),
),
),
);
})
),
),
],
)
),
);
}
void _scrollToIndex(int index) {
controller.animateToPage(index + 1, duration: Duration(seconds: 2), curve: Curves.fastLinearToSlowEaseIn);
}
}
The output:
ScrollController is the thing you are looking for.
Add a new one to your ScrolView and you can set where you want it to scroll to.
Josteve mentioned a way of doing it. But I'd like to show the other way which provides more features as one would expect in the gif example you have put.
You can see the demo here: https://mohith7548.github.io/portfolio/
My project has 3 sections called About, Blog & Projects. It also has another top section called Home. So the order of screens is Home, About, Blog & Projects. Each section takes full-screen height & width. So the starting offset for these pages are [0 * screenHeight, 1 * screenHeight, 2 * screenHeight, 3 * screenHeight] respectively. screenHeight can be accessed by MediaQuery.of(context).size.height inside build method.
class Portfolio extends StatefulWidget {
#override
_PortfolioState createState() => _PortfolioState();
}
class _PortfolioState extends State<Portfolio> {
ScrollController _scrollController;
String _curNavItem;
static double offsetHome = 0;
static double offsetAbout = SizeConfig.screenHeight;
static double offsetBlog = 2 * SizeConfig.screenHeight;
static double offsetProjects = 3 * SizeConfig.screenHeight;
#override
void initState() {
super.initState();
_scrollController = ScrollController();
}
#override
void dispose() {
super.dispose();
_scrollController.dispose();
}
void scrollTo(String title) {
double offset = 0;
switch (title) {
case Constants.HOME:
offset = offsetHome;
break;
case Constants.ABOUT:
offset = offsetAbout;
break;
case Constants.BLOG:
offset = offsetBlog;
break;
case Constants.PROJECTS:
offset = offsetProjects;
break;
}
setState(() {
_curNavItem = title;
});
// animate to the pag
_scrollController.animateTo(
offset,
duration: Duration(milliseconds: 500),
curve: Curves.easeInOutQuart,
);
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: CustomScrollView(
physics: PageScrollPhysics(), // use NeverScrollableScrollPhysics() to block user scrolling
controller: _scrollController,
slivers: <Widget>[
// This is just SliverAppBar wrapped in InterheritedWidget called NavState
// You can use just SliverAppBar
NavState(
curNavItem: _curNavItem,
scrollTo: scrollTo,
child: AppBanner(key: _appBannerKey), // SliverAppBar in another file
),
SliverList(
delegate: SliverChildListDelegate([
About(),
Blog(),
Projects(),
]),
)
],
),
);
}
}
You can do this in different ways:
TabBarView https://stackoverflow.com/a/60624536/10976088
PageView https://stackoverflow.com/a/60778791/10976088
NavigationRail https://api.flutter.dev/flutter/material/NavigationRail-class.html
My method: Using a state management way to keep name or index of content pages and change visible page. I do it with the Riverpod package here:
Suppose you want to have a fixed SidebarView and HeaderView in all pages and also you have a ContentPage that will be changed.
So you can have a RootPage including these 3 sections and change ContentPage by the riverpod, so that only ContentPage will be changed.
class RootPage extends StatelessWidget {
const RootPage({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return Scaffold(
drawer: SidebarView(),
body: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
if (Responsive.isDesktop(context))
const Expanded(
flex: 1,
child: SidebarView(),
),
Expanded(
flex: 5,
child: SafeArea(
child: Column(
mainAxisAlignment: MainAxisAlignment.start,
children: [
HeaderView(),
Expanded(
child: Padding(
padding: const EdgeInsets.all(16),
child: Consumer(
builder: (context, ref, _) {
var watch = ref.watch(pageVisibleStateProvider);
return contentPageSelection(watch.state);
},
),
),
),
],
),
),
),
],
),
);
}
}
simply change content page:
Widget contentPageSelection(String pageName){
switch(pageName){
case "page1":
return Page1();
case "page2":
return Page2();
case "page3":
return Page3();
default:
return DefaultPage();
}
}
where:
final pageVisibleStateProvider = StateProvider<String>((_) => "defaultPage");
and:
class SidebarView extends StatelessWidget {
const SidebarView({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return Container(
child: Text("sidebar content"),
);
}
}
class HeaderView extends StatelessWidget {
const HeaderView({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return Container(
child: Text("HeaderView content"),
);
}
}
Now you can change content page. for example you want to show Page2:
ElevatedButton(
onPressed: (){
ref.read(pageVisibleStateProvider.notifier).state = "page2";
},
child: Text("go to page 2"),
)
where page2 and other content pages only includes content not sidebar or header:
class Page2 extends StatelessWidget {
const Page2({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return Text("page2 content");
}
}
I'm trying to create a screen that is contained within a pageview, that also contains a page view for part of the screen.
To acheive this I have an unlimited page view for the whole page itself, then every page has a header view, with a bottom half that has a page view with 3 possible options. I have this pretty much working, however, the pages I am using I would like a StreamBuilder... This is where the issue is caused.
class DiaryPage extends StatefulWidget {
#override
State<StatefulWidget> createState() => _DiaryPage();
}
class _DiaryPage extends State<DiaryPage> with TickerProviderStateMixin {
DiaryBloc _diaryBloc;
TabController _tabController;
PageController _pageController;
#override
void initState() {
_diaryBloc = BlocProvider.of<DiaryBloc>(context);
_diaryBloc.init();
_tabController = TabController(length: 3, vsync: this);
_pageController = PageController(initialPage: _diaryBloc.initialPage);
super.initState();
}
#override
void dispose() {
_diaryBloc.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
return Flexible(
child: PageView.builder(
controller: _pageController,
itemBuilder: (BuildContext context, int position) {
return _buildPage(_diaryBloc.getDateFromPosition(position));
},
itemCount: _diaryBloc.amountOfPages,
),
);
}
Widget _buildPage(DateTime date) {
return Column(
mainAxisSize: MainAxisSize.max,
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[_getHeader(date), _getTabBody()],
);
}
Widget _getHeader(DateTime date) {
return Card(
child: SizedBox(
width: double.infinity,
height: 125,
child: Column(
children: <Widget>[
Padding(
padding: const EdgeInsets.fromLTRB(8, 16, 8, 0),
child: Text(
'${DateFormat('EEEE').format(date)} ${date.day} ${DateFormat('MMMM').format(date)}',
style: Theme.of(context).textTheme.subtitle,
textScaleFactor: 1,
textAlign: TextAlign.center,
),
),
Row(
mainAxisSize: MainAxisSize.max,
children: <Widget>[
IconButton(
icon: const Icon(Icons.chevron_left),
onPressed: () => {
_pageController.previousPage(
duration: Duration(milliseconds: 250),
curve: Curves.ease)
},
),
const Expanded(child: LinearProgressIndicator()),
IconButton(
icon: const Icon(Icons.chevron_right),
onPressed: () => {
_pageController.nextPage(
duration: Duration(milliseconds: 250),
curve: Curves.ease)
},
),
],
),
Container(
height: 40.0,
child: DefaultTabController(
length: 3,
child: Scaffold(
backgroundColor: Colors.white,
appBar: TabBar(
controller: _tabController,
unselectedLabelColor: Colors.grey[500],
labelColor: Theme.of(context).primaryColor,
tabs: const <Widget>[
Tab(icon: Icon(Icons.pie_chart)),
Tab(icon: Icon(Icons.fastfood)),
Tab(icon: Icon(Icons.directions_run)),
],
),
),
),
),
],
),
),
);
}
Widget _getTabBody() {
return Expanded(
child: TabBarView(
controller: _tabController,
children: <Widget>[
_getOverviewScreen(),
_getFoodScreen(),
_getExerciseScreen(),
],
),
);
}
// TODO - this seems to be the issue, wtf and why
Widget _getBody() {
return Flexible(
child: StreamBuilder<Widget>(
stream: _diaryBloc.widgetStream,
initialData: _diaryBloc.buildEmptyWidget(),
builder: (BuildContext context, AsyncSnapshot<Widget> snapshot) {
return snapshot.data;
},
),
);
}
Widget _getExerciseScreen() {
return Text("Exercise Screen"); //_getBody();
}
Widget _getFoodScreen() {
return Text("Food Screen"); //_getBody();
}
Widget _getOverviewScreen() {
return _getBody();
}
}
As you can see, there are three widgets being returned as part of the sub page view, 2 of them are Text Widgets which show correctly, but the StreamBuilder, which is populated correctly with another Text Widget seems to give me the red screen of death. Any ideas?
Fixed the problem, it was related to the StreamBuilder being wrapped in a Flexible rather than a column. I then added column to have a mainAxisSize of max... Seemed to work.
For custom ListView/PageView
In my case, I wanted to clear the list of my listview. In a custom ListView/PageView, the findChildIndexCallback will find the element's index after i.e. a reordering operation, but also when you clear the list.
yourList.indexWhere()unfortunately returns -1 when it couldn't find an element. So, Make sure to return null in that case, to tell the callback that the child doesn't exist anymore.
...
findChildIndexCallback: (Key key) {
final ValueKey<String> valueKey = key as ValueKey<String>;
final data = valueKey.value;
final index = images.indexWhere((element) => element.id == data);
//important here:
if (index > 0 ) return index;
else return null;
},
I want to navigate from "Now Showing" to "Coming Soon" with a left swipe on the image, Moreover, I want the Appbar to not to move when I swipe, but I think it is only possible with tab bars and I am not sure, please give some advice if you know how to achieve this
enter image description here
As per GaboBrandX, he is correct. But you can also do one thing with the tabs also. The sliding will not work. It is complex, but you can give it a shot.
The picture I will give you, so there would be Tabs and below that there would be a container each container gets replaces by a click.
TabController controller;
int activeIndex = 0;
#override
void initState() {
super.initState();
this.tabController = TabController(length: 3, vsync: this);
}
//This changes the activeIndex based upon the tabController index
onTabChanged(){
this.setState((){
this.activeTabIndex = this.tabController.index;
});
}
//This will return your container, based upon your tabs selected
Widget getActiveTabView(){
case 1: {return YourSecondContainer();}
break;
default: {return YourFirstContainer();}
}
//Here is your full layout
#override
Widget build(BuildContext context) {
return Column(
children: <Widget>[
TMTabBar(titles: 'XYZ', controller: this.tabController, onChange: this.onTabChanged),
this.getActiveTabView(),
]
);
}
//Create a TabBarWidget and do this
class TMTabBar extends StatefulWidget {
String/List<String> titles;
TabController controller;
VoidCallback onChange;
TMTabBar({#required this.titles, #required this.controller, this.onChange});
#override
_TMTabBarState createState() => _TMTabBarState();
}
class _TMTabBarState extends State<TMTabBar> {
#override
void initState() {
//this is for changing the content as per the tabbar
this.widget.controller.addListener((){
if(this.widget.controller.indexIsChanging){
if(this.widget.onChange != null) this.widget.onChange();
}
});
super.initState();
}
#override
Widget build(BuildContext context) {
return TabBar(tab: YourTabs);
}
This basically gives you, what you're hoping for. Hope that helps. Thanks :)
Here I've made an example of what your looking for using a PageView. I've put only text on PageView's children, but you can put there your ListViews or anything you need. When tapping on a button the PageView navigates to the corresponding "page". This can be a starting point for you:
import 'package:flutter/material.dart';
class HomePage extends StatefulWidget {
#override
_HomePageState createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> {
PageController _pageController = PageController(
initialPage: 0,
);
goToPage(num page) {
_pageController.animateToPage(
page,
duration: Duration(milliseconds: 350),
curve: Curves.easeIn,
);
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: SafeArea(
child: Stack(
children: <Widget>[
Align(
alignment: Alignment.topCenter,
child: Container(
width: double.infinity,
height: 60.0,
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 16.0, vertical: 8.0),
child: Row(
mainAxisSize: MainAxisSize.max,
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: <Widget>[
Expanded(
child: RaisedButton(
onPressed: () => goToPage(0),
child: Text('Now Showing'),
),
),
SizedBox(
width: 4.0,
),
Expanded(
child: RaisedButton(
onPressed: () => goToPage(1),
child: Text('Coming Soon'),
),
),
],
),
),
),
),
Align(
alignment: Alignment.bottomCenter,
child: Container(
width: double.infinity,
height: MediaQuery.of(context).size.height - 60.0,
child: PageView(
controller: _pageController,
children: <Widget>[
Center(
child: Text('Tab 1'),
),
Center(
child: Text('Tab 2'),
),
],
),
),
),
],
),
),
);
}
}