I have implemented https://pub.dev/packages/alphabet_scroll_view in my project, I edited it for my own customization but when I scroll list with items scroll with letters is not changing.
https://imgur.com/a/ResAVrE here is a video to better understand what I want to achieve. That changes on selected letter is just my tap on them.
Here is other link if above is not working https://drive.google.com/file/d/1Oy6XWalXwXM-yqk7IZU0Av2_jEL3ZNk1/view?usp=sharing. I hope this will working.
I want to change selected letter dynamically when I am scrolling items.
Here is my code:
import 'package:alphabet_scroll_view/src/meta.dart';
import 'package:collection/collection.dart' show IterableExtension;
import 'package:flutter/material.dart';
enum LetterAlignment { left, right }
class AlphabetScrollView extends StatefulWidget {
AlphabetScrollView(
{Key? key,
required this.list,
this.alignment = LetterAlignment.right,
this.isAlphabetsFiltered = true,
this.overlayWidget,
required this.selectedTextStyle,
required this.unselectedTextStyle,
this.itemExtent = 40,
required this.itemBuilder})
: super(key: key);
/// List of Items should be non Empty
/// and you must map your
/// ```
/// List<T> to List<AlphaModel>
/// e.g
/// List<UserModel> _list;
/// _list.map((user)=>AlphaModel(user.name)).toList();
/// ```
/// where each item of this ```list``` will be mapped to
/// each widget returned by ItemBuilder to uniquely identify
/// that widget.
final List<AlphaModel> list;
/// ```itemExtent``` specifies the max height of the widget returned by
/// itemBuilder if not specified defaults to 40.0
final double itemExtent;
/// Alignment for the Alphabet List
/// can be aligned on either left/right side
/// of the screen
final LetterAlignment alignment;
/// defaults to ```true```
/// if specified as ```false```
/// all alphabets will be shown regardless of
/// whether the item in the [list] exists starting with
/// that alphabet.
final bool isAlphabetsFiltered;
/// Widget to show beside the selected alphabet
/// if not specified it will be hidden.
/// ```
/// overlayWidget:(value)=>
/// Container(
/// height: 50,
/// width: 50,
/// alignment: Alignment.center,
/// color: Theme.of(context).primaryColor,
/// child: Text(
/// '$value'.toUpperCase(),
/// style: TextStyle(fontSize: 20, color: Colors.white),
/// ),
/// )
/// ```
final Widget Function(String)? overlayWidget;
/// Text styling for the selected alphabet by which
/// we can customize the font color, weight, size etc.
/// ```
/// selectedTextStyle:
/// TextStyle(
/// fontWeight: FontWeight.bold,
/// color: Colors.black,
/// fontSize: 20
/// )
/// ```
final TextStyle selectedTextStyle;
/// Text styling for the unselected alphabet by which
/// we can customize the font color, weight, size etc.
/// ```
/// unselectedTextStyle:
/// TextStyle(
/// fontWeight: FontWeight.normal,
/// color: Colors.grey,
/// fontSize: 18
/// )
/// ```
final TextStyle unselectedTextStyle;
/// The itemBuilder must return a non-null widget and the third paramter id specifies
/// the string mapped to this widget from the ```[list]``` passed.
Widget Function(BuildContext, int, String) itemBuilder;
#override
_AlphabetScrollViewState createState() => _AlphabetScrollViewState();
}
class _AlphabetScrollViewState extends State<AlphabetScrollView> {
void init() {
widget.list
.sort((x, y) => x.key.toLowerCase().compareTo(y.key.toLowerCase()));
_list = widget.list;
setState(() {});
/// filter Out AlphabetList
if (widget.isAlphabetsFiltered) {
List<String> temp = [];
alphabets.forEach((letter) {
AlphaModel? firstAlphabetElement = _list.firstWhereOrNull(
(item) => item.key.toLowerCase().startsWith(letter.toLowerCase()));
if (firstAlphabetElement != null) {
temp.add(letter);
}
});
_filteredAlphabets = temp;
} else {
_filteredAlphabets = alphabets;
}
calculateFirstIndex();
setState(() {});
}
#override
void initState() {
init();
WidgetsBinding.instance.addPostFrameCallback((timeStamp) {
listController.addListener(() {
print('scrolling ${listController.position.pixels}');
if (listController.position.pixels >=
listController.position.maxScrollExtent) {
print('achieved end');
} else if (listController.position.pixels <=
listController.position.minScrollExtent) {
print('achieved start');
}
});
});
if (listController.hasClients) {
maxScroll = listController.position.maxScrollExtent;
}
super.initState();
}
ScrollController listController = ScrollController();
final _selectedIndexNotifier = ValueNotifier<int>(0);
final positionNotifer = ValueNotifier<Offset>(Offset(0, 0));
final Map<String, int> firstIndexPosition = {};
List<String> _filteredAlphabets = [];
final letterKey = GlobalKey();
List<AlphaModel> _list = [];
bool isLoading = false;
bool isFocused = false;
final key = GlobalKey();
#override
void didUpdateWidget(covariant AlphabetScrollView oldWidget) {
super.didUpdateWidget(oldWidget);
if (oldWidget.list != widget.list ||
oldWidget.isAlphabetsFiltered != widget.isAlphabetsFiltered) {
_list.clear();
firstIndexPosition.clear();
init();
}
}
int getCurrentIndex(double vPosition) {
double kAlphabetHeight = letterKey.currentContext!.size!.height;
return (vPosition ~/ kAlphabetHeight);
}
/// calculates and Maintains a map of
/// [letter:index] of the position of the first Item in list
/// starting with that letter.
/// This helps to avoid recomputing the position to scroll to
/// on each Scroll.
void calculateFirstIndex() {
_filteredAlphabets.forEach((letter) {
AlphaModel? firstElement = _list.firstWhereOrNull(
(item) => item.key.toLowerCase().startsWith(letter));
if (firstElement != null) {
int index = _list.indexOf(firstElement);
firstIndexPosition[letter] = index;
}
});
}
void scrolltoIndex(int x, Offset offset) {
int index = firstIndexPosition[_filteredAlphabets[x].toLowerCase()]!;
final scrollToPostion = widget.itemExtent * index;
if (index != null) {
listController.animateTo((scrollToPostion),
duration: const Duration(milliseconds: 300), curve: Curves.easeOut);
}
positionNotifer.value = offset;
}
void onVerticalDrag(Offset offset) {
int index = getCurrentIndex(offset.dy);
if (index < 0 || index >= _filteredAlphabets.length) return;
_selectedIndexNotifier.value = index;
setState(() {
isFocused = true;
});
scrolltoIndex(index, offset);
}
double? maxScroll;
#override
Widget build(BuildContext context) {
return Stack(
children: [
ListView.builder(
controller: listController,
scrollDirection: Axis.vertical,
itemCount: _list.length,
physics: ClampingScrollPhysics(),
itemBuilder: (_, x) {
return ConstrainedBox(
constraints: BoxConstraints(maxHeight: widget.itemExtent),
child: widget.itemBuilder(_, x, _list[x].key));
}),
Align(
alignment: widget.alignment == LetterAlignment.left
? Alignment.centerLeft
: Alignment.centerRight,
child: Container(
key: key,
padding: const EdgeInsets.symmetric(horizontal: 2),
child: SingleChildScrollView(
child: GestureDetector(
onVerticalDragStart: (z) => onVerticalDrag(z.localPosition),
onVerticalDragUpdate: (z) => onVerticalDrag(z.localPosition),
onVerticalDragEnd: (z) {
setState(() {
isFocused = false;
});
},
child: ValueListenableBuilder<int>(
valueListenable: _selectedIndexNotifier,
builder: (context, int selected, Widget? child) {
return Column(
mainAxisAlignment: MainAxisAlignment.center,
children: List.generate(
_filteredAlphabets.length,
(x) => GestureDetector(
key: x == selected ? letterKey : null,
onTap: () {
_selectedIndexNotifier.value = x;
scrolltoIndex(x, positionNotifer.value);
},
child: Container(
decoration: BoxDecoration(
borderRadius: BorderRadius.only(
topRight: widget.alignment == LetterAlignment.left ? Radius.circular(20) : Radius.circular(0),
bottomRight: widget.alignment == LetterAlignment.left ? Radius.circular(20) : Radius.circular(0),
topLeft: widget.alignment == LetterAlignment.right ? Radius.circular(20) : Radius.circular(0),
bottomLeft: widget.alignment == LetterAlignment.right ? Radius.circular(20) : Radius.circular(0)
),
color: selected == x ? Color(0xFFFA3B71) : Colors.transparent
),
padding: const EdgeInsets.symmetric(
horizontal: 12, vertical: 2),
child: Text(
_filteredAlphabets[x].toUpperCase(),
style: selected == x
? widget.selectedTextStyle
: widget.unselectedTextStyle,
// style: TextStyle(
// fontSize: 12,
// fontWeight: selected == x
// ? FontWeight.bold
// : FontWeight.normal),
),
),
),
));
}),
),
),
),
),
!isFocused
? Container()
: ValueListenableBuilder<Offset>(
valueListenable: positionNotifer,
builder:
(BuildContext context, Offset position, Widget? child) {
return Positioned(
right:
widget.alignment == LetterAlignment.right ? 40 : null,
left:
widget.alignment == LetterAlignment.left ? 40 : null,
top: position.dy,
child: widget.overlayWidget == null
? Container()
: widget.overlayWidget!(_filteredAlphabets[
_selectedIndexNotifier.value]));
})
],
);
}
}
class AlphaModel {
final String key;
final String? secondaryKey;
AlphaModel(this.key, {this.secondaryKey});
}
If you want to test my code you can install package linked above, I just changed customization for background of letter scrollview.
You can do Like this :
Check this Example:
Expanded(
child: AlphabetScrollView(
list:
_filterList.map((e) => AlphaModel(e.employeeName)).toList(),
// isAlphabetsFiltered: false,
alignment: LetterAlignment.right,
itemExtent: 90,
unselectedTextStyle: TextStyle(
fontSize: 18,
fontWeight: FontWeight.normal,
color: _filterList.length > 5
? Colors.black
: Colors.transparent),
selectedTextStyle: TextStyle(
fontSize: 20,
fontWeight: FontWeight.bold,
color: _filterList.length > 5
? Colors.red
: Colors.transparent),
overlayWidget: (value) => Stack(
alignment: Alignment.center,
children: [
Icon(
Icons.star,
size: 50,
color: Colors.red,
),
Container(
height: 50,
width: 50,
decoration: BoxDecoration(
shape: BoxShape.circle,
// color: Theme.of(context).primaryColor,
),
alignment: Alignment.center,
child: Text(
'$value'.toUpperCase(),
style: TextStyle(fontSize: 18, color: Colors.white),
),
),
],
),
itemBuilder: (_, index, id) {
return InkWell(
onTap: () => Get.toNamed(EmployeeInfo.route,
arguments: _filterList[index]),
child: Container(
margin: EdgeInsets.only(right: 20),
child: Column(
children: [
Padding(
padding: const EdgeInsets.only(bottom: 12.0),
child: Row(
children: [
SizedBox(
width: 10,
),
Stack(
children: [
CircularProfileAvatar(
SystemConfiguration.baseUrl +
SystemConfiguration.getEmployePhoto +
_filterList[index]
.id, //sets image path, it should be a URL string. default value is empty string, if path is empty it will display only initials
radius: 30, // sets radius, default 50.0
backgroundColor: Colors
.transparent, // sets background color, default Colors.white
borderWidth:
2, // sets border, default 0.0
borderColor: Colors
.blue, // sets border color, default Colors.white
cacheImage:
true, // allow widget to cache image against provided url
imageFit: BoxFit.cover,
// sets on tap
showInitialTextAbovePicture: true,
errorWidget: (BuildContext context,
String data, dynamic v) {
return Icon(
FeatherIcons.user,
size: 40,
color: Colors.white,
);
}, // setting it true will show initials text above profile picture, default false
),
Positioned(
bottom: 5,
right: 0,
child: Padding(
padding:
const EdgeInsets.only(left: 3.0),
child: Container(
width: 17,
height: 17,
decoration: BoxDecoration(
shape: BoxShape.circle,
color: Color(0xFFe0f2f1)),
child: Icon(
Icons.circle,
size: 16.0,
// ignore: unnecessary_null_comparison
color: (_filterList[index]
.attendanceStatus
.length >
0 &&
_filterList[index]
.InOffice
.contains("IN"))
? Colors.green
: Colors.red[500],
),
),
))
],
),
Expanded(
flex: 3,
child: Padding(
padding: const EdgeInsets.all(8.0),
child: Column(
mainAxisAlignment:
MainAxisAlignment.start,
crossAxisAlignment:
CrossAxisAlignment.start,
children: [
Text(
_filterList[index].employeeName,
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: GoogleFonts.roboto(
fontSize: 13.0,
fontWeight: FontWeight.w500),
),
],
),
),
),
Expanded(
flex: 2,
child: Stack(
children: [
Positioned(
left: 44,
child: ElevatedButton(
onPressed: () => _launchURL(
_filterList[index].mobile1),
child: Icon(
Icons.call,
size: 20.0,
color: Colors.white,
),
style: ElevatedButton.styleFrom(
shape: CircleBorder(),
padding: EdgeInsets.all(2),
primary:
Colors.blue, // <-- Button color
onPrimary:
Colors.red, // <-- Splash color
),
),
),
ElevatedButton(
onPressed: () {
// _textMe(_filterList[index].mobile1); mobile text
Get.toNamed(ChatViwer.route,
arguments: {
"employe_name":
_filterList[index]
.employeeName,
"employe_id":
_filterList[index].id
});
},
child: Icon(
Icons.message,
size: 20.0,
color: Colors.white,
),
style: ElevatedButton.styleFrom(
shape: CircleBorder(),
padding: EdgeInsets.all(2),
primary:
Colors.blue, // <-- Button color
onPrimary:
Colors.red, // <-- Splash color
),
),
],
),
)
Related
I wrote the code to get data from List to chips and when click chips the colour changed to blue. But I want to fetch data from firestore instead "words list".
Instead this words list ...
Database collection image
I want to display "WordName" field in the chips.
My code..
class uitry extends StatefulWidget {
const uitry({Key? key}) : super(key: key);
#override
State<uitry> createState() => _uitryState();
}
class _uitryState extends State<uitry> {
List<String> wordList = [
'Shopping',
'Brunch',
'Music',
'Road Trips',
'Tea',
'Trivia',
'Comedy',
'Clubbing',
'Drinking',
'Wine',
];
List<String> selectedWord = [];
List<String>? deSelectedWord = [];
#override
Widget build(BuildContext context) {
double height = MediaQuery.of(context).size.height;
double width = MediaQuery.of(context).size.width;
return Scaffold(
body: Container(
decoration: const BoxDecoration(
image: DecorationImage(
image: AssetImage(Config.app_background4), fit: BoxFit.fill),
),
child: SafeArea(
child: Center(
child: Column(
children: <Widget>[
Padding(
padding: const EdgeInsets.only(top: 14, right: 0),
child: Column(
children: [
SizedBox(
width: width * 0.94,
height: height * 0.30,
child: Column(
children: <Widget>[
const SizedBox(height: 16),
Wrap(
children: wordList.map(
(word) {
bool isSelected = false;
if (selectedWord!.contains(word)) {
isSelected = true;
}
return GestureDetector(
onTap: () {
if (!selectedWord!.contains(word)) {
if (selectedWord!.length < 50) {
selectedWord!.add(word);
deSelectedWord!.removeWhere(
(element) => element == word);
setState(() {});
print(selectedWord);
}
} else {
selectedWord!.removeWhere(
(element) => element == word);
deSelectedWord!.add(word);
setState(() {
// selectedHobby.remove(hobby);
});
print(selectedWord);
print(deSelectedWord);
}
},
child: Container(
margin: const EdgeInsets.symmetric(
horizontal: 5, vertical: 4),
child: Container(
padding: const EdgeInsets.symmetric(
vertical: 5, horizontal: 12),
decoration: BoxDecoration(
color: isSelected
? HexColor('#0000FF')
: HexColor('#D9D9D9'),
borderRadius: BorderRadius.circular(18),
border: Border.all(
color: isSelected
? HexColor('#0000FF')
: HexColor('#D9D9D9'),
width: 2)),
child: Text(
word,
style: TextStyle(
color: isSelected
? Colors.black
: Colors.black,
fontSize: 14,
fontWeight: FontWeight.w600),
),
),
),
);
},
).toList(),
),
],
),
),
],
),
),
],
))),
),
);
}
}
How get that from firestore? I hope You can understand what I ask. Thank you!
I would do the following:
Initialize your list of words to an empty list
Use the initState method of the stateful widget to make a call to firestore to fetch all the documents that have the wordName property and get the word from the result and set it to a new list
Assign the new list to the wordList property and setState to re-render.
This would be it to get the words and set the chips with fetched words.
Keep in mind that since you are making an async call to firestore you should show some form of loading to tell the user you are fetching the data otherwise you would show and empty chip list until you fetch the data.
I created a stepper (4 steps) with two buttons for next and previous. Each step has a form, and each form is in a widget in its own class.
The first problem is that every time I click the previous button, the data in the text fields disappear.
How can I preserve the state of each widget in each step?
The second problem, I want the last step to be a summary of what the user has entered in the previous steps. What is the best way to get the data from each step and display them in the last step?
I would really appreciate it if you could give me a solution. Thank you
I tried using AutomaticKeepAliveClientMixin but it didn't work .
import 'package:flutter/material.dart';
class CustomeStepper extends StatelessWidget {
final double width;
final List<IconData> icons;
final List<String> titles;
final int curStep;
final Color circleActiveColor;
final Color circleInactiveColor;
final Color iconActiveColor;
final Color iconInactiveColor;
final Color textActiveColor;
final Color textInactiveColor;
final double lineWidth = 4.0;
final List<Widget> content;
CustomeStepper(
{required this.icons,
required this.curStep,
required this.titles,
required this.width,
required this.circleActiveColor,
required this.circleInactiveColor,
required this.iconActiveColor,
required this.iconInactiveColor,
required this.textActiveColor,
required this.textInactiveColor,
required this.content})
: assert(curStep > 0 && curStep <= icons.length),
assert(width > 0);
#override
Widget build(BuildContext context) {
return Directionality(
textDirection: TextDirection.rtl,
child: Container(
width: width,
padding: const EdgeInsets.only(
top: 32.0,
left: 24.0,
right: 24.0,
),
child: Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
Row(
children: _iconViews(),
),
const SizedBox(
height: 10,
),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: _titleViews(),
),
Expanded(
child: Container(
margin: const EdgeInsets.only(top: 16),
child: content[curStep - 1]),
)
],
)),
);
}
List<Widget> _iconViews() {
var list = <Widget>[];
icons.asMap().forEach((i, icon) {
var circleColor = (i == 0 || curStep >= i + 1)
? circleActiveColor
: circleInactiveColor;
var lineColor = (i == 0 || curStep >= i + 1)
? circleActiveColor
: circleInactiveColor;
var iconColor =
(i == 0 || curStep >= i + 1) ? iconActiveColor : iconInactiveColor;
list.add(
Container(
width: 50.0,
height: 50.0,
padding: const EdgeInsets.all(0),
child: Icon(
icon,
color: iconColor,
size: 25.0,
),
decoration: BoxDecoration(
color: circleColor,
borderRadius: const BorderRadius.all(
Radius.circular(25.0),
),
),
),
);
//line between icons
if (i != icons.length - 1) {
list.add(Expanded(
child: Container(
height: lineWidth,
color: lineColor,
)));
}
});
return list;
}
List<Widget> _titleViews() {
var list = <Widget>[];
titles.asMap().forEach((i, text) {
var _textColor =
(i == 0 || curStep > i + 1) ? textActiveColor : textInactiveColor;
list.add(
Container(
width: 50.0,
alignment: Alignment.topCenter,
padding: const EdgeInsets.all(0),
child: Text(
text,
textAlign: TextAlign.center,
style: TextStyle(color: _textColor, fontWeight: FontWeight.bold),
),
),
);
});
return list;
}
}
import 'package:flutter/material.dart';
import 'package:project_five/widgets/business/adding_product_widgets/first_step.dart';
import 'package:project_five/widgets/business/adding_product_widgets/four_step.dart';
import 'package:project_five/widgets/business/adding_product_widgets/second_step.dart';
import 'package:project_five/widgets/business/adding_product_widgets/third_step.dart';
import 'package:project_five/widgets/business/custome_stepper.dart';
class AddProduct extends StatefulWidget {
const AddProduct({Key? key}) : super(key: key);
#override
State<AddProduct> createState() => _AddProductState();
}
class _AddProductState extends State<AddProduct> {
static const _stepIcons = [
Icons.add_circle,
Icons.document_scanner,
Icons.camera_alt_rounded,
Icons.check,
];
static const _titles = ['المنتج', 'تفاصيل', 'الصور', 'نشر'];
var _contnet = [
FirstStep(),
SecondStep(),
ThirdStep(),
Forth()];
var _curStep = 1;
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('إضافة منتج'),
centerTitle: true,
),
persistentFooterButtons: [
Row(
children: [
Expanded(
child: ElevatedButton(
child: const Text('التالي'),
style: ElevatedButton.styleFrom(
padding: const EdgeInsets.all(16),
),
onPressed: () => setState(() {
if (_curStep < _stepIcons.length) _curStep++;
}),
),
),
const SizedBox(
width: 8,
),
Expanded(
child: ElevatedButton(
child: const Text('رجوع'),
style: ElevatedButton.styleFrom(
primary: Colors.white,
onPrimary: Colors.black,
padding: const EdgeInsets.all(16)),
onPressed: () => setState(() {
if (_curStep > 1) _curStep--;
}),
),
),
],
)
],
body: CustomeStepper(
icons: _stepIcons,
width: MediaQuery.of(context).size.width,
curStep: _curStep,
titles: _titles,
circleActiveColor: Colors.green,
circleInactiveColor: const Color(0xffD5D5D5),
iconActiveColor: Colors.white,
iconInactiveColor: Colors.white,
textActiveColor: Colors.green,
textInactiveColor: const Color(0xffD5D5D5),
content: _contnet,
),
);
}
}
I had the same problem, It would help to see your forms widgets. I will try my best to describe what you need to do.
Your textfields in your forms should be tied to your model class. Example: onChange: Product.title = TextField.value. and you should use initial value with your model properties, example: initialValue: Product.title. I think this way you can retain the state of the inputs in your forms.
As for the second part of your question, the Main widget that is controlling the stepper should have a state variable, such as isCompleted, on the last step you set this variable to 'true' and the main body of the stepper should be in a stack, in your stack you check if "isCompleted" ? Stepper : SummaryWidget.
How are handling Arabic titles for text fields and matching them with your class model properties?
I hope my answer can help!
I'm trying to create a custom TabBar which navigates only forward with a button click.
im using an enum to manage the state and couple of class objects to manage items for Tabs.
What result I expected
VS
What result I've got
The code I worked out is as follows. I am still trying to figure out a way of managing the expected outcome with my code implementation. if anyone can help out will be amazing.
Object classes
class CategoryModel {
final String? title;
final eCategory category;
// final eCategoryState? catState;
CategoryModel({this.title, required this.category});
}
class CategoryStateModel {
final CategoryModel? tItem;
late eTabState? tState;
CategoryStateModel({this.tItem, this.tState});
}
The code portion
class _ContributeTabScreenState extends State<ContributeTabScreen>
with SingleTickerProviderStateMixin {
eButtonState _submitBtnState = eButtonState.bActive;
eButtonState _continueBtnState = eButtonState.bActive;
int pageNo = 0;
TabController? _tabController;
late List<eTabState>? _tabState = [];
late List<CategoryModel>? _categoryModelList = [];
late List<Tab>? _tabList = [];
late List<CategoryStateModel>? _catStateList = [];
#override
void initState() {
_categoryModelList = widget.catList;
_assignTabs();
_tabController = new TabController(
vsync: this,
length: _categoryModelList!.length, //3,
);
super.initState();
// print(_categoryModelList![0].title.toString());
}
List<Tab> _assignTabs() {
for (var item = 0; item < _categoryModelList!.length; item++) {
//
if (item != 0) {
for (var t in _categoryModelList!) {
_catStateList!.add(
new CategoryStateModel(tItem: t, tState: eTabState.tUnSelected));
}
} else {
for (var t in _categoryModelList!) {
_catStateList!.add(
new CategoryStateModel(tItem: t, tState: eTabState.tSelected));
}
}
//
_tabList!.add(
new Tab(
child: _tabItem(_catStateList![item]),
// child: _tabItem(_categoryModelList![item]),
),
);
}
return _tabList!;
}
void _goBack() {
Navigator.of(context).pop();
}
//the onPressed call back I manage the forward move of a tabbar item + tabview
void forwardTabPage() {
if (pageNo >= 0 && pageNo < _categoryModelList!.length) {
setState(() {
// });
// setState(() {
pageNo = pageNo + 1;
// _catStateList![pageNo - 1].tState = eTabState.tCompleted;
// _tabState![pageNo - 1] = _catStateList![pageNo - 1].tState!;
});
_tabController!.animateTo(pageNo);
}
}
...rest of the code
//the Tabbar item
_tabItem(CategoryStateModel item) => Container(
width: 140.0,
alignment: Alignment.center,
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
item.tItem!.title!,
style: tBody4.copyWith(color: CustomColors.mDarkBlue),
),
item.tState == _catStateList![pageNo].tState //eTabState.tCompleted
? SizedBox(
width: 8.0,
)
: SizedBox(
width: 0.0,
),
item.tState == _catStateList![pageNo].tState //eTabState.tCompleted
? CircleAvatar(
radius: 12.0,
backgroundColor: CustomColors.green600,
child: Icon(
Icons.check_outlined,
size: 20.0,
),
)
: SizedBox(
width: 0.0,
),
],
),
);
continueBtn() => ButtonWidget(
btnColor: CustomColors.green600,
borderColor: CustomColors.green600,
textColor: CustomColors.mWhite,
text: "Continue",
eButtonType: eButtonType.bText,
eButtonState: _continueBtnState,
onPressed: () {
forwardTabPage();
// _tabState = eTabState.tCompleted;
},
);
submitBtn() => ButtonWidget(
btnColor: CustomColors.green600,
borderColor: CustomColors.green600,
textColor: CustomColors.mWhite,
text: "Submit",
eButtonType: eButtonType.bText,
eButtonState: _submitBtnState,
onPressed: () {},
);
#override
Widget build(BuildContext context) {
return DefaultTabController(
length: _categoryModelList!.length,
child: Scaffold(
appBar: AppBar(
toolbarHeight: 60.0,
leadingWidth: 100.0,
leading: GestureDetector(
onTap: _goBack,
child: Container(
child: Row(
children: [
SizedBox(
width: 16.0,
),
const Icon(
Icons.arrow_back_ios,
color: CustomColors.grey700,
size: 24.0,
),
Flexible(
child: Text(
"Back",
style: tButtonSmall,
),
),
],
),
),
),
backgroundColor: Colors.white,
elevation: 0.0,
bottom: PreferredSize(
preferredSize: Size.fromHeight(50.0),
child: IgnorePointer(
child: TabBar(
controller: _tabController,
isScrollable: false,
enableFeedback: false,
padding: EdgeInsets.only(bottom: 8.0),
indicatorSize: TabBarIndicatorSize.label,
indicator:
// _tabState == eTabState.tSelected
// ?
BoxDecoration(
borderRadius: BorderRadius.circular(40.0),
color: CustomColors.green300,
border: Border.all(color: CustomColors.mGreen, width: 2),
),
tabs: _tabList!
),
),
),
),
body: Container(
child: TabBarView(
controller: _tabController,
physics: NeverScrollableScrollPhysics(),
children: [
Container(
color: CustomColors.grey600.withOpacity(0.2),
child: Center(
child: Text("Home Tab View"),
),
),
Center(
child: Text("Map Tab View"),
),
],
),
),
persistentFooterButtons: [
Container(
width: MediaQuery.of(context).size.width,
// height: 40,
padding: EdgeInsets.symmetric(horizontal: 24.0),
margin: EdgeInsets.symmetric(vertical: 16.0),
child: (_categoryModelList!.length == 1 ||
pageNo == _categoryModelList!.length - 1)
? submitBtn()
: continueBtn(),
)
],
),
);
}
enum classes
enum eCategoryState {
cSelected,
cUnSelected,
cEnded,
}
enum eTabState {
tSelected,
tUnSelected,
tCompleted,
}
enum eCategory {
cAccess,
cAmenities,
}
I don't know if I understand what you mean or not. but try this. TabBar has a isScrollable property. set it to true
I have a category bar in my flutter application. Initially all the category names were hard coded but now I'm getting the category names from json data.
what I need to do is to set a green container color for the selected button and the text will be white. And the other buttons will be black text + white container.
When I had hardcoded data I checked the button's index individually like this:
for container:
color: buttonIndex == 0 ? Color(0xff75c760) : Colors.white,
for button text:
buttonIndex == 0 ? Colors.white : Colors.black,
but now the as there can be any number of category name's I dont know how to turn this into a dynamic one.
Full Code:
class _RecipeCategoryBarState extends State<RecipeCategoryBar> {
late AppData appData;
int buttonIndex = 0;
void setIndex(int val) {
setState(() {
buttonIndex = val;
});
}
Widget recipeCategoryButton() {
return Container();
}
#override
void initState() {
super.initState();
appData = Store.instance.getAppData();
}
#override
Widget build(BuildContext context) {
return Container(
height: 50.0,
child: ListView.builder(
scrollDirection: Axis.horizontal,
itemCount: appData.recipeCategories!.length,
itemBuilder: (BuildContext context, int index) {
return ButtonBar(
children: <Widget>[
Material(
color: buttonIndex == 0 ? Color(0xff75c760) : Colors.white,
borderRadius: BorderRadius.circular(15),
clipBehavior: Clip.antiAlias,
child: Padding(
padding: const EdgeInsets.symmetric(
horizontal: 5,
),
child: Row(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
TextButton(
onPressed: () {
setIndex(0);
widget.sendDataToParent(index);
},
child: Text(
appData.recipeCategories![index].categoryName!,
style: TextStyle(
color:
buttonIndex == 0 ? Colors.white : Colors.black,
fontSize: 12,
fontFamily: "Poppins",
fontWeight: FontWeight.w500,
),
),
),
],
),
),
),
],
);
},
),
);
}
}
If I understand what you want to do, you can achieve it by using the index given by the ListView.builder instead of your buttonIndex variable.
I've used this dep. https://pub.dev/packages/flutter_reorderable_list
to create the reorderable list, I did it and styled it. Instead of a text I used input with a checkbox, I generated 10 lists and 9 of them were clickable but not the first one.
class DragAndDropSubTasks extends StatefulWidget {
DragAndDropSubTasks({Key key, this.title}) : super(key: key);
final String title;
#override
_DragAndDropSubTasksState createState() => _DragAndDropSubTasksState();
}
class ItemData {
ItemData(this.title, this.key);
final String title;
// Each item in reorderable list needs stable and unique key
final Key key;
}
enum DraggingMode {
iOS,
Android,
}
class _DragAndDropSubTasksState extends State<DragAndDropSubTasks> {
List<ItemData> _items;
_DragAndDropSubTasksState() {
_items = List();
for (int i = 0; i < 1; i++) {
String label = "List item $i";
_items.add(ItemData(label, ValueKey(i)));
}
}
// Returns index of item with given key
int _indexOfKey(Key key) {
return _items.indexWhere((ItemData d) => d.key == key);
}
bool _reorderCallback(Key item, Key newPosition) {
int draggingIndex = _indexOfKey(item);
int newPositionIndex = _indexOfKey(newPosition);
// Uncomment to allow only even target reorder possition
// if (newPositionIndex % 2 == 1)
// return false;
final draggedItem = _items[draggingIndex];
setState(() {
debugPrint("Reordering $item -> $newPosition");
_items.removeAt(draggingIndex);
_items.insert(newPositionIndex, draggedItem);
});
return true;
}
void _reorderDone(Key item) {
final draggedItem = _items[_indexOfKey(item)];
debugPrint("Reordering finished for ${draggedItem.title}}");
}
//
// Reordering works by having ReorderableList widget in hierarchy
// containing ReorderableItems widgets
//
DraggingMode _draggingMode = DraggingMode.iOS;
Widget build(BuildContext context) {
return Scaffold(
body: ReorderableList(
onReorder: this._reorderCallback,
onReorderDone: this._reorderDone,
child: CustomScrollView(
// cacheExtent: 3000,
slivers: <Widget>[
SliverPadding(
padding: EdgeInsets.only(
bottom: MediaQuery.of(context).padding.bottom),
sliver: SliverList(
delegate: SliverChildBuilderDelegate(
(BuildContext context, int index) {
return Container(
child: Item(
data: _items[index],
// first and last attributes affect border drawn during dragging
isFirst: index == 0,
isLast: index == _items.length - 1,
draggingMode: _draggingMode,
),
);
},
childCount: _items.length,
),
),
),
],
),
),
);
}
}
class Item extends StatelessWidget {
Item({
this.data,
this.isFirst,
this.isLast,
this.draggingMode,
});
final ItemData data;
final bool isFirst;
final bool isLast;
final DraggingMode draggingMode;
Widget _buildChild(BuildContext context, ReorderableItemState state) {
var device = MediaQuery.of(context).size;
BoxDecoration decoration;
if (state == ReorderableItemState.dragProxy ||
state == ReorderableItemState.dragProxyFinished) {
// slightly transparent background white dragging (just like on iOS)
} else {
bool placeholder = state == ReorderableItemState.placeholder;
decoration = BoxDecoration(
border: Border(
top: isFirst && !placeholder
? Divider.createBorderSide(context) //
: BorderSide.none,
bottom: isLast && placeholder
? BorderSide.none //
: Divider.createBorderSide(context)),
color: placeholder ? null : Colors.white);
}
// For iOS dragging mdoe, there will be drag handle on the right that triggers
// reordering; For android mode it will be just an empty container
Widget dragHandle = draggingMode == DraggingMode.iOS
? ReorderableListener(
child: Container(
height: device.height * 39 / 812,
padding: EdgeInsets.only(right: 18.0, left: 18.0),
margin: EdgeInsets.only(top: 7),
decoration: BoxDecoration(
borderRadius: new BorderRadius.only(
topRight: const Radius.circular(8.0),
bottomRight: const Radius.circular(8.0),
),
color: Color.fromRGBO(249, 248, 255, 1),
),
child: Center(child: BuildSvg('assets/svg/DragAndDropIcon.svg')),
),
)
: Container();
Widget content = Container(
decoration: decoration,
child: SafeArea(
top: false,
bottom: false,
child: Opacity(
// hide content for placeholder
opacity: state == ReorderableItemState.placeholder ? 0.0 : 1.0,
child: IntrinsicHeight(
child: Row(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: <Widget>[
Container(
alignment: Alignment.centerLeft,
padding: EdgeInsets.only(left: 7),
width: device.width * 270 / 375,
margin: EdgeInsets.only(top: 7),
decoration: BoxDecoration(
borderRadius: new BorderRadius.only(
topLeft: const Radius.circular(8.0),
bottomLeft: const Radius.circular(8.0),
),
color: Color.fromRGBO(249, 248, 255, 1),
),
child: Stack(
children: <Widget>[
CheckBox('', false, Colors.white),
Container(
margin: EdgeInsets.only(left: 30),
child: TextFormField(
decoration: new InputDecoration(
border: InputBorder.none,
focusedBorder: InputBorder.none,
),
keyboardType: TextInputType.text,
),
),
],
)),
// Triggers the reordering
dragHandle,
],
),
),
)),
);
// For android dragging mode, wrap the entire content in DelayedReorderableListener
if (draggingMode == DraggingMode.Android) {
content = DelayedReorderableListener(
child: content,
);
}
return content;
}
#override
Widget build(BuildContext context) {
return ReorderableItem(
key: data.key, //
childBuilder: _buildChild,
);
}
}
The input and checkbox don't work on the first list element but they do work fine on the others