I'm trying to build a Tabbed View that has lists as children.
Both the Category labels and the lists content will be fetched from a database.
I am passing the labels from the caller page and successfully passing them as a List.
Now I'm trying to load my lists, and I have built a Widget (myList) that returns successfully a Future ListView.
The problems are two:
Every time i swipe left or right, the list rebuilds itself, while I would like to have it built only once
How can I use the code I made to have the tabs' children actually reflect the labels and are loaded dinamically according to how many categories i have?
Right now my code is this:
import 'package:flutter/material.dart';
import 'package:flutter_app/ui/menu_category_list.dart';
// Each TabBarView contains a _Page and for each _Page there is a list
// of _CardData objects. Each _CardData object is displayed by a _CardItem.
List<Tab> Tabs(List<String> l){
List<Tab> list;
for (String c in l) {
list.add(new Tab(text: c));
}
return list;
}
class TabsDemo extends StatelessWidget {
const TabsDemo({ Key key , this.categorie}) : super(key: key);
final List<Tab> categorie;
#override
Widget build(BuildContext ctxt) {
return new MaterialApp(
title: "Nice app",
home: new DefaultTabController(
length: 5,
child: new Scaffold(
appBar: new AppBar(
title: new Text("Title"),
bottom: new TabBar(
tabs:
categories,
//new Tab(text: "First Tab"),
//new Tab(text: "Second Tab"),
),
),
body: new TabBarView(
children: [
new MenuCategoryList(),
new MenuCategoryList(),
new MenuCategoryList(),
new MenuCategoryList(),
new MenuCategoryList()
]
)
),
)
);
}
}
currently result
Thanks a lot in advance
You can use List<E>.generate to achieve this.
import 'package:flutter/material.dart';
Say you have a set of categories passed from your caller page. And let's say this is your list of categories.
List<String> categories = ["a", "b", "c", "d", "e", "f", "g", "h"];
Then you can do something like this to achieve what you desire.
class TabsDemo extends StatefulWidget {
#override
_TabsDemoState createState() => _TabsDemoState();
}
class _TabsDemoState extends State<TabsDemo> {
TabController _controller;
#override
void initState() {
super.initState();
}
#override
Widget build(BuildContext ctxt) {
return new MaterialApp(
home: DefaultTabController(
length: categories.length,
child: new Scaffold(
appBar: new AppBar(
title: new Text("Title"),
bottom: new TabBar(
isScrollable: true,
tabs: List<Widget>.generate(categories.length, (int index){
print(categories[0]);
return new Tab(icon: Icon(Icons.directions_car), text: "some random text");
}),
),
),
body: new TabBarView(
children: List<Widget>.generate(categories.length, (int index){
print(categories[0]);
return new Text("again some random text");
}),
)
))
);
}
You can also set different set of widgets as the Tab's view. You can create a list of pages and follow the same method.
Absolutely true List<E>.generate best solution to solve.
Problems arise if you need to modify the arrays. They consist in the fact that when modifying an array you do not have the opportunity to use the same controller.
You can use the next custom widget for this case:
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
home: MyHomePage(),
);
}
}
class MyHomePage extends StatefulWidget {
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
List<String> data = ['Page 0', 'Page 1', 'Page 2'];
int initPosition = 1;
#override
Widget build(BuildContext context) {
return Scaffold(
body: SafeArea(
child: CustomTabView(
initPosition: initPosition,
itemCount: data.length,
tabBuilder: (context, index) => Tab(text: data[index]),
pageBuilder: (context, index) => Center(child: Text(data[index])),
onPositionChange: (index){
print('current position: $index');
initPosition = index;
},
onScroll: (position) => print('$position'),
),
),
floatingActionButton: FloatingActionButton(
onPressed: () {
setState(() {
data.add('Page ${data.length}');
});
},
child: Icon(Icons.add),
),
);
}
}
/// Implementation
class CustomTabView extends StatefulWidget {
final int itemCount;
final IndexedWidgetBuilder tabBuilder;
final IndexedWidgetBuilder pageBuilder;
final Widget stub;
final ValueChanged<int> onPositionChange;
final ValueChanged<double> onScroll;
final int initPosition;
CustomTabView({
#required this.itemCount,
#required this.tabBuilder,
#required this.pageBuilder,
this.stub,
this.onPositionChange,
this.onScroll,
this.initPosition,
});
#override
_CustomTabsState createState() => _CustomTabsState();
}
class _CustomTabsState extends State<CustomTabView> with TickerProviderStateMixin {
TabController controller;
int _currentCount;
int _currentPosition;
#override
void initState() {
_currentPosition = widget.initPosition ?? 0;
controller = TabController(
length: widget.itemCount,
vsync: this,
initialIndex: _currentPosition,
);
controller.addListener(onPositionChange);
controller.animation.addListener(onScroll);
_currentCount = widget.itemCount;
super.initState();
}
#override
void didUpdateWidget(CustomTabView oldWidget) {
if (_currentCount != widget.itemCount) {
controller.animation.removeListener(onScroll);
controller.removeListener(onPositionChange);
controller.dispose();
if (widget.initPosition != null) {
_currentPosition = widget.initPosition;
}
if (_currentPosition > widget.itemCount - 1) {
_currentPosition = widget.itemCount - 1;
_currentPosition = _currentPosition < 0 ? 0 :
_currentPosition;
if (widget.onPositionChange is ValueChanged<int>) {
WidgetsBinding.instance.addPostFrameCallback((_){
if(mounted) {
widget.onPositionChange(_currentPosition);
}
});
}
}
_currentCount = widget.itemCount;
setState(() {
controller = TabController(
length: widget.itemCount,
vsync: this,
initialIndex: _currentPosition,
);
controller.addListener(onPositionChange);
controller.animation.addListener(onScroll);
});
} else if (widget.initPosition != null) {
controller.animateTo(widget.initPosition);
}
super.didUpdateWidget(oldWidget);
}
#override
void dispose() {
controller.animation.removeListener(onScroll);
controller.removeListener(onPositionChange);
controller.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
if (widget.itemCount < 1) return widget.stub ?? Container();
return Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: <Widget>[
Container(
alignment: Alignment.center,
child: TabBar(
isScrollable: true,
controller: controller,
labelColor: Theme.of(context).primaryColor,
unselectedLabelColor: Theme.of(context).hintColor,
indicator: BoxDecoration(
border: Border(
bottom: BorderSide(
color: Theme.of(context).primaryColor,
width: 2,
),
),
),
tabs: List.generate(
widget.itemCount,
(index) => widget.tabBuilder(context, index),
),
),
),
Expanded(
child: TabBarView(
controller: controller,
children: List.generate(
widget.itemCount,
(index) => widget.pageBuilder(context, index),
),
),
),
],
);
}
onPositionChange() {
if (!controller.indexIsChanging) {
_currentPosition = controller.index;
if (widget.onPositionChange is ValueChanged<int>) {
widget.onPositionChange(_currentPosition);
}
}
}
onScroll() {
if (widget.onScroll is ValueChanged<double>) {
widget.onScroll(controller.animation.value);
}
}
}
You can use dynamic children using for loop within your Tabbarview Widget
List<String> categories = ["category 1" , "category 2", "category 3",];
return TabBarView(
children:[
for(var category in categories)
Text(category), // this widget will show a text with specific category. You can use any other widget
],
);
Null safety version
import 'package:flutter/material.dart';
class CustomTabView extends StatefulWidget {
final int? itemCount;
final IndexedWidgetBuilder? tabBuilder;
final IndexedWidgetBuilder? pageBuilder;
final Widget? stub;
final ValueChanged<int>? onPositionChange;
final ValueChanged<double>? onScroll;
final int? initPosition;
CustomTabView({this.itemCount, this.tabBuilder, this.pageBuilder, this.stub,
this.onPositionChange, this.onScroll, this.initPosition});
#override
_CustomTabsState createState() => _CustomTabsState();
}
class _CustomTabsState extends State<CustomTabView> with TickerProviderStateMixin {
late TabController controller;
late int _currentCount;
late int _currentPosition;
#override
void initState() {
_currentPosition = widget.initPosition!;
controller = TabController(
length: widget.itemCount!,
vsync: this,
initialIndex: _currentPosition,
);
controller.addListener(onPositionChange);
controller.animation!.addListener(onScroll);
_currentCount = widget.itemCount!;
super.initState();
}
#override
void didUpdateWidget(CustomTabView oldWidget) {
if (_currentCount != widget.itemCount) {
controller.animation!.removeListener(onScroll);
controller.removeListener(onPositionChange);
controller.dispose();
if (widget.initPosition != null) {
_currentPosition = widget.initPosition!;
}
if (_currentPosition > widget.itemCount! - 1) {
_currentPosition = widget.itemCount! - 1;
_currentPosition = _currentPosition < 0 ? 0 :
_currentPosition;
if (widget.onPositionChange is ValueChanged<int>) {
WidgetsBinding.instance!.addPostFrameCallback((_){
if(mounted) {
widget.onPositionChange!(_currentPosition);
}
});
}
}
_currentCount = widget.itemCount!;
setState(() {
controller = TabController(
length: widget.itemCount!,
vsync: this,
initialIndex: _currentPosition,
);
controller.addListener(onPositionChange);
controller.animation!.addListener(onScroll);
});
} else if (widget.initPosition != null) {
controller.animateTo(widget.initPosition!);
}
super.didUpdateWidget(oldWidget);
}
#override
void dispose() {
controller.animation!.removeListener(onScroll);
controller.removeListener(onPositionChange);
controller.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
if (widget.itemCount! < 1) return widget.stub ?? Container();
return Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: <Widget>[
Container(
alignment: Alignment.center,
child: TabBar(
isScrollable: true,
controller: controller,
labelColor: Theme.of(context).primaryColor,
unselectedLabelColor: Theme.of(context).hintColor,
indicator: BoxDecoration(
border: Border(
bottom: BorderSide(
color: Theme.of(context).primaryColor,
width: 2,
),
),
),
tabs: List.generate(
widget.itemCount!,
(index) => widget.tabBuilder!(context, index),
),
),
),
Expanded(
child: TabBarView(
controller: controller,
children: List.generate(
widget.itemCount!,
(index) => widget.pageBuilder!(context, index),
),
),
),
],
);
}
onPositionChange() {
if (!controller.indexIsChanging) {
_currentPosition = controller.index;
if (widget.onPositionChange is ValueChanged<int>) {
widget.onPositionChange!(_currentPosition);
}
}
}
onScroll() {
if (widget.onScroll is ValueChanged<double>) {
widget.onScroll!(controller.animation!.value);
}
}
}
Related
I am trying to build a Reviews section for my app. In that, I'm trying to have a read more feature which obviously as the name suggests will expand the widget to show more text, upon tapping the widget.
I'm using a indexed stack with BottomNavigationBar to switch between reviews and the product page.
import 'package:cached_network_image/cached_network_image.dart';
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter_rating_bar/flutter_rating_bar.dart';
import 'Product_Page_Widgets.dart';
void main() => runApp(Product());
class Product extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
home: ProductState(),
);
}
}
class ProductState extends StatefulWidget {
Map<dynamic,dynamic> productData;
CollectionReference reviews;
ProductState({#required this.productData,#required this.reviews});
#override
_ProductStateState createState() => _ProductStateState();
}
class _ProductStateState extends State<ProductState> with TickerProviderStateMixin{
List<NetworkImage> _Images = [];
List<Widget> productImages = [];
List<Widget> reviewWidgets = [];
String changeableText = "";
List<String> firstHalf = [];
List<String> secondHalf = [];
var cardHeight = [];
bool load = false;
int pageIndex = 0;
List<Widget> pageWidget = [];
addPageWidgets() async{
pageWidget.add( ProductPageWidget(
productName: widget.productData['Name'],
galleryItems: _Images,cachedImages: productImages,description:widget.productData['description'] ?? "placeholder")
);
Widget w = await buildReviewsWidgets();
pageWidget.add(w);
}
GlobalKey key = new GlobalKey();
Future <Widget> buildReviewsWidgets() async{
var docs = await widget.reviews.getDocuments();
var documents = docs.documents;
print(documents.toString());
if(docs != null) {
for (int i = 0; i < documents.length; i++) {
cardHeight.add(MediaQuery.of(context).size.height/4);
print(documents[i].data['Review']);
if(documents[i].data['Review'].toString().length>50){
firstHalf.add( documents[i].data['Review'].toString().substring(0,50));
secondHalf.add(documents[i].data['Review'].toString().substring(51));
}else{
firstHalf.add( documents[i].data['Review'].toString());
secondHalf.add("");
}
}
}
for(var doc in documents){
print(doc.data);
}
return Container(
height:MediaQuery.of(context).size.height,
child: ListView.builder(
key: key,
itemCount: documents.length,
itemBuilder: (context,index) => AnimatedSize(
vsync: this,
curve: Curves.easeIn,
duration: new Duration(milliseconds: 300),
child: Container(
height: cardHeight[index],
child: Card(
elevation: 2.0,
child: ListTile(
onTap: (){
stateUpdate(index);
},
leading: CircleAvatar(),
title: Text(documents[index].data['Title']),
subtitle: Column(children: [
RatingBar(
itemBuilder: (context, _) => Icon(
Icons.star,
color: Colors.amber,
),
maxRating: 5.0,
allowHalfRating: false,
ignoreGestures: true,
initialRating: int.parse(documents[index].data['Rating'].toString()).toDouble(),
),
Wrap(
children: <Widget>[
Text(firstHalf[index].toString()),
Text("Read More")
],
),
]),
),
),
),
),
),
);
}
void AddNetworkImages (BuildContext context){
if(!load) {
for (int i = 0; i < widget.productData.length; i++) {
if (widget.productData.containsKey('Img$i')) {
print("Img$i exists");
_Images.add(
NetworkImage(widget.productData['Img$i'])
);
productImages.add(
CachedNetworkImage(
imageUrl: widget.productData['Img$i'],
placeholder: (context, url) => CircularProgressIndicator(),
errorWidget: (context, url, error) => Icon(Icons.error),
),
);
}
}
}
}
void loadWidgets(BuildContext context){
setState(() {
});
load = true;
}
#override
void initState() {
//AddNetworkImages(context);
addPageWidgets();
super.initState();
}
#override
Widget build(BuildContext context) {
WidgetsBinding.instance
.addPostFrameCallback((_) => AddNetworkImages(context));
WidgetsBinding.instance
.addPostFrameCallback((_) => loadWidgets(context));
return Scaffold(
appBar: AppBar(
actions: <Widget>[
],
title: Text("$changeableText"),
backgroundColor: Colors.deepOrangeAccent,
),
body: SingleChildScrollView(
scrollDirection: Axis.vertical,
physics: BouncingScrollPhysics(
),
child:IndexedStack(
index: pageIndex,
children: pageWidget,
)),
bottomNavigationBar:BottomNavigationBar(
currentIndex: pageIndex,
onTap: (index){
setState(() {
pageIndex = index;
});
},
items: [
BottomNavigationBarItem(
icon: Icon(Icons.shopping_cart),
title: Text("Overview")
),
BottomNavigationBarItem(
icon: Icon(Icons.star),
title: Text("Reviews")
)
],
)
);
}
void stateUpdate(int index) {
setState(() {
firstHalf[index]+=secondHalf[index];
cardHeight[index] = MediaQuery.of(context).size.height/2;
});
print(index);
print(firstHalf[index]);
}
}
Now, inside the buildReviewsWidgets() method, i'm getting all of the review data ,iterating over it and splitting it into the firstHalf and secondHalf lists, so that, on tapping the ListTile, i can simply join firstHalf[index] and secondHalf[index], as i have done so on the ontap method of the ListTiles, but tapping on the list tile does nothing at all..the list tiles don't update, basically nothing happens. But whats interesting is that if I setState and change the value of changeableText, it does update the text on the appbar.. can anyone tell me why this is happening? how do i fix it?
I just cleaned the code up and removed the need of storing widgets with lists..not involving any lists with the widgets instantly fixed the problem.
I am trying to build a form in flutter with the dynamic list of chips where user can select the multiple category but i am unable to build it so as i am new to flutter i am not able to get it done that how can i get the static form fields and get the dynamic list of the chips in it.
I am trying to get it using the grid view but the grid view is repeating the the whole form with every chip and if i use the grid view with only chip i am not able to get the rest of the static field of the form.
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("Student form"),
),
body:ExamGrid(),
);
}
Below is the ExamGrid:
class _ExamGridState extends State<ExamGrid> {
#override
Widget build(BuildContext context) {
final loadedExams = Provider.of<StudentFormProvider>(context);
final loadedExam = loadedExams.exams;
return GridView.builder(
padding: const EdgeInsets.all(1.0),
itemCount: loadedExam.length,
itemBuilder: (ctx, i) {
String catname;
final exam = loadedExam[i];
if (i > 0) {
catname = exam.catname;
}
if (exam.catname != catname) {
//new SizedBox(height: 8);
catname = exam.catname;
return Padding(
padding: EdgeInsets.fromLTRB(16, 0, 8, 8),
child: Align(alignment: Alignment.centerLeft,
child: Text(exam.catname,
style: TextStyle(
color: Colors.black,
fontWeight: FontWeight.bold,
fontSize: 17,
),
),
),
);
} else {
return FilterChip(
label: Text(exam.examname),
backgroundColor: Colors.transparent,
shape: StadiumBorder(side: BorderSide()),
selected: exam.isselected,
onSelected: (bool checked) {
setState(() {
exam.isselected = checked;
});
},
);
}
},
gridDelegate: SliverGridDelegateWithMaxCrossAxisExtent(
//crossAxisCount: 1,
maxCrossAxisExtent: 200,
childAspectRatio: 4 / 1,
mainAxisSpacing: 10,
),
);
}
}
Can anyone please help me what to do in such scenario as anything i am trying results to unexpected result. I want to have multiple static form list with the dynamic list of chips
Have two widgets list in the class. One empty & one with all of the categories.
Show the user all of the categories and when the user tap on one category add it to the empty list and call setstate.
I have used this you need in my project there is a complete sample, you can select one chip or multiple
class CourseFilterScreen extends StatefulWidget {
static const route = "/CourseFilterScreen";
_CourseFilterScreenState createState() => _CourseFilterScreenState();
}
class _CourseFilterScreenState extends State<CourseFilterScreen> {
List title = [
{'title': "React jsx", "value": "React jsx"},
{'title': "English For Kids", "value": "English for kids"},
{'title': "IELTS", "value": "IELTS"},
{'title': "Egnlish", "value": "Egnlish"},
{'title': "Flutter", "value": "Flutter"},
];
List selectedReportList = List();
String selectedReport = '';
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
Container(
child: MultiSelectChip(
reportList: title,
onSelectionChanged: (selectedList) {
setState(() {
selectedReportList = selectedList;
});
},
),
),
Text("${selectedReportList.join(",")}"),
SingleSelectChip(
reportList: title,
onSelectionChanged: (selectItem) {
setState(() {
selectedReport = selectItem;
});
},
),
Text(selectedReport),
],
),
),
);
}
}
class MultiSelectChip extends StatefulWidget {
final List reportList;
final Function onSelectionChanged;
MultiSelectChip({this.reportList, this.onSelectionChanged});
#override
_MultiSelectChipState createState() => _MultiSelectChipState();
}
class _MultiSelectChipState extends State<MultiSelectChip> {
List selectedChoices = List();
#override
Widget build(BuildContext context) {
return Wrap(
children: widget.reportList
.map((item) => (Container(
padding: const EdgeInsets.all(2.0),
child: ChoiceChip(
selectedColor: Colors.lightBlueAccent,
label: Text(item['title']),
selected: selectedChoices.contains(item['value']),
onSelected: (selected) {
setState(() {
selectedChoices.contains(item['value'])
? selectedChoices.remove(item['value'])
: selectedChoices.add(item['value']);
widget.onSelectionChanged(selectedChoices);
});
},
),
)))
.toList());
}
}
class SingleSelectChip extends StatefulWidget {
final List reportList;
final Function onSelectionChanged;
SingleSelectChip({this.reportList, this.onSelectionChanged});
#override
_SingleSelectChipState createState() => _SingleSelectChipState();
}
class _SingleSelectChipState extends State<SingleSelectChip> {
String selectedChoices = '';
#override
Widget build(BuildContext context) {
return Wrap(
children: widget.reportList
.map((item) => (Container(
padding: const EdgeInsets.all(2.0),
child: ChoiceChip(
selectedColor: Colors.lightBlueAccent,
label: Text(item['title']),
selected: selectedChoices.contains(item['value']),
onSelected: (selected) {
setState(() {
selectedChoices = item['value'];
});
},
),
)))
.toList());
}
}
I am trying to use TabBar and Bottom Navigation bar together. I am able to save state of tabview when I switch to different page using bottom navigation bar. When I switch back, the position of the selected tab and pageview do not match.
What could be possibly wrong? Please help me with example code of using tab bar and bottom navigation bar together.
Set initialIndex, Take a variable which update your initial and last selected tab position, This variable must be update with the new value when you change any tab.
int lastTabPosition=0;
DefaultTabController(
length: 3,
initialIndex: lastTabPosition,
child: Scaffold(
appBar: AppBar(
title: Text("Tabs"),
bottom: TabBar(
tabs: <Widget>[
Tab(text: 'One'),
Tab(text: 'Two'),
Tab(text: 'Three'),
],
),
),
body: TabBarView(
children: <Widget>[
],
),
),
);
import 'package:flutter/material.dart';
void main() {
runApp(new MaterialApp(
home: new MyHomePage(),
));
}
class TabbedPage extends StatefulWidget {
TabbedPage({Key key, this.pageIndex, this.tabCount}) : super(key: key);
final int pageIndex;
final int tabCount;
_TabbedPageState createState() => new _TabbedPageState();
}
class _TabbedPageState extends State<TabbedPage> with TickerProviderStateMixin {
TabController _tabController;
int _getInitialIndex() {
int initialIndex = PageStorage.of(context).readState(
context,
identifier: widget.pageIndex,
) ??
0;
print("Initial Index ${initialIndex}");
return initialIndex;
}
#override
void initState() {
_tabController = new TabController(
length: widget.tabCount,
vsync: this,
initialIndex: _getInitialIndex(),
);
_tabController.addListener(() {
print("New Index ${_tabController.index}");
PageStorage.of(context).writeState(
context,
_tabController.index,
identifier: widget.pageIndex,
);
});
super.initState();
}
Widget build(BuildContext context) {
return new Column(
children: <Widget>[
new Container(
color: Theme.of(context).primaryColor,
child: new TabBar(
controller: _tabController,
isScrollable: true,
tabs: new List<Tab>.generate(widget.tabCount, (int tabIndex) {
var name = 'Tab ${widget.pageIndex}-${tabIndex}';
return new Tab(text: name);
}),
),
),
new Expanded(
child: new TabBarView(
controller: _tabController,
children:
new List<Widget>.generate(widget.tabCount, (int tabIndex) {
return new ListView.builder(
key: new PageStorageKey<String>(
'TabBarView:${widget.pageIndex}:$tabIndex'),
itemCount: 20,
itemExtent: 60.0,
itemBuilder: (BuildContext context, int index) => new Text(
'View ${widget.pageIndex}-${tabIndex}-${index}'));
}),
),
),
],
);
}
}
class MyHomePage extends StatefulWidget {
_MyHomePageState createState() => new _MyHomePageState();
}
const List<int> tabCounts = const <int>[5, 8];
class _MyHomePageState extends State<MyHomePage> with TickerProviderStateMixin {
PageController _controller = new PageController();
int currentIndex = 0;
Widget build(BuildContext context) {
return new Scaffold(
appBar: new AppBar(),
body: new PageView(
controller: _controller,
children: new List<Widget>.generate(tabCounts.length, (int index) {
return new TabbedPage(
pageIndex: index,
tabCount: tabCounts[index],
);
}),
),
bottomNavigationBar: BottomNavigationBar(
currentIndex: currentIndex,
onTap: (int index) {
setState(() {
currentIndex = index;
_controller.jumpToPage(index);
});
},
items: <BottomNavigationBarItem>[
BottomNavigationBarItem(
icon: Icon(Icons.home),
title: Text('Home'),
),
BottomNavigationBarItem(
icon: Icon(Icons.settings),
title: Text("Settings"),
),
],
),
);
}
}
reference : https://github.com/flutter/flutter/issues/20341
I have been trying to create backdrop menu with some navigation buttons each with his own stateless widget page . I want to pass the child widget page based on index position of the clicked menu.
backlayer.dart
class BackLayer extends StatefulWidget {
AnimationController controller;
Widget child;
BackLayer({this.controller});
_BackLayerState createState() => _BackLayerState();
}
class _BackLayerState extends State<BackLayer> {
var _currentPageIndex;
List<String> navlist = ['Home', 'About', 'Contact Us'];
bool get isPanelVisible {
final AnimationStatus status = widget.controller.status;
return status == AnimationStatus.completed ||
status == AnimationStatus.forward;
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: ListView.builder(
itemCount: navlist.length,
itemBuilder: (BuildContext context, int index) {
return Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
ListTile(
onTap: () {
setState(() {
_currentPageIndex = navlist[index];
});
// PanelClass(frontLayer: About(),);
print(navlist[index].toString());
widget.controller
.fling(velocity: isPanelVisible ? -1.0 : 1.0);
},
title: Text(
navlist[index],
textAlign: TextAlign.center,
style: TextStyle(
color: Colors.black87, fontWeight: FontWeight.bold),
),
)
],
);
I want to pass the reference of the clicked menu inside backdrop.dart ,where i am gonna pass that reference to panelClass body as the child .
Backdrop.dart
class BackDrop extends StatefulWidget {
_BackDropState createState() => _BackDropState();
}
class _BackDropState extends State<BackDrop> with TickerProviderStateMixin {
AnimationController controller;
#override
void initState() {
super.initState();
controller = AnimationController(
duration: Duration(seconds: 3), vsync: this, value: 1.0);
}
#override
void dispose() {
super.dispose();
controller.dispose();
}
bool get isPanelVisible {
final AnimationStatus status = controller.status;
return status == AnimationStatus.completed ||
status == AnimationStatus.forward;
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
elevation: 0.0,
backgroundColor: Theme.of(context).accentColor,
title: Text('Home'),
leading: IconButton(
onPressed: () {
controller.fling(velocity: isPanelVisible ? -1.0 : 1.0);
},
icon: AnimatedIcon(
progress: controller.view,
icon: AnimatedIcons.close_menu,
),
),
),
body: PanelClass(controller: controller,frontLayer: 'Need to get reference here',),
);
}
}
I am trying to create an application that manages Views with Tab.
The navigation bar and the tab bar are displayed in the application, and the content of the content to be displayed is managed by List.
This is the class that manages the content of Tab.
class TabItem {
TabItem({
Widget view,
Widget icon,
Widget activeIcon,
String title,
TickerProvider vsync,
}) : _view = view,
_title = title,
item = BottomNavigationBarItem(
icon: icon,
// activeIcon: activeIcon,
title: Text(title),
),
controller = AnimationController(
duration: kThemeAnimationDuration,
vsync: vsync,
) {
_animation = CurvedAnimation(
parent: controller,
curve: const Interval(0.5, 1.0, curve: Curves.fastOutSlowIn),
);
}
final Widget _view;
final String _title;
final BottomNavigationBarItem item;
final AnimationController controller;
CurvedAnimation _animation;
FadeTransition transition(
BottomNavigationBarType type, BuildContext context) {
~~~~~~~~~~~~
//transition animation setting method
~~~~~~~~~~~~
}
}
This is main.dart
Depending on the action of the user, the content in the List is displayed.
void main() {
SystemChrome
.setPreferredOrientations([DeviceOrientation.portraitUp]).then((_) {
runApp(MyApp());
});
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
home: RootTabView(),
);
}
}
class RootTabView extends StatefulWidget {
#override
_RootTabViewState createState() => _RootTabViewState();
}
class _RootTabViewState extends State<RootTabView>
with TickerProviderStateMixin {
int _currentIndex = 0;
BottomNavigationBarType _type = BottomNavigationBarType.fixed;
List<TabItem> _tabItems;
#override
void initState() {
super.initState();
// tabitems of view
_tabItems = <TabItem>[
TabItem(
view: hoge0View(),
icon: const Icon(Icons.accessibility),
title: 'hoge0',
vsync: this,
),
TabItem(
view: hoge1View(),
icon: const Icon(Icons.accessibility),
title: 'hoge1',
vsync: this,
),
TabItem(
view: hoge2View(),
icon: const Icon(Icons.accessibility),
title: 'hoge2',
vsync: this,
),
TabItem(
view: hoge3View(),
icon: const Icon(Icons.accessibility),
title: 'hoge3',
vsync: this,
)
];
for (TabItem tabItem in _tabItems) {
tabItem.controller.addListener(_rebuild);
}
_tabItems[_currentIndex].controller.value = 1.0;
}
#override
void dispose() {
for (TabItem tabItem in _tabItems) {
tabItem.controller.dispose();
}
super.dispose();
}
void _rebuild() {
if (this.mounted) {
setState(() {
// Rebuild in order to animate views.
});
}
}
Widget _buildTransitionsStack() {
final List<FadeTransition> transitions = <FadeTransition>[];
for (TabItem tabItem in _tabItems) {
transitions.add(tabItem.transition(_type, context));
}
// We want to have the newly animating (fading in) views on top.
transitions.sort((FadeTransition a, FadeTransition b) {
final Animation<double> aAnimation = a.opacity;
final Animation<double> bAnimation = b.opacity;
final double aValue = aAnimation.value;
final double bValue = bAnimation.value;
return aValue.compareTo(bValue);
});
return Stack(children: transitions);
}
#override
Widget build(BuildContext context) {
final BottomNavigationBar botNavBar = BottomNavigationBar(
items: _tabItems.map((TabItem tabItem) => tabItem.item).toList(),
currentIndex: _currentIndex,
type: _type,
onTap: (int index) {
setState(() {
var currentView = _tabItems[_currentIndex];
currentView.controller.reverse();
_currentIndex = index;
currentView = _tabItems[_currentIndex];
currentView.controller.forward();
});
},
);
return Scaffold(
appBar: AppBar(
title: Text(_tabItems[_currentIndex]._title),
actions: actions,
),
body: Center(child: _buildTransitionsStack()),
bottomNavigationBar: botNavBar,
);
}
}
Well, here I rewrite a part of the build method.
I placed a button on AppBar when index: 1 and wanted to tap and call the method of the view instance being displayed.
var actions = <Widget>[];
if (_currentIndex == 1) {
var editButton = FlatButton(
child: Text('Edit'),
onPressed: () {
// I want to call hoge1View method...
});
actions.add(editButton);
}
return Scaffold(
appBar: AppBar(
title: Text(_tabItems[_currentIndex]._title),
actions: actions,
),
body: Center(child: _buildTransitionsStack()),
bottomNavigationBar: botNavBar,
);
The implementation of Hoge1View is as follows.
I want to call showEditButtonDialog () when I tap the editButton.
class Hoge1View extends StatefulWidget {
#override
_Hoge1ViewState createState() => _Hoge1ViewState();
}
class _Hoge1ViewState extends State<DiaryView> {
Future showEditButtonDialog() async {
return showDialog(
context: context,
builder: (BuildContext context) {
return AlertDialog(
title: Text('test'),
content: SingleChildScrollView(
child: ListBody(
children: <Widget>[Text('test message')],
),
),
actions: <Widget>[
FlatButton(
child: Text('action1'),
onPressed: () {
print('pressed');
},
)
],
);
});
}
#override
Widget build(BuildContext context) {
return ListView(
children: <Widget>[
Container(),
Container(),
Container(),
]
);
}
}
I do not know how to get Hoge1View instances currently displayed on onPress.