In this code, when I change page (I'm using PageView as is it in code below) flutter doesn't trigger rebuild, so condition if(_page == 1) will take effect after I press "hot reload". Any tips for solution? I calling this class in main.dart (HomePage) which is Stateless widget. Could it be the problem?
Thanks for any help!
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
int _page = 0;
class Guide extends StatefulWidget {
#override
State<StatefulWidget> createState() {
return new GuideState();
}
}
class GuideState extends State<Guide> {
#override
Widget build(BuildContext context) {
return Scaffold(
body: SafeArea(
child: Center(
child: Column(children: [
if (_page == 1)
Padding(
padding: EdgeInsets.fromLTRB(0, 10, 0, 0),
child: Image(
image: AssetImage('graphics/Logo.png'),
height: MediaQuery.of(context).size.height * 0.1)),
SizedBox(height: 500, child: Page()),
]))));
}
}
class Page extends StatefulWidget {
PageState createState() => PageState();
}
class PageState extends State<Page> {
final controller = PageController(
initialPage: 0,
);
#override
Widget build(BuildContext context) {
return Scaffold(
//appBar: AppBar(title: Text('PageView Widget in Flutter')),
body: Center(
child: Container(
width: MediaQuery.of(context).size.width * 0.95,
height: MediaQuery.of(context).size.height * 0.6,
child: PageView(
controller: controller,
onPageChanged: (page) {
setState(() {
if (page == 1) {
_page = 1;
}
});
},
pageSnapping: true,
scrollDirection: Axis.horizontal,
children: <Widget>[
Container(
//color: Colors.pink,
//child: Center(
child: Text(
'1. Tento text bude nahrán z databáze.',
style: TextStyle(fontSize: 25, color: Colors.black),
)),
Container(
//color: Colors.green,
child: Text(
'2. Tento text bude nahrán z databáze',
style: TextStyle(fontSize: 25, color: Colors.black),
)),
Container(
// color: Colors.lightBlue,
child: Text(
'3. Tento text bude nahrán z databáze',
style: TextStyle(fontSize: 25, color: Colors.black),
)),
],
),
)));
}
}
The variable _page is set as global, it has to be part of as state inorder to trigger changes, but in your case you want to change a widget base on action in another child widget, this can be done in several ways depending on your choice the easies in you case is to have a function as a parameter for your child widget Page :
class Page extends StatefulWidget {
final Function(int) onChange;
const Page({Key key, this.onChange}) : super(key: key);
PageState createState() => PageState();
}
and then call it when the page change
onPageChanged: (page) {
widget.onChange(page);
},
so with this you can handle the change in you parent widget and trigger state change
class GuideState extends State<Guide> {
int _page = 0;
#override
Widget build(BuildContext context) {
return Scaffold(
body: SafeArea(
child: Center(
child: Column(
children:[
if (_page == 1)
Padding(
padding: EdgeInsets.fromLTRB(0, 10, 0, 0),
child: Image(
image: AssetImage('graphics/Logo.png'),
height: MediaQuery.of(context).size.height * 0.1,
),
),
SizedBox(
height: 500,
child: Page(
onChange: (page) {
setState(() => _page = page);
},
),
),
],
),
),
),
);
}
}
int _page = 0; is not part of the state of your Guide widget. Place it here:
class GuideState extends State<Guide> {
int _page = 0;
...
Related
I have list of products in bottom sheet, when I choose any product I want to parent's widget to add it, unfortunately my product adds only after hot reload, or when I create a new route from bottom sheet to parent's widget, how can I solve this problem, any ideas? Here is the part of the bottom sheet code
class IceBottomSheet extends StatefulWidget {
const IceBottomSheet({Key? key}) : super(key: key);
#override
_IceBottomSheetState createState() => _IceBottomSheetState();
}
class _IceBottomSheetState extends State<IceBottomSheet> {
final _model = ProductWidgetsModel();
#override
Widget build(BuildContext context) {
List<Widget> productWidgetList = [];
products.forEach((product) =>
productWidgetList.add(SingleProductWidget(product: product)));
return Provider(
model: _model,
child: Expanded(
child: GridView.count(
crossAxisSpacing: 10,
mainAxisSpacing: 16,
shrinkWrap: true,
crossAxisCount: 2, children: productWidgetList),
),
);
}
}
class SingleProductWidget extends StatefulWidget {
final Product product;
const SingleProductWidget({Key? key, required this.product})
: super(key: key);
#override
State<SingleProductWidget> createState() => _SingleProductWidgetState();
}
class _SingleProductWidgetState extends State<SingleProductWidget> {
#override
Widget build(BuildContext context) {
final model = Provider.of(context)?.model;
return Padding(
padding: const EdgeInsets.all(5.0),
child:
Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
SizedBox(
width: double.infinity,
height: 100,
child: DecoratedBox(
decoration: BoxDecoration(
image: DecorationImage(
image: widget.product.image,
),
shape: BoxShape.circle,
border: Border.all(
color: model?.idSelected == widget.product.id
? Colors.yellow
: Colors.grey,
width: 5.0,
style: BorderStyle.solid,
),
),
child: GestureDetector(
onTap: () {
model?.idSelected = widget.product.id;
// Route route =
// MaterialPageRoute(builder: (context) => BerryPage(context,));
// Navigator.push(context, route);
if(model?.idSelected == 1){
menuRow.removeAt(2);
Navigator.pop(context);
choice.insert(2, Adds(id: 102, name: 'Холодок', img: 'https://autogear.ru/misc/i/gallery/73434/2759438.jpg'));
}
}),
),
),
And here is the part of parent's widget code, it is inside GestureDetector
else if (index == 2){
setState(() {
});
showModalBottomSheet(
context: context,
isScrollControlled: true,
builder: (BuildContext builder) {
return Container(
height: 250,
child: Column(
children: [
SizedBox(
height: 10,
),Row(
children: [
Padding(
padding: EdgeInsets.only(left: MediaQuery.of(context).size.width*0.25 + MediaQuery.of(context).size.width *0.12),
child: Text(
'Холодок',
style: TextStyle(
fontFamily: 'Newfont',
fontSize: 22,
),
),
),
SizedBox(width: MediaQuery.of(context).size.width*0.25,),
IconButton(icon: Icon(Icons.close),onPressed: (){Navigator.pop(context);},)
],
),
Divider(),
IceBottomSheet(),
],
));
},
);
So when you open the BottomSheet you have to add await before it, so when you call Navigator.pop(context, data_you_want_to_pass_to_parent) it will wait for some data to be returned.
final data = await openBottomSheet();
inside the bottomSheet when you want to close, just pass the the desired data as so
Navigator.pop(context, data_you_want_to_pass_to_parent);
I have to implement a horizontal scroll list in flutter.I could do that and have included the code below(The code is still to be modified but the base of the code is good enough to put in the pictures and other such details)
But the problem is the minus bar below the horizontal scroll.I don't know what feature in flutter allows to do that.I search many things but other than radio boxes,check boxes, switches,etc I am not able to find any details of it.Please have a look at the screenshot of the app ,I have indicated the minus bar control in red.Home screen,the minus bar indicated in red
The code I have written:
#override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.black ,
body: Container(
margin: EdgeInsets.symmetric(vertical: 20.0),
height: 500,
child: ListView(
// This next line does the trick.
scrollDirection: Axis.horizontal,
shrinkWrap: true,
children: <Widget>[
Container(
width:400 ,
color: Colors.red,
),
Container(
width: 400.0,
color: Colors.blue,
),
Container(
width: 400.0,
color: Colors.green,
),
],
),
)
);
}
}
What you want to look for is not ListView but PageView here is a small code sample to try in DartPad and see how you could make your layout.
Basically I am using a PageController to change the current page by taping on certain widgets.
Code
import 'package:flutter/material.dart';
final Color darkBlue = Color.fromARGB(255, 18, 32, 47);
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
theme: ThemeData.dark().copyWith(scaffoldBackgroundColor: darkBlue),
debugShowCheckedModeBanner: false,
home: Scaffold(body: MyWidget()),
);
}
}
class MyWidget extends StatefulWidget {
#override
_MyWidgetState createState() => _MyWidgetState();
}
class _MyWidgetState extends State<MyWidget> {
final _items = [Colors.red, Colors.blue, Colors.yellow];
final _pageController = PageController();
int _currentPageNotifier = 0;
final double _indicatorWidth = 30;
Widget _buildPageView() {
return PageView.builder(
controller: _pageController,
itemCount: _items.length,
itemBuilder: (context, index) => Center(
child: FlutterLogo(
colors: _items[index],
size: 50,
),
),
onPageChanged: (int index) =>
setState(() => _currentPageNotifier = index),
);
}
Widget _buildIndicator() {
List<Widget> itemWidgets = [];
for (int index = 0; index < _items.length; index++) {
itemWidgets.add(GestureDetector(
onTap: () => _pageController.animateToPage(
index,
duration: Duration(milliseconds: 300),
curve: Curves.ease,
),
child: Container(
decoration: BoxDecoration(
color: _currentPageNotifier == index
? Colors.green
: Colors.grey,
borderRadius: BorderRadius.circular(9),
),
margin: EdgeInsets.only(right: 10),
width: _indicatorWidth,
height: 8,
),
));
}
return Positioned(
bottom: MediaQuery.of(context).size.height / 2 - 50,
left: MediaQuery.of(context).size.width / 2 -
_items.length * _indicatorWidth +
_items.length * 10,
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: itemWidgets,
),
);
}
#override
Widget build(BuildContext context) {
return Stack(
children: <Widget>[
_buildPageView(),
_buildIndicator(),
],
);
}
}
I'm trying to implement a solution where a row (containing both a TextField and a Text) in ListView.Builder is automatically for every record retrieved from a webserver.
However when I want to start typing in such a TextField the keyboard appears and immediatly disappears again.
This is the code of my screen.
class GameScreen extends StatelessWidget {
static const RouteName = "/GameScreen";
#override
Widget build(BuildContext context) {
var size = MediaQuery.of(context).size;
const horizontalMargin = 20.0;
return Scaffold(
appBar: getAppBar(),
backgroundColor: Colors.transparent,
body: Stack(
children: <Widget>[
Background(),
Column(
children: <Widget>[
Header("Starting letter: B"),
Expanded(
child: ListBlocProvider(
listWidget: GameCategoriesList(),
itemsService: CategoriesService(),
margin: EdgeInsets.only(
left: horizontalMargin,
bottom: 10,
right: horizontalMargin,
),
),
),
SizedBox(
height: 20,
),
SizedBox(
width: size.width - 40,
height: 60,
child: Container(
height: 60,
child: TextButtonWidget(
() {
// Navigator.of(context).pushNamed(GameScreen.RouteName);
},
"Stop game",
),
),
),
SizedBox(
height: 20,
)
],
),
],
),
);
}
}
This is the code of my ListBlocProvider:
class ListBlocProvider extends StatelessWidget {
final ListWidget listWidget;
final ItemsService itemsService;
final bool useColor;
final bool usePaddingTop;
final double height;
final EdgeInsets margin;
const ListBlocProvider({
#required this.listWidget,
#required this.itemsService,
this.useColor = true,
this.usePaddingTop = true,
this.height = 200,
this.margin,
});
#override
Widget build(BuildContext context) {
const horizontalMargin = 20.0;
return BlocProvider(
create: (context) => ItemsBloc(itemsService: itemsService)..add(ItemsFetched()),
child: Container(
padding: usePaddingTop ? EdgeInsets.only(top: 10) : null,
decoration: BoxDecoration(
color: this.useColor ? Color.fromRGBO(10, 50, 75, 0.9) : null,
borderRadius: BorderRadius.only(
bottomLeft: Radius.circular(10),
bottomRight: Radius.circular(10),
),
),
margin: this.margin,
height: this.height,
child: this.listWidget,
),
);
}
}
This is the code of my List:
class GameCategoriesList extends ListWidget {
#override
_GameCategoriesListState createState() => _GameCategoriesListState();
}
class _GameCategoriesListState extends State<GameCategoriesList> {
#override
Widget build(BuildContext context) {
return BlocBuilder<ItemsBloc, ItemsState>(
builder: (context, state) {
if (state is ItemsFailure) {
return Center(
child: Text('failed to fetch categories'),
);
}
if (state is ItemsSuccess) {
if (state.items.isEmpty) {
return Center(
child: Text('no categories found.'),
);
}
return ListView.builder(
itemBuilder: (BuildContext context, int index) {
var textEditingController = TextEditingController();
return GameCategoryItemWidget(
key: UniqueKey(),
categoryModel: state.items[index],
textEditingController: textEditingController,
);
},
itemCount: state.items.length,
);
}
return Center(
child: LoadingIndicator(),
);
},
);
}
}
And this is the code where the both the TextField and the Text are build:
class GameCategoryItemWidget extends StatefulWidget {
final CategoryModel categoryModel;
final TextEditingController textEditingController;
const GameCategoryItemWidget({Key key, this.categoryModel, this.textEditingController}) :
super(key: key);
#override
_GameCategoryItemWidgetState createState() => _GameCategoryItemWidgetState();
}
class _GameCategoryItemWidgetState extends State<GameCategoryItemWidget> {
var formKey = GlobalKey<FormState>();
#override
Widget build(BuildContext context) {
return Container(
child: Form(
key: this.formKey,
child: Column(
children: <Widget>[
Container(
padding: EdgeInsets.only(left: 10, top: 20, bottom: 10),
child: Text(
this.widget.categoryModel.name,
style: TextStyle(
color: Colors.white,
fontSize: 18,
),
),
),
Container(
color: Colors.white,
child: InputField(
InputDecoration(labelText: this.widget.categoryModel.name),
this.widget.textEditingController,
false,
),
),
],
),
),
);
}
#override
void dispose() {
this.widget.textEditingController.dispose();
super.dispose();
}
}
The InputField is a custom widget to hide the switch between a Material and a Cupertino version of the TextField.
I've already tried to remove the Key from the custom TextField widget. The funny part is that the input is actually working, however it can't determine for which of the TextFields in the ListView the input is determined so it adds the input to all of them. I've also tried to swap things around with making Stateless widgets Statefull, but that didn't help either.
The entire build is based upon: https://bloclibrary.dev/#/flutterinfinitelisttutorial.
Hoping you guys can help me.
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");
}
}
With the normal scroll effect, you are free to scroll how much ever you want,
but I want to have a scrollable list but only scroll full widget or 1/4 of the widget.
something like this:-
How to get a scrolling effect?
You can use PageView.
Here is the sample code. It has the paging animation. It also has attached listener to the PageController, which is useful to get current state.
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> {
var _controller = PageController(viewportFraction: 0.6);
var _color = "";
#override
void initState() {
super.initState();
_controller.addListener(() {
if (_controller.page < 0.5) {
setState(() {
_color = "yellow";
});
}
if (_controller.page >= 0.5 && _controller.page < 1.5) {
setState(() {
_color = "red";
});
}
if (_controller.page >= 1.5 && _controller.page < 2) {
setState(() {
_color = "blue";
});
}
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: Column(
children: [
SizedBox(
height: 200,
),
Text(
_color,
style: TextStyle(fontSize: 40),
),
SizedBox(
height: 100,
child: PageView(
controller: _controller,
children: [
Padding(
padding: const EdgeInsets.all(8.0),
child: SizedBox(
child: Container(
color: Colors.amber,
),
),
),
Padding(
padding: const EdgeInsets.all(8.0),
child: SizedBox(
width: 200,
child: Container(
color: Colors.red,
),
),
),
Padding(
padding: const EdgeInsets.all(8.0),
child: SizedBox(
width: 200,
child: Container(
color: Colors.lightBlue,
),
),
),
],
),
),
],
));
}
}