How would I create a circular ListView in Flutter?
I want something that allows me to have a list of widgets rotate around an origin.
Something similar to this:
Any help would be appreciated.
Circular List View Demo. Which Is helpful for You May be.
Main.dart
import 'package:master/numbers_list.dart';
import 'package:master/radial_list.dart';
import 'package:meta/meta.dart';
import 'package:flutter/material.dart';
void main() {
runApp(MyHomePage());
}
class MyHomePage extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
theme: new ThemeData(
accentColor: Colors.blue,
),
home: HomePage(),
);
}
}
class HomePage extends StatefulWidget {
RadialListViewModel radialList;
HomePage({
#required this.radialList
});
#override
HomePageState createState() {
return new HomePageState();
}
}
class HomePageState extends State<HomePage> {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: new AppBar(
title: Text("My Home Page"),
),
body: Stack(
children: <Widget>[
RadialList(
radialList: radialNumbers,
radius: 150.00,
),
],
)
);
}
}
numbers_list.dart
import 'package:master/radial_list.dart';
final RadialListViewModel radialNumbers = new RadialListViewModel(
items: [
new RadialListItemViewModel(
number: 1,
isSelected: true,
),
new RadialListItemViewModel(
number: 2,
isSelected: false,
),
new RadialListItemViewModel(
number: 3,
isSelected: false,
),new RadialListItemViewModel(
number: 4,
isSelected: false,
),
new RadialListItemViewModel(
number: 5,
isSelected: false,
),
new RadialListItemViewModel(
number: 6,
isSelected: false,
),
new RadialListItemViewModel(
number: 7,
isSelected: false,
),
new RadialListItemViewModel(
number: 8,
isSelected: false,
),
new RadialListItemViewModel(
number: 9,
isSelected: false,
),
new RadialListItemViewModel(
number: 10,
isSelected: false,
),
new RadialListItemViewModel(
number: 11,
isSelected: false,
),new RadialListItemViewModel(
number: 12,
isSelected: false,
),
]
);
radial_list.dart
import 'package:flutter/material.dart';
import 'dart:math';
import 'package:master/radial_position.dart';
class RadialList extends StatefulWidget {
final RadialListViewModel radialList;
final double radius;
RadialList({
this.radialList,
this.radius,
});
List<Widget> _radialListItems(){
final double firstItemAngle = pi;
final double lastItemAngle = pi;
final double angleDiff = (firstItemAngle + lastItemAngle) / (radialList.items.length);
double currentAngle = firstItemAngle;
return radialList.items.map((RadialListItemViewModel viewModel){
final listItem = _radialListItem(viewModel,currentAngle);
currentAngle += angleDiff;
return listItem;
}).toList();
}
Widget _radialListItem(RadialListItemViewModel viewModel, double angle){
return Transform(
transform: new Matrix4.translationValues(
180.0,
250.0,
0.0
),
child: RadialPosition(
radius: radius,
angle: angle,
child: new RadialListItem(
listItem: viewModel,
),
),
);
}
#override
RadialListState createState() {
return new RadialListState();
}
}
class RadialListState extends State<RadialList> {
#override
Widget build(BuildContext context) {
return new Stack(
children: widget._radialListItems(),
);
}
}
class RadialListItem extends StatefulWidget {
final RadialListItemViewModel listItem;
RadialListItem({
this.listItem
});
#override
RadialListItemState createState() {
return new RadialListItemState();
}
}
class RadialListItemState extends State<RadialListItem> {
#override
Widget build(BuildContext context) {
return Transform(
transform: new Matrix4.translationValues(-30.0, -30.0, 0.0),
child: Container(
width: 60.0,
height: 60.0,
decoration: new BoxDecoration(
shape: BoxShape.circle,
color: Colors.deepPurpleAccent,
border: new Border.all(
color: Colors.red,
width: 2.0
)
),
child: Padding(
padding: const EdgeInsets.all(0.0),
child: OutlineButton(
shape: new RoundedRectangleBorder(borderRadius: new BorderRadius.circular(60.0)),
color: Colors.transparent,
onPressed: () {
setState(() {
widget.listItem.isSelected = true;
//widget.listItem.number = widget.listItem.number + 1;
});
},
child: new Text(
widget.listItem.number.toString(),
style: new TextStyle(
color: widget.listItem.isSelected ? Colors.red : Colors.yellow,
fontSize: 20.0
),
),
),
),
),
);
}
}
class RadialListViewModel{
final List<RadialListItemViewModel> items;
RadialListViewModel({
this.items = const [],
});
}
class RadialListItemViewModel{
int number;
bool isSelected;
RadialListItemViewModel({
this.isSelected=false,
this.number,
});
}
radial_position.dart
import 'package:flutter/material.dart';
import 'dart:math';
class RadialPosition extends StatefulWidget {
final double radius;
final double angle;
final Widget child;
RadialPosition({
this.angle,
this.child,
this.radius,
});
#override
RadialPositionState createState() {
return new RadialPositionState();
}
}
class RadialPositionState extends State<RadialPosition> {
#override
Widget build(BuildContext context) {
final x = cos(widget.angle) * widget.radius;
final y = sin(widget.angle) * widget.radius;
return Transform(
transform: new Matrix4.translationValues(x, y, 0.0),
child: widget.child,
);
}
}
A simpler way to do it is to use this package
circular_motion
Here's an example using that package
CircularMotion.builder(
itemCount: 18,
centerWidget: Text('Center'),
builder: (context, index) {
return Text('$index');
}
)
Related
I have a main widget called DashboardWidget. Inside it, I have a Scaffold with BottomNavigationBar and a FloatingActionButton:
Now, I want to make a widget that would be dragged from the bottom by:
Swiping up with the finger.
Pressing on FloatingActionButton.
In other words, I want to expand the BottomNavigationBar.
Here's a design concept in case I was unclear.
The problem is, I'm not sure where to start to implement that. I've thought about removing the BottomNavigationBar and create a custom widget that can be expanded, but I'm not sure if it's possible either.
Output:
I used a different approach and did it without AnimationController, GlobalKey etc, the logic code is very short (_handleClick).
I only used 4 variables, simple and short!
void main() => runApp(MaterialApp(home: HomePage()));
class HomePage extends StatefulWidget {
#override
_HomePageState createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> {
static double _minHeight = 80, _maxHeight = 600;
Offset _offset = Offset(0, _minHeight);
bool _isOpen = false;
#override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Color(0xFFF6F6F6),
appBar: AppBar(backgroundColor: Color(0xFFF6F6F6), elevation: 0),
body: Stack(
alignment: Alignment.bottomCenter,
children: <Widget>[
Align(
alignment: Alignment.topLeft,
child: FlatButton(
onPressed: _handleClick,
splashColor: Colors.transparent,
textColor: Colors.grey,
child: Text(_isOpen ? "Back" : ""),
),
),
Align(child: FlutterLogo(size: 300)),
GestureDetector(
onPanUpdate: (details) {
_offset = Offset(0, _offset.dy - details.delta.dy);
if (_offset.dy < _HomePageState._minHeight) {
_offset = Offset(0, _HomePageState._minHeight);
_isOpen = false;
} else if (_offset.dy > _HomePageState._maxHeight) {
_offset = Offset(0, _HomePageState._maxHeight);
_isOpen = true;
}
setState(() {});
},
child: AnimatedContainer(
duration: Duration.zero,
curve: Curves.easeOut,
height: _offset.dy,
alignment: Alignment.center,
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.only(
topLeft: Radius.circular(30),
topRight: Radius.circular(30),
),
boxShadow: [BoxShadow(color: Colors.grey.withOpacity(0.5), spreadRadius: 5, blurRadius: 10)]),
child: Text("This is my Bottom sheet"),
),
),
Positioned(
bottom: 2 * _HomePageState._minHeight - _offset.dy - 28, // 56 is the height of FAB so we use here half of it.
child: FloatingActionButton(
child: Icon(_isOpen ? Icons.keyboard_arrow_down : Icons.add),
onPressed: _handleClick,
),
),
],
),
);
}
// first it opens the sheet and when called again it closes.
void _handleClick() {
_isOpen = !_isOpen;
Timer.periodic(Duration(milliseconds: 5), (timer) {
if (_isOpen) {
double value = _offset.dy + 10; // we increment the height of the Container by 10 every 5ms
_offset = Offset(0, value);
if (_offset.dy > _maxHeight) {
_offset = Offset(0, _maxHeight); // makes sure it does't go above maxHeight
timer.cancel();
}
} else {
double value = _offset.dy - 10; // we decrement the height by 10 here
_offset = Offset(0, value);
if (_offset.dy < _minHeight) {
_offset = Offset(0, _minHeight); // makes sure it doesn't go beyond minHeight
timer.cancel();
}
}
setState(() {});
});
}
}
You can use the BottomSheet class.
Here is a Medium-tutorial for using that, here is a youtube-tutorial using it and here is the documentation for the class.
The only difference from the tutorials is that you have to add an extra call method for showBottomSheet from your FloatingActionButton when it is touched.
Bonus: here is the Material Design page on how to use it.
You can check this code, it is a complete example of how to start implementing this kind of UI, take it with a grain of salt.
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:flutter/scheduler.dart';
import 'package:rxdart/rxdart.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Orination Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: MyHomePage(),
);
}
}
class MyHomePage extends StatefulWidget {
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> with TickerProviderStateMixin {
bool _isOpen;
double _dragStart;
double _hieght;
double _maxHight;
double _currentPosition;
GlobalKey _cardKey;
AnimationController _controller;
Animation<double> _cardAnimation;
#override
void initState() {
_isOpen = false;
_hieght = 50.0;
_cardKey = GlobalKey();
_controller =
AnimationController(vsync: this, duration: Duration(milliseconds: 700));
_cardAnimation = Tween(begin: _hieght, end: _maxHight).animate(
CurvedAnimation(parent: _controller, curve: Curves.easeInOut)
);
_controller.addListener(() {
setState(() {
_hieght = _cardAnimation.value;
});
});
super.initState();
}
#override
void dispose() {
_controller.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
elevation: 0.0,
backgroundColor: Colors.transparent,
titleSpacing: 0.0,
title: _isOpen
? MaterialButton(
child: Text(
"Back",
style: TextStyle(color: Colors.red),
),
onPressed: () {
_isOpen = false;
_cardAnimation = Tween(begin: _hieght, end: 50.0).animate(
CurvedAnimation(parent: _controller, curve: Curves.easeInOut)
);
_controller.forward(from: 0.0);
},
)
: Text(""),
),
floatingActionButton: FloatingActionButton(
child: Icon(Icons.keyboard_arrow_up),
onPressed: () {
final RenderBox renderBoxCard = _cardKey.currentContext
.findRenderObject();
_maxHight = renderBoxCard.size.height;
_cardAnimation = Tween(begin: _hieght, end: _maxHight).animate(
CurvedAnimation(parent: _controller, curve: Curves.easeInOut)
);
_controller.forward(from: 0.0);
_isOpen = true;
}),
body: Stack(
key: _cardKey,
alignment: Alignment.bottomCenter,
children: <Widget>[
Container(
width: double.infinity,
height: double.infinity,
color: Colors.black12,
),
GestureDetector(
onPanStart: _onPanStart,
onPanUpdate: _onPanUpdate,
onPanEnd: _onPanEnd,
child:Material(
borderRadius: BorderRadius.only(
topRight: Radius.circular(16.0),
topLeft: Radius.circular(16.0),
),
elevation: 60.0,
color: Colors.white,
// shadowColor: Colors.,
child: Container(
height: _hieght,
child: Center(
child: Text("Hello, You can drag up"),
),
),
),
),
],
),
);
}
void _onPanStart(DragStartDetails details) {
_dragStart = details.globalPosition.dy;
_currentPosition = _hieght;
}
void _onPanUpdate(DragUpdateDetails details) {
final RenderBox renderBoxCard = _cardKey.currentContext.findRenderObject();
_maxHight = renderBoxCard.size.height;
final hieght = _currentPosition - details.globalPosition.dy + _dragStart;
print(
"_currentPosition = $_currentPosition _hieght = $_hieght hieght = $hieght");
if (hieght <= _maxHight && hieght >= 50.0) {
setState(() {
_hieght = _currentPosition - details.globalPosition.dy + _dragStart;
});
}
}
void _onPanEnd(DragEndDetails details) {
_currentPosition = _hieght;
if (_hieght <= 60.0) {
setState(() {
_isOpen = false;
});
} else {
setState(() {
_isOpen = true;
});
}
}
}
Edit: I modified the code by using Material Widget instead of A container with shadow for better performance,If you have any issue, please let me know .
How do i create a gridview-layout with multi-select feature in Flutter, like android photo app? I was looking for an existing widget but couldn't find one.
What I have at the moment: a gridview-layout with n rows and 2 columns. The cells contain a GridTile-widget with some information and a header text. Now i want to have a functionality like in android photo app, after a long press on one of these tiles, a check-circle appears on the left top corner for all items.
Do i have to build this on my own, or is there an existing Flutter-widget which i didn't find so far?
I also don't know an existing widget, but perhaps this will help you:
import 'package:flutter/material.dart';
import 'package:flutter_staggered_grid_view/flutter_staggered_grid_view.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
debugShowCheckedModeBanner: false,
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
List<String> _imageList = List();
List<int> _selectedIndexList = List();
bool _selectionMode = false;
#override
Widget build(BuildContext context) {
List<Widget> _buttons = List();
if (_selectionMode) {
_buttons.add(IconButton(
icon: Icon(Icons.delete),
onPressed: () {
_selectedIndexList.sort();
print('Delete ${_selectedIndexList.length} items! Index: ${_selectedIndexList.toString()}');
}));
}
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
actions: _buttons,
),
body: _createBody(),
);
}
#override
void initState() {
super.initState();
_imageList.add('https://picsum.photos/800/600/?image=280');
_imageList.add('https://picsum.photos/800/600/?image=281');
_imageList.add('https://picsum.photos/800/600/?image=282');
_imageList.add('https://picsum.photos/800/600/?image=283');
_imageList.add('https://picsum.photos/800/600/?image=284');
}
void _changeSelection({bool enable, int index}) {
_selectionMode = enable;
_selectedIndexList.add(index);
if (index == -1) {
_selectedIndexList.clear();
}
}
Widget _createBody() {
return StaggeredGridView.countBuilder(
crossAxisCount: 2,
mainAxisSpacing: 4.0,
crossAxisSpacing: 4.0,
primary: false,
itemCount: _imageList.length,
itemBuilder: (BuildContext context, int index) {
return getGridTile(index);
},
staggeredTileBuilder: (int index) => StaggeredTile.count(1, 1),
padding: const EdgeInsets.all(4.0),
);
}
GridTile getGridTile(int index) {
if (_selectionMode) {
return GridTile(
header: GridTileBar(
leading: Icon(
_selectedIndexList.contains(index) ? Icons.check_circle_outline : Icons.radio_button_unchecked,
color: _selectedIndexList.contains(index) ? Colors.green : Colors.black,
),
),
child: GestureDetector(
child: Container(
decoration: BoxDecoration(border: Border.all(color: Colors.blue[50], width: 30.0)),
child: Image.network(
_imageList[index],
fit: BoxFit.cover,
),
),
onLongPress: () {
setState(() {
_changeSelection(enable: false, index: -1);
});
},
onTap: () {
setState(() {
if (_selectedIndexList.contains(index)) {
_selectedIndexList.remove(index);
} else {
_selectedIndexList.add(index);
}
});
},
));
} else {
return GridTile(
child: InkResponse(
child: Image.network(
_imageList[index],
fit: BoxFit.cover,
),
onLongPress: () {
setState(() {
_changeSelection(enable: true, index: index);
});
},
),
);
}
}
}
I used staggered grid view to show a grid and grid tiles with a header to have a space for the selection icon. Hope that helps!
This is a plugin from flutter package You can use this
https://pub.dev/packages/drag_select_grid_view
I use flutter_local_notifications package to set notifications. I have an expandable list and every option of a title has a star icon. When I press one of white star icons, its color changes and set a notification("_showNotification" method).
In case that I press two or more stars, my app shows only last notification, but I want to show all of them. How can I do this?
This is whole code:
import 'package:flutter/material.dart';
import 'package:flutter_local_notifications/flutter_local_notifications.dart';
void main() {
runApp(new MaterialApp(home: new Home()));
}
class Home extends StatelessWidget {
#override
Widget build(BuildContext context) {
return new Scaffold(
appBar: new AppBar(
title: new Text("Expandable List"),
),
body: new ListView.builder(
itemBuilder: (BuildContext context, int index) {
return new ExpandableListView(ind: index, title: broadcast[index].title);
},
itemCount: 2,
),
);
}
}
class ExpandableListView extends StatefulWidget {
final String title;
final int ind;
const ExpandableListView({this.ind, this.title});
#override
_ExpandableListViewState createState() => new _ExpandableListViewState();
}
class _ExpandableListViewState extends State<ExpandableListView> {
bool expandFlag = false;
Color _iconColor = Colors.white;
#override
Widget build(BuildContext context) {
return new Container(
margin: new EdgeInsets.symmetric(vertical: 1.0),
child: new Column(
children: <Widget>[
new Container(
padding: new EdgeInsets.symmetric(horizontal: 5.0),
child: new Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[
new IconButton(
icon: new Container(
height: 50.0,
width: 50.0,
decoration: new BoxDecoration(
color: Colors.orange,
shape: BoxShape.circle,
),
child: new Center(
child: new Icon(
expandFlag ? Icons.keyboard_arrow_up : Icons.keyboard_arrow_down,
),
),
),
onPressed: () {
setState(() {
expandFlag = !expandFlag;
});
}),
new Text(
widget.title,
)
],
),
),
new ExpandableContainer(
expanded: expandFlag,
expandedHeight: 60.0 * 3,
child: new ListView.builder(
itemBuilder: (BuildContext context, int index) {
return StatefulListTile(
title: broadcast[widget.ind].contents[index],
second: broadcast[widget.ind].time[index],
);
},
itemCount: broadcast[widget.ind].contents.length,
))
],
),
);
}
}
class ExpandableContainer extends StatelessWidget {
final bool expanded;
final double expandedHeight;
final Widget child;
ExpandableContainer({
#required this.child,
this.expandedHeight,
this.expanded = true,
});
#override
Widget build(BuildContext context) {
double screenWidth = MediaQuery.of(context).size.width;
return new AnimatedContainer(
duration: new Duration(milliseconds: 100),
curve: Curves.easeInOut,
width: screenWidth,
height: expanded ? expandedHeight : 0.0,
child: new Container(
child: child,
decoration: new BoxDecoration(border: new Border.all(width: 1.0)),
),
);
}
}
class StatefulListTile extends StatefulWidget {
const StatefulListTile({this.title, this.second});
final String title;
final int second;
#override
_StatefulListTileState createState() => _StatefulListTileState();
}
class _StatefulListTileState extends State<StatefulListTile> {
Color _iconColor = Colors.white;
FlutterLocalNotificationsPlugin flutterLocalNotificationsPlugin;
#override
initState() {
super.initState();
var initializationSettingsAndroid =
new AndroidInitializationSettings('#mipmap/ic_launcher');
var initializationSettingsIOS = new IOSInitializationSettings();
var initializationSettings = new InitializationSettings(
initializationSettingsAndroid, initializationSettingsIOS);
flutterLocalNotificationsPlugin = new FlutterLocalNotificationsPlugin();
flutterLocalNotificationsPlugin.initialize(initializationSettings,
onSelectNotification: onSelectNotification);
}
#override
Widget build(BuildContext context) {
return new Container(
decoration: new BoxDecoration(
border: new Border.all(width: 1.0, color: Colors.grey),
color: Colors.blue,),
child: new ListTile(
title: new Text(widget.title),
leading: new IconButton(
icon: Icon(Icons.star, color: _iconColor),
onPressed: () {
setState(() {
if (_iconColor == Colors.white) {
_iconColor = Colors.yellow;
_showNotification(widget.second);
} else {
_iconColor = Colors.white;
}
});
},
),
),
);
}
Future onSelectNotification(String payload) async {
showDialog(
context: context,
builder: (_) {
return new AlertDialog(
title: Text("PayLoad"),
content: Text("Payload : $payload"),
);
},
);
}
Future _showNotification(second) async {
var time = new Time(10, 18, second);
var androidPlatformChannelSpecifics =
new AndroidNotificationDetails('show weekly channel id',
'show weekly channel name', 'show weekly description');
var iOSPlatformChannelSpecifics =
new IOSNotificationDetails();
var platformChannelSpecifics = new NotificationDetails(
androidPlatformChannelSpecifics, iOSPlatformChannelSpecifics);
await flutterLocalNotificationsPlugin.showWeeklyAtDayAndTime(
0,
widget.title,
'',
Day.Monday,
time,
platformChannelSpecifics,
payload: widget.title);
}
}
class Broadcast {
final String title;
List<String> contents;
List<int> time = [];
Broadcast(this.title, this.contents, this.time);
}
List<Broadcast> broadcast = [
new Broadcast(
'A',
['1', '2', '3'],
[5, 10, 15],
),
new Broadcast(
'B',
['4', '5'],
[20, 25],
),
];
You need to change channelID for each notification you don't want to stack.
flutterLocalNotificationsPlugin.showWeeklyAtDayAndTime(your_channelID_goes_here,
widget.title,
'',
Day.Monday,
time,
platformChannelSpecifics,
payload: widget.title);
Just change the channelID. You have put it as 0. change it is as 0,1,2,3 then you will get multiple notifications
flutterLocalNotificationsPlugin.showWeeklyAtDayAndTime(
"change Here channelID as 0 or 1 or 2 or 3",
widget.title,
'',
Day.Monday,
time,
platformChannelSpecifics,
payload: widget.title);
Want to clone playstore front page appbar scrolling functionality in flutter. Playstore Appbar I'm trying to make a screen which contains static Tabs under SliverAppBar bottom property. I need to create dynamic Tabs and TabBarViews whenever i clicked onto any parent static tab. I did it successfully but i'm facing some problems.
When i click onto any parent tab, I would try to initialize the tabController but the currentIndex remains the same as it was into the previous parent tab.First Tab Second Tab
Each tab body must save its scrolling position.
This my screen code.
class DynamicTabContent {
IconData icon;
String tooTip;
DynamicTabContent.name(this.icon, this.tooTip);
}
int currentTabBlue = 0;
class TestAppHomePage extends StatefulWidget {
#override
TestAppHomePageState createState() => new TestAppHomePageState();
}
class TestAppHomePageState extends State<TestAppHomePage>
with TickerProviderStateMixin {
List<DynamicTabContent> myList = new List();
ScrollController _scrollController = new ScrollController();
TabController _tabControllerBlue;
TabController _tabController;
handleTabChange() {
currentTabBlue = _tabControllerBlue.index;
print("CurrentTab = " + currentTabBlue.toString());
if (_tabControllerBlue.index == 0) {
setState(() {
myList.clear();
myList.add(new DynamicTabContent.name(Icons.favorite, "Favorited"));
myList
.add(new DynamicTabContent.name(Icons.local_pizza, "local pizza"));
this._tabController = new TabController(initialIndex: 0, length: 15, vsync: this);
});
} else if (_tabControllerBlue.index == 1) {
setState(() {
myList.clear();
myList.add(new DynamicTabContent.name(Icons.favorite, "Favorited"));
myList
.add(new DynamicTabContent.name(Icons.local_pizza, "local pizza"));
myList
.add(new DynamicTabContent.name(Icons.local_pizza, "local pizza"));
this._tabController = new TabController(initialIndex: 0, length: 15, vsync: this);
});
} else if (_tabControllerBlue.index == 2) {
setState(() {
myList.clear();
myList.add(new DynamicTabContent.name(Icons.favorite, "Favorited"));
myList.add(new DynamicTabContent.name(Icons.favorite, "Favorited"));
myList
.add(new DynamicTabContent.name(Icons.local_pizza, "local pizza"));
myList
.add(new DynamicTabContent.name(Icons.local_pizza, "local pizza"));
myList
.add(new DynamicTabContent.name(Icons.local_pizza, "local pizza"));
this._tabController = new TabController(initialIndex: 0, length: 15, vsync: this);
});
}
}
#override
void initState() {
print("initState = TestAppHomePage");
myList.add(new DynamicTabContent.name(Icons.favorite, "Favorited"));
myList.add(new DynamicTabContent.name(Icons.local_pizza, "local pizza"));
_tabControllerBlue =
new TabController(initialIndex: 0, length: 3, vsync: this);
_tabControllerBlue.addListener(handleTabChange);
_tabController =
new TabController(initialIndex: 0, length: myList.length, vsync: this);
}
#override
void dispose() {
print("dispose");
// _tabController.removeListener(handleTabChange);
// _tabController.dispose();
super.dispose();
}
Future<void> executeAfterBuild() async {
print("Build: Called Back");
}
#override
Widget build(BuildContext context) {
executeAfterBuild();
return new Scaffold(
body: new NestedScrollView(
controller: _scrollController,
headerSliverBuilder: (BuildContext context, bool innerBoxIsScrolled) {
return <Widget>[
SliverAppBar(
leading: IconButton(
icon: Icon(
Icons.arrow_back,
color: Colors.white,
),
onPressed: null,
),
title: Text('Kitchen'),
floating: true,
pinned: true,
bottom: TabBar(
controller: _tabControllerBlue,
tabs: [
Tab(icon: Icon(Icons.lightbulb_outline), text: "Tab 1"),
Tab(icon: Icon(Icons.lightbulb_outline), text: "Tab 2"),
Tab(icon: Icon(Icons.lightbulb_outline), text: "Tab 3"),
],
),
),
new SliverPersistentHeader(
pinned: true,
delegate: TestTabBarDelegate(controller: _tabController),
),
];
},
body: new TestHomePageBody(
tabController: _tabController,
scrollController: _scrollController,
myList: myList,
),
),
);
}
}
class TestHomePageBody extends StatefulWidget {
TestHomePageBody({this.tabController, this.scrollController, this.myList});
final TabController tabController;
final ScrollController scrollController;
final List<DynamicTabContent> myList;
State<StatefulWidget> createState() {
return TestHomePageBodyState();
}
}
class TestHomePageBodyState extends State<TestHomePageBody> {
Key _key = new PageStorageKey({});
bool _innerListIsScrolled = false;
void _updateScrollPosition() {
if (!_innerListIsScrolled &&
widget.scrollController.position.extentAfter == 0.0) {
setState(() {
_innerListIsScrolled = true;
print("_innerListIsScrolled = true");
});
} else if (_innerListIsScrolled &&
widget.scrollController.position.extentAfter > 0.0) {
setState(() {
_innerListIsScrolled = false;
print("_innerListIsScrolled = false");
// Reset scroll positions of the TabBarView pages
_key = new PageStorageKey({});
});
}
}
#override
void initState() {
widget.scrollController.addListener(_updateScrollPosition);
print("initState = TestHomePageBodyState");
super.initState();
}
#override
void dispose() {
widget.scrollController.removeListener(_updateScrollPosition);
super.dispose();
}
#override
Widget build(BuildContext context) {
return new TabBarView(
controller: widget.tabController,
key: _key,
children: widget.myList.isEmpty
? <Widget>[]
: widget.myList.map(
(dynamicContent) {
return new Card(
child: new Column(
children: <Widget>[
new Container(
height: 450.0,
width: 300.0,
child: new IconButton(
icon: new Icon(dynamicContent.icon, size: 100.0),
tooltip: dynamicContent.tooTip,
onPressed: null,
),
),
Text(dynamicContent.tooTip),
],
),
);
},
).toList(),
);
}
}
class TestTabBarDelegate extends SliverPersistentHeaderDelegate {
TestTabBarDelegate({this.controller});
final TabController controller;
#override
double get minExtent => kToolbarHeight;
#override
double get maxExtent => kToolbarHeight;
#override
Widget build(
BuildContext context, double shrinkOffset, bool overlapsContent) {
return new Container(
color: Theme.of(context).cardColor,
height: kToolbarHeight,
child: new TabBar(
controller: controller,
isScrollable: true,
labelColor: Theme.of(context).accentColor,
indicatorSize: TabBarIndicatorSize.label,
key: new PageStorageKey<Type>(TabBar),
indicatorColor: Theme.of(context).accentColor,
tabs: List<Widget>.generate(controller.length, (int index) {
print(controller.length);
return new Tab(text: "Excluded Discounted Deals");
}),
),
);
}
#override
bool shouldRebuild(covariant TestTabBarDelegate oldDelegate) {
return oldDelegate.controller != controller;
}
}
One way to solve it is using PageStorage. eg.
In the onTap method you will need to write the state, and when building the widget you will need to read the state, something as follow:
// Reading state:
var tabInternalIndex = PageStorage.of(context).readState(context, identifier: ValueKey('tab3'));
_currentTab = tabInternalIndex == null ? _currentTab : tabInternalIndex;
// Writing State
onTap: (int index) {
setState(() {
_currentTab = index;
PageStorage.of(context).writeState(context, index,
identifier: ValueKey('tab3'));
});
}
Please notice that identifier for every main tab has to be different.
Complete example:
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
#override
Widget build(BuildContext context) {
return DefaultTabController(
length: 3,
initialIndex: 1,
child: Scaffold(
appBar: AppBar(
bottom: TabBar(
tabs: [
Tab(icon: Icon(Icons.mail)),
Tab(icon: Icon(Icons.contacts)),
Tab(icon: Icon(Icons.info)),
],
),
title: Text('Sample tabs'),
),
body: TabBarView(children: <Widget>[
SubTabs1(),
SubTabs2(),
SubTabs3()
]),
),
);
}
}
class SubTabs1 extends StatefulWidget {
#override
_SubTabs1State createState() => _SubTabs1State();
}
class _SubTabs1State extends State<SubTabs1> {
int _currentTab = 0;
#override
Widget build(BuildContext context) {
var tabInternalIndex = PageStorage.of(context)
.readState(context, identifier: ValueKey('tab1'));
_currentTab = tabInternalIndex == null ? _currentTab : tabInternalIndex;
return DefaultTabController(
initialIndex: _currentTab,
length: 3,
child: Scaffold(
appBar: AppBar(
bottom: TabBar(
onTap: (int index) {
setState(() {
_currentTab = index;
PageStorage.of(context).writeState(context, index,
identifier: ValueKey('tab1'));
});
},
tabs: [
Tab(icon: Icon(Icons.delete)),
Tab(icon: Icon(Icons.delete_forever)),
Tab(icon: Icon(Icons.delete_outline)),
],
),
),
body: TabBarView(
children: <Widget>[
Container(
color: Colors.green[100],
child: Text('Child 5'),
),
Container(
color: Colors.green[300],
child: Text('Child 6'),
),
Container(
color: Colors.green[600],
child: Text('Child 7'),
)
],
)));
}
}
class SubTabs2 extends StatefulWidget {
#override
_SubTabs2State createState() => _SubTabs2State();
}
class _SubTabs2State extends State<SubTabs2> {
int _currentTab = 0;
#override
Widget build(BuildContext context) {
var tabInternalIndex = PageStorage.of(context)
.readState(context, identifier: ValueKey('tab2'));
_currentTab = tabInternalIndex == null ? _currentTab : tabInternalIndex;
return DefaultTabController(
initialIndex: _currentTab,
length: 3,
child: Scaffold(
appBar: AppBar(
bottom: TabBar(
onTap: (int index) {
setState(() {
_currentTab = index;
PageStorage.of(context).writeState(context, index,
identifier: ValueKey('tab2'));
});
},
tabs: [
Tab(icon: Icon(Icons.alarm_add)),
Tab(icon: Icon(Icons.alarm_off)),
Tab(icon: Icon(Icons.alarm_on)),
],
),
),
body: TabBarView(
children: <Widget>[
Container(
color: Colors.yellow[100],
child: Text('Child 5'),
),
Container(
color: Colors.yellow[300],
child: Text('Child 6'),
),
Container(
color: Colors.yellow[600],
child: Text('Child 7'),
)
],
)));
}
}
class SubTabs3 extends StatefulWidget {
#override
_SubTabs3State createState() => _SubTabs3State();
}
class _SubTabs3State extends State<SubTabs3> {
int _currentTab = 0;
#override
Widget build(BuildContext context) {
// Reading state:
var tabInternalIndex = PageStorage.of(context).readState(context, identifier: ValueKey('tab3'));
_currentTab = tabInternalIndex == null ? _currentTab : tabInternalIndex;
return DefaultTabController(
initialIndex: _currentTab,
length: 3,
child: Scaffold(
appBar: AppBar(
bottom: TabBar(
onTap: (int index) {
setState(() {
_currentTab = index;
PageStorage.of(context).writeState(context, index,
identifier: ValueKey('tab3'));
});
},
tabs: [
Tab(icon: Icon(Icons.ac_unit)),
Tab(icon: Icon(Icons.accessible)),
Tab(icon: Icon(Icons.airport_shuttle)),
],
),
),
body: TabBarView(
children: <Widget>[
Container(
color: Colors.pink[100],
child: Text('Child 1'),
),
Container(
color: Colors.pink[300],
child: Text('Child 2'),
),
Container(
color: Colors.pink[600],
child: Text('Child 3'),
)
],
)));
}
}
Hope this help.
I have the following widget which will fit into a different parent widget into its body section. So in the parent widget I call this widget as below
body: MapsDemo(),. The issue now is that at this section I run an interval where every 30 seconds I want to call api to get all the latest markers.
Currently I print the count down as this codes print("${30 - timer.tick * 1}"); My issue is very simple I have 3 three floating action button and I have given them their id. Thus on each count down for the last floatingaction button which is btn3.text I am trying to set its value as ${30 - timer.tick * 1} but it does not work on this basis. How can I update the count down in the button?
class MapsDemo extends StatefulWidget {
#override
State createState() => MapsDemoState();
}
class MapsDemoState extends State<MapsDemo> {
GoogleMapController mapController;
#override
void initState() {
super.initState();
startTimer(30);
}
startTimer(int index) async {
print("Index30");
print(index);
new Timer.periodic(new Duration(seconds: 1), (timer) {
if ((30 / 1) >= timer.tick) {
print("${30 - timer.tick * 1}");
btn3.text = ${30 - timer.tick * 1};
} else {
timer.cancel();
var responseJson = NetworkUtils.getAllMarkers(
authToken
);
mapController.clearMarkers();
//startTimer(index + 1);
}
});
}
//Map<PermissionGroup, PermissionStatus> permissions = await PermissionHandler().requestPermissions([PermissionGroup.contacts]);import 'package:permission_handler/permission_handler.dart';
#override
Widget build(BuildContext context) {
return Scaffold(
body: Stack(
children: <Widget>[
GoogleMap(
onMapCreated: (GoogleMapController controller) {
mapController = controller;
},
initialCameraPosition: new CameraPosition(target: LatLng(3.326411697920109, 102.13127606037108))
),
Padding(
padding: const EdgeInsets.all(16.0),
child: Align(
alignment: Alignment.topRight,
child: Column(
children: <Widget>[
FloatingActionButton(
onPressed: () => print('button pressed'),
materialTapTargetSize: MaterialTapTargetSize.padded,
backgroundColor: Colors.lightBlue,
child: const Icon(Icons.map, size: 30.0),
heroTag: "btn1",
),
SizedBox(height: 5.0),
FloatingActionButton(
onPressed: () => print('second pressed'),
materialTapTargetSize: MaterialTapTargetSize.padded,
backgroundColor: Colors.lightBlue,
child: const Icon(Icons.add, size: 28.0),
heroTag: "btn2",
),
SizedBox(height: 5.0),
FloatingActionButton(
onPressed: () => print('second pressed'),
materialTapTargetSize: MaterialTapTargetSize.padded,
backgroundColor: Colors.lightBlue,
child: const Icon(Icons.directions_bike, size: 28.0),
heroTag: "btn3",
),
]
)
),
),
]
)
);
}
}
The below will display a countdown from 30 seconds inside of a FloatingActionButton.
import 'dart:async';
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'FAB Countdown Demo',
home: MyHomePage(),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
static const _defaultSeconds = 30;
Timer _timer;
var _countdownSeconds = 0;
#override
void initState() {
_timer = Timer.periodic(Duration(seconds: 1), (Timer t) => _getTime());
super.initState();
}
#override
void dispose() {
_timer.cancel();
super.dispose();
}
void _getTime() {
setState(() {
if (_countdownSeconds == 0) {
_countdownSeconds = _defaultSeconds;
} else {
_countdownSeconds--;
}
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
resizeToAvoidBottomPadding: false,
appBar: AppBar(
title: const Text('FAB Countdown Demo'),
),
floatingActionButton: FloatingActionButton(
child: Text('$_countdownSeconds'), onPressed: () {}),
);
}
}