Center a listview child on selection - flutter

I am trying to make all the selected dates to be yellow background and always in the center, how can I achieve that ?
What I have right now :
Here's what I have achieve so far :
Current date selected : https://i.stack.imgur.com/P2bPp.png
Another date selected : https://i.stack.imgur.com/oKFXu.png
Example of what I'm trying to achieve :
https://i.stack.imgur.com/yb6Lx.png
https://i.stack.imgur.com/G9rZc.png
Please advice. Thanks in advance.
Here is my code :
Date_picker
return Container(
height: 85.0,
margin: EdgeInsets.only(left: 20.0, right: 20.0),
child: ListView.builder(
scrollDirection: Axis.horizontal,
controller: _controller,
itemCount: daysCount,
itemBuilder: (context, index) {
int daysCountBefore = daysCount ~/ 2;
DateTime today = DateTime.now();
//get half of the days count before today first then start up total daysCount
// 2021-01-30 15:31:16.481
DateTime startDate =
today.subtract(Duration(days: daysCountBefore));
//convert to 00:00:00.000 hours
// 2021-01-30 00:00:00.000
DateTime _startDate =
new DateTime(startDate.year, startDate.month, startDate.day);
// print(_startDate.day);
// print(daysCountBefore);
//show days count from start date
DateTime dates = _startDate.add(Duration(days: index));
//format to 00:00:00.00 hrs
//mainly for _compareDates();
DateTime _dates = new DateTime(dates.year, dates.month, dates.day);
bool isSelected = _currentDate != null
? _compareDates(_dates, _currentDate)
: false;
return DateWidget(
date: dates,
width: isSelected ? 65.0 : 40.0,
selectedColor:
isSelected ? widget.selectedDateColor : Colors.transparent,
dayNumTextStyle: isSelected
? kSelectedDayNumTextStyle
: kNotSelectedDayNumTextStyle,
dayMonthTextStyle: isSelected
? kSelectedDayMonthTextStyle
: kNotSelectedDayMonthTextStyle,
dateTapped: (dateToShow) {
//change state to the date that is tapped
setState(() {
_currentDate = dateToShow;
});
//Callback
widget.onDateChange(dateToShow);
},
);
},
));
Date_widget :
import 'package:flutter/material.dart';
import 'package:intl/intl.dart';
class DateWidget extends StatelessWidget {
final DateTime date;
final TextStyle textStyle;
final Color selectedColor;
final TextStyle dayMonthTextStyle;
final Function(DateTime) dateTapped;
final double width; //75.0
final TextStyle dayNumTextStyle;
DateWidget(
{#required this.date,
#required this.dateTapped,
this.textStyle,
this.width,
this.selectedColor,
this.dayNumTextStyle,
this.dayMonthTextStyle});
#override
Widget build(BuildContext context) {
return GestureDetector(
child: Container(
margin: EdgeInsets.all(3.0),
width: width,
height: 80.0,
decoration: BoxDecoration(
borderRadius: BorderRadius.all(Radius.circular(20.0)),
color: selectedColor,
),
child: Padding(
padding: EdgeInsets.all(8.0),
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Text(new DateFormat('MMM').format(date).toUpperCase(),
style: dayMonthTextStyle),
Text(date.day.toString(), style: dayNumTextStyle),
Text(
new DateFormat('E').format(date).toUpperCase(),
// style: TextStyle(fontWeight: FontWeight.bold)
style: dayMonthTextStyle,
)
],
),
)),
onTap: () {
dateTapped(date);
});
}
}

Here is a solution based only on ListView.builder and its ScrollController:
import 'dart:math';
import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
void main() {
runApp(
MaterialApp(
title: 'Flutter Demo',
home: Scaffold(
body: MyWidget(data: List.generate(100, (index) => index)),
),
),
);
}
class MyWidget extends HookWidget {
final List<int> data;
const MyWidget({Key key, this.data}) : super(key: key);
#override
Widget build(BuildContext context) {
final _scrollController = useScrollController();
final _selected = useState(0);
return LayoutBuilder(
builder: (context, constraints) {
final double size = constraints.biggest.width / 10;
return SizedBox(
height: size,
child: ListView.builder(
controller: _scrollController,
scrollDirection: Axis.horizontal,
itemExtent: size,
itemCount: data.length,
itemBuilder: (context, index) => Padding(
padding: EdgeInsets.all(size * .05),
child: GestureDetector(
onTap: () {
_selected.value = index;
_scrollController.animateTo(
max(index - 4.5, 0) * size,
duration: Duration(seconds: 1),
curve: Curves.easeInOut,
);
},
child: Card(
color: _selected.value == index
? Colors.amber
: Colors.lightGreen.shade100,
child: Center(
child: Text(data[index].toString()),
),
),
),
),
),
);
},
);
}
}

You can use Carousel Slider
Example -
CarouselSlider(
options: CarouselOptions(height: 400.0),
items: [1,2,3,4,5].map((i) {
return Builder(
builder: (BuildContext context) {
return Container(
width: MediaQuery.of(context).size.width,
margin: EdgeInsets.symmetric(horizontal: 5.0),
decoration: BoxDecoration(
color: Colors.amber
),
child: Text('text $i', style: TextStyle(fontSize: 16.0),)
);
},
);
}).toList(),
)

Related

Flutter PageView dynamic height

I created a PageView with a fixed value but it is an issue. How i am gonna convert this to dynamic height? SizedBox is in a Column's child. I tried Expanded and Flexible widgets but they did not work.
class BranchViewBottomSection extends ConsumerWidget {
const BranchViewBottomSection({
Key? key,
required this.data,
required this.branchId,
required this.companyId,
}) : super(key: key);
final Map<String, dynamic> data;
final String branchId;
final String companyId;
#override
Widget build(BuildContext context, WidgetRef ref) {
return Column(
children: [
SizedBox(
height: 50,
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
ListView.builder(
shrinkWrap: true,
scrollDirection: Axis.horizontal,
itemCount: data['available_sections'].length,
itemBuilder: (BuildContext context, int index) {
if (data['available_sections'][index]["branchTabValue"] ==
ref.read(tabIndexProvider)) {
return BranchSectionBox(
data: data, index: index, isActive: true);
} else {
return BranchSectionBox(
data: data, index: index, isActive: false);
}
},
),
],
),
),
GestureDetector(
onTap: () {
Navigator.of(context).push(
PageRouteBuilder(
pageBuilder: (context, animation, secondaryAnimation) =>
MenuView(
branchId: branchId,
companyId: companyId,
branchData: data,
),
transitionsBuilder:
(context, animation, secondaryAnimation, child) {
const begin = Offset(1.0, 0.0);
const end = Offset.zero;
const curve = Curves.ease;
final tween = Tween(begin: begin, end: end);
final curvedAnimation = CurvedAnimation(
parent: animation,
curve: curve,
);
return SlideTransition(
position: tween.animate(curvedAnimation),
child: child,
);
},
),
);
},
child: Container(
height: 50,
width: MediaQuery.of(context).size.width,
color: Colors.pink,
child: const Center(
child: Text(
"Sipariş vermek için dokunun.",
style: TextStyle(color: Colors.white),
),
),
),
),
Expanded(
child: PageView(
onPageChanged: (v) {
ref.watch(tabIndexProvider.notifier).state =
data['available_sections'][v]['branchTabValue'];
},
children: <Widget>[
BranchViewHomePage(),
BranchViewDetailsPage(),
const BranchViewCommentsPage(),
BranchViewContactPage(),
],
),
)
],
);
}
}
This is the parent.
// ignore_for_file: non_constant_identifier_names, file_names
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:neshapp/comps/common/MainCircularProgressIndicator.dart';
import 'package:neshapp/services/FirestoreService.dart';
import 'package:neshapp/utils/constants.dart';
import '../../providers/BranchViewProviders.dart';
import '../../providers/MenuProviders.dart';
import 'BranchSectionBoxes.dart';
class BranchView extends ConsumerWidget {
final String branchId;
final String companyId;
final String tableNo;
const BranchView(
{Key? key,
required this.branchId,
required this.companyId,
required this.tableNo})
: super(key: key);
#override
Widget build(BuildContext context, WidgetRef ref) {
/*
Uygulamanın her yerinde kullanabilmek için eğer bir şubeye
girilirse şubenin ve markanın id'lerini providerlara veriyorum.
*/
ref.watch(branchIdProvider.notifier).setId(branchId);
ref.watch(companyIdProvider.notifier).setId(companyId);
ref.watch(tableNoProvider.notifier).setTable(tableNo);
return SafeArea(
child: Scaffold(
body: FutureBuilder<DocumentSnapshot>(
future: FirestoreService.getBranchData(companyId, branchId),
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.done) {
final data = snapshot.data?.data() as Map<String, dynamic>;
return CustomScrollView(
scrollBehavior: const ScrollBehavior(),
slivers: <Widget>[
SliverAppBar(
elevation: 0,
title: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text("${data['branch_name']} Şubesi"),
FutureBuilder<DocumentSnapshot>(
future: FirestoreService.getCompanyData(companyId),
builder: (context, snapshot) {
if (snapshot.connectionState ==
ConnectionState.done) {
final CData =
snapshot.data?.data() as Map<String, dynamic>;
return Container(
height: 40,
width: 40,
decoration: BoxDecoration(
shape: BoxShape.circle,
color: constsColor.black.withOpacity(0.5),
),
child: Image.network(CData['company_logo']),
);
} else {
return const MainCircularProgressIndicator();
}
},
),
],
),
pinned: true,
expandedHeight: 200,
backgroundColor: constsColor.neshMoru,
flexibleSpace: FlexibleSpaceBar(
background: AspectRatio(
aspectRatio: 16 / 9,
child: Stack(
children: [
Container(
decoration: BoxDecoration(
image: DecorationImage(
image: NetworkImage(data['branch_image']),
fit: BoxFit.cover,
),
),
),
Positioned(
bottom: 10,
right: 10,
child: Text(
tableNo,
style: TextStyle(
color: constsColor.white,
fontSize: 18,
),
),
),
],
),
),
),
),
SliverToBoxAdapter(
child: BranchViewBottomSection(
data: data,
branchId: branchId,
companyId: companyId,
),
),
],
);
} else {
return const Center(
child: MainCircularProgressIndicator(),
);
}
},
),
),
);
}
}
expand and flexible i believe are both depend on the parten widget.
how about using the state for that ?

How to change opacity of text and angle of icon while using slider button in flutter

I want that as I move my slider button towards right, the opacity of text decreases and arrow icon rotates exactly oppposite, i.e. it strts rotating and at last last it should point backwards. I want to use opacity and Transform.rotate widgets, but how do I keep updating the value of dx ,so I can divide it with total width of container and use the fraction for calculation.
If there is another way, please do tell me.
import 'dart:math';
import 'package:flutter/material.dart';
import 'package:passenger_flutter_app/utils/colors.dart';
import 'package:passenger_flutter_app/widgets/custom_sliding_button.dart';
class CommonSwipeButton extends StatelessWidget {
final String? buttonText1;
final String buttonText2;
final VoidCallback buttonCallBack2;
final bool isInfo;
final VoidCallback? buttonCallBack1;
final Widget itemWidget;
CommonSwipeButton(
{this.buttonCallBack1,
required this.buttonCallBack2,
this.isInfo = false,
this.buttonText1,
required this.buttonText2,
required this.itemWidget});
#override
Widget build(BuildContext context) {
return Container(
child: Column(
//crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
children: [
Padding(padding: const EdgeInsets.only(left: 16.0, right: 16.0, bottom: 16.0, top: 16), child: itemWidget),
Padding(
padding:
const EdgeInsets.only(bottom: 16.0, left: 16.0, right: 16.0),
child: Align(
alignment: Alignment.center,
child: SizedBox(
width: MediaQuery.of(context).size.width,
height: 44,
child: CustomSlidingButton(
//text: buttonText2,
),
),
),
)
],
),
);
}
}
/*
class SwipeButton extends StatefulWidget {
final ValueChanged<double>? valueChanged;
final String? text;
final Function? callBack;
SwipeButton({this.valueChanged, this.text, this.callBack});
#override
SwipeButtonState createState() {
return new SwipeButtonState();
}
}
class SwipeButtonState extends State<SwipeButton> {
ValueNotifier<double> valueListener = ValueNotifier(.0);
GlobalKey swipeKey = GlobalKey();
ValueNotifier<double> x=ValueNotifier<double>(0);
ValueNotifier<bool> isVisible = ValueNotifier<bool>(true);
#override
void initState() {
valueListener.addListener(notifyParent);
super.initState();
}
void notifyParent() {
if (widget.valueChanged != null) {
widget.valueChanged!(valueListener.value);
}
}
void getPos(double totalSize) {
RenderBox box = swipeKey.currentContext?.findRenderObject() as RenderBox;
Offset position = box.localToGlobal(Offset.zero); //this is global position
x.value = position.dx;
print(x);
if(x.value>355) {
print("Reached");
isVisible.value=false;
}
}
#override
Widget build(BuildContext context) {
return Container(
color: colorPrimary,
height: 40.0,
padding: EdgeInsets.symmetric(horizontal: 10.0),
child: Stack(
children: [
Center(
child: Padding(
padding: const EdgeInsets.only(left: 10.0),
child: Text(
"${widget.text}",
style: TextStyle(
color: Colors.white,
fontSize: 17,
),
),
),
),
Builder(
builder: (context) {
final handle = GestureDetector(
onHorizontalDragUpdate: (details) {
valueListener.value = (valueListener.value +
details.delta.dx / context.size!.width)
.clamp(.0, 1.0);
getPos(context.size!.width-5);
print(context.size?.width);
},
child: ValueListenableBuilder(
valueListenable: isVisible,
builder: (BuildContext context, bool val, Widget? child) {
return Container(
key: swipeKey,
height: 25.0,
width: 25.0,
color: val ? Colors.white : colorPrimary,
child: Center(
child: ValueListenableBuilder(
valueListenable: x,
builder: (BuildContext context, double d, Widget? child) {
return Transform.rotate(
angle: -pi*(d/350),
child: Icon(
Icons.arrow_forward,
color: Colors.orange,
size: 12,
),
);
},
),
),
);
},
),
);
return AnimatedBuilder(
animation: valueListener,
builder: (context, child) {
return Align(
alignment: Alignment(valueListener.value * 2 - 1, 0),
child: child,
);
},
child: handle,
);
},
),
],
),
);
}
}*/
You can use Slider widget from Flutter framework and update a local variable in the onChange function:
Slider(
value: _currentSliderValue,
max: 100, //or any max value you need
onChanged: (double value) {
setState(() {
_value = value;
});
},
);
And the _value variable you will use in Opacity and Transform widgets.

Move an item from one list to another with animation in Flutter

I have two vertical lists, one on the left side and the other one on the right, let's call them "Selected List" and "Unselected List".
I want the items in Unselected List to Animate from left side to the right side of the screen and add to Selected List.
the other items should fill the empty space in Unselected List and items in Selected List should free up the space for new item.
Here's the Ui
My Code:
class AddToFave extends StatefulWidget {
const AddToFave({Key? key}) : super(key: key);
#override
_AddToFaveState createState() => _AddToFaveState();
}
class _AddToFaveState extends State<AddToFave> {
List<String> unselected = [ '1','2','3','4','5','6','7','8','9','10'];
List<String> selected = [];
#override
Widget build(BuildContext context) {
return Scaffold(
body: SafeArea(
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Container(
width: MediaQuery.of(context).size.width / 5,
height: MediaQuery.of(context).size.height,
child: ListView.builder(
itemCount: selected.length,
itemBuilder: (context, index) {
return InkWell(
onTap: () {
unselected.add(selected[index]);
selected.removeAt(index);
setState(() {});
},
child: Container(
width: MediaQuery.of(context).size.width / 5,
height: MediaQuery.of(context).size.width / 5,
decoration: BoxDecoration(
color: Colors.black,
borderRadius: BorderRadius.circular(
MediaQuery.of(context).size.width / 5)),
child: Center(
child: Text(
selected[index],
style: TextStyle(color: Colors.white),
)),
),
);
}),
),
Container(
width: MediaQuery.of(context).size.width / 5,
height: MediaQuery.of(context).size.height,
child: ListView.builder(
itemCount: unselected.length,
itemBuilder: (context, index) {
return InkWell(
onTap: () {
selected.add(unselected[index]);
unselected.removeAt(index);
setState(() {});
},
child: Container(
width: MediaQuery.of(context).size.width / 5,
height: MediaQuery.of(context).size.width / 5,
decoration: BoxDecoration(
color: Colors.black,
borderRadius: BorderRadius.circular(
MediaQuery.of(context).size.width / 5)),
child: Center(
child: Text(
unselected[index],
style: TextStyle(color: Colors.white),
)),
),
);
}),
),
],
),
),
);
}
}
Thank you in advance.
This task can be broken into 2 parts.
First, use an AnimatedList instead of a regular ListView, so that when an item is removed, you can control its "exit animation" and shrink its size, thus making other items slowly move upwards to fill in its spot.
Secondly, while the item is being removed from the first list, make an OverlayEntry and animate its position, to create an illusion of the item flying. Once the flying is finished, we can remove the overlay and insert the item in the actual destination list.
Full source code for you to use, as a starting point:
import 'package:flutter/material.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
home: TwoAnimatedListDemo(),
);
}
}
class TwoAnimatedListDemo extends StatefulWidget {
const TwoAnimatedListDemo({Key? key}) : super(key: key);
#override
_TwoAnimatedListDemoState createState() => _TwoAnimatedListDemoState();
}
class _TwoAnimatedListDemoState extends State<TwoAnimatedListDemo> {
final List<String> _unselected = ['A', 'B', 'C', 'D', 'E', 'F', 'G'];
final List<String> _selected = [];
final _unselectedListKey = GlobalKey<AnimatedListState>();
final _selectedListKey = GlobalKey<AnimatedListState>();
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Two Animated List Demo'),
),
body: Row(
children: [
SizedBox(
width: 56,
child: AnimatedList(
key: _unselectedListKey,
initialItemCount: _unselected.length,
itemBuilder: (context, index, animation) {
return InkWell(
onTap: () => _moveItem(
fromIndex: index,
fromList: _unselected,
fromKey: _unselectedListKey,
toList: _selected,
toKey: _selectedListKey,
),
child: Item(text: _unselected[index]),
);
},
),
),
Spacer(),
SizedBox(
width: 56,
child: AnimatedList(
key: _selectedListKey,
initialItemCount: _selected.length,
itemBuilder: (context, index, animation) {
return InkWell(
onTap: () => _moveItem(
fromIndex: index,
fromList: _selected,
fromKey: _selectedListKey,
toList: _unselected,
toKey: _unselectedListKey,
),
child: Item(text: _selected[index]),
);
},
),
),
],
),
);
}
int _flyingCount = 0;
_moveItem({
required int fromIndex,
required List fromList,
required GlobalKey<AnimatedListState> fromKey,
required List toList,
required GlobalKey<AnimatedListState> toKey,
Duration duration = const Duration(milliseconds: 300),
}) {
final globalKey = GlobalKey();
final item = fromList.removeAt(fromIndex);
fromKey.currentState!.removeItem(
fromIndex,
(context, animation) {
return SizeTransition(
sizeFactor: animation,
child: Opacity(
key: globalKey,
opacity: 0.0,
child: Item(text: item),
),
);
},
duration: duration,
);
_flyingCount++;
WidgetsBinding.instance!.addPostFrameCallback((timeStamp) async {
// Find the starting position of the moving item, which is exactly the
// gap its leaving behind, in the original list.
final box1 = globalKey.currentContext!.findRenderObject() as RenderBox;
final pos1 = box1.localToGlobal(Offset.zero);
// Find the destination position of the moving item, which is at the
// end of the destination list.
final box2 = toKey.currentContext!.findRenderObject() as RenderBox;
final box2height = box1.size.height * (toList.length + _flyingCount - 1);
final pos2 = box2.localToGlobal(Offset(0, box2height));
// Insert an overlay to "fly over" the item between two lists.
final entry = OverlayEntry(builder: (BuildContext context) {
return TweenAnimationBuilder(
tween: Tween<Offset>(begin: pos1, end: pos2),
duration: duration,
builder: (_, Offset value, child) {
return Positioned(
left: value.dx,
top: value.dy,
child: Item(text: item),
);
},
);
});
Overlay.of(context)!.insert(entry);
await Future.delayed(duration);
entry.remove();
toList.add(item);
toKey.currentState!.insertItem(toList.length - 1);
_flyingCount--;
});
}
}
class Item extends StatelessWidget {
final String text;
const Item({Key? key, required this.text}) : super(key: key);
#override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.all(4.0),
child: CircleAvatar(
child: Text(text),
radius: 24,
),
);
}
}

Draw outside listview bounds in Flutter

I want to transform my item that it is bigger than the listview itself. (intention for focused navigation)
My List:
Container(
height: 100,
child: ListView.builder(
itemBuilder: (context, index) => HomeItem(title: '$index'),
scrollDirection: Axis.horizontal,
),
),
My Item:
class HomeItem extends StatelessWidget {
final String title;
final bool expand;
const HomeItem({
#required this.title,
this.expand = false,
});
#override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.symmetric(horizontal: ThemeDimens.padding8),
child: Transform.scale(
scale: expand ? 1.5 : 1,
child: AnimatedContainer(
width: 50,
height: 100,
color: expand ? ThemeColors.accent : ThemeColors.primary,
duration: ThemeDurations.shortAnimationDuration(),
child: Center(
child: Text(title),
),
),
),
);
}
}
Current behaviour
Expected behaviour
If you try to use OverflowBox or Transform, content of an item will still clip and won't be drawn outside of its bounding box. But it's possible to use Overlay to draw an element on top of list and position it on a specific list item, though it's a bit complicated.
class _MyHomePageState extends State<MyHomePage> with WidgetsBindingObserver {
final elements = List.generate(12, (i) => i);
int selectedIndex;
OverlayEntry overlayEntry;
List<LayerLink> layerLinks;
#override
void initState() {
super.initState();
// Creating a layer link for each list cell
layerLinks = List.generate(elements.length, (i) => LayerLink());
}
void createOverlayEntry(int i, BuildContext context) {
// Removing an overlay entry, if there was one
overlayEntry?.remove();
final renderBox = context.findRenderObject() as RenderBox;
final size = renderBox.size;
final offset = renderBox.localToGlobal(Offset.zero);
// Creating a new overlay entry linked to specific list element
overlayEntry = OverlayEntry(
builder: (context) => Positioned(
left: 0,
top: 0,
child: CompositedTransformFollower(
link: layerLinks[i],
showWhenUnlinked: false,
offset: Offset(-20, 0),
child: Material(
color: Colors.yellow,
child: InkWell(
onTap: () {
setState(() {
selectedIndex = null;
});
overlayEntry?.remove();
overlayEntry = null;
},
child: Container(
alignment: Alignment.center,
width: 70,
height: elementHeight,
child: Text('$i')
),
)
),
)
)
);
// Inserting an entry
Overlay.of(context).insert(overlayEntry);
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Container(
height: elementHeight,
child: ListView.separated(
scrollDirection: Axis.horizontal,
itemCount: elements.length,
itemBuilder: (c, i) {
return CompositedTransformTarget(
link: layerLinks[i],
child: Material(
color: Colors.red,
child: InkWell(
onTap: () {
setState(() {
selectedIndex = i;
});
createOverlayEntry(i, context);
},
child: Container(
alignment: Alignment.center,
width: 30,
child: Text('${elements[i]}'),
),
),
),
);
},
separatorBuilder: (c, i) {
return Container(width: 10, height: 10);
},
),
),
);
}
}

unable to update single item background color in Listview.builder in flutter

Im working on flutter listview.builder I just want to change background colour of 3 items in Row widget at where user clicks but its changing colours of all the items in listview. just want to change color at specific index based on user click. here is my code...
import 'dart:convert';
import 'package:dotted_border/dotted_border.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:fluttergyancare/ColorLoader3.dart';
import 'package:fluttergyancare/Models/AddAttendanceModel.dart';
import 'package:fluttergyancare/Models/serializers.dart';
import 'package:gradient_app_bar/gradient_app_bar.dart';
import 'package:http/http.dart' as http;
class AddAttendance extends StatelessWidget {
final String id;
final String section;
final String school;
final String Class;
AddAttendance({this.id, this.section, this.school, this.Class});
#override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Color(0xFFEEEEEE),
appBar: GradientAppBar(
title: Text('Normal'),
centerTitle: true,
backgroundColorStart: Color(0xFFFF9844),
backgroundColorEnd: Color(0xFFFD7267),
),
body: FutureBuilderUI(
id: id,
section: section,
school: section,
Class: Class,
),
);
}
}
Future<AddAttendanceModel> call(http.Client client, String id, String section,
String school, String Class) async {
var send =
await http.post("http://localhost/app/api/get_students", body: {
"teacher_id": "1",
"class_id": id,
"section": section,
"school_name": school,
"Class": Class,
});
return compute(parseJson, (send.body));
}
AddAttendanceModel parseJson(String json) {
final jsonStr = jsonDecode(json);
AddAttendanceModel article = standardSerializers.deserializeWith(
AddAttendanceModel.serializer, jsonStr);
return article;
}
class FutureBuilderUI extends StatelessWidget {
final String id;
final String section;
final String school;
final String Class;
FutureBuilderUI({this.id, this.section, this.school, this.Class});
#override
Widget build(BuildContext context) {
return FutureBuilder<AddAttendanceModel>(
future: call(http.Client(), id, section, school, Class),
builder: (context, snapshot) {
switch (snapshot.connectionState) {
case ConnectionState.none:
case ConnectionState.active:
case ConnectionState.waiting:
return Center(
child: ColorLoader3(
radius: 25.0,
dotRadius: 10.0,
),
);
case ConnectionState.done:
if (snapshot.hasError) print(snapshot.error);
print(snapshot.data.studentsInfo.length.toString() +
" StudentsInfo Length");
if (snapshot.data.studentsInfo.length != 0) {
return snapshot.hasData
? AddAttendanceUI(students: snapshot.data)
: Container();
} else {
return Container(
padding: EdgeInsets.all(10.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
Align(alignment: Alignment.center, child: Text("No..")),
],
),
);
}
}
return null;
// return ListView.builder(
// itemBuilder: (context, index) {
// return ListTile(
// title: Text(snapshot.data[index].status),
// );
// },
// );
},
);
}
}
class AddAttendanceUI extends StatefulWidget {
final AddAttendanceModel students;
AddAttendanceUI({this.students});
#override
_AddAttendanceUIState createState() => _AddAttendanceUIState();
}
class _AddAttendanceUIState extends State<AddAttendanceUI> {
var pColor = Colors.green;
var aColor = Colors.grey;
var nColor = Colors.grey;
int _onSelectedindex = 0;
#override
Widget build(BuildContext context) {
return ListView.builder(
itemCount: widget.students.studentsInfo.length,
itemBuilder: (BuildContext context, int index) {
return Card(
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
children: <Widget>[
SizedBox(width: 10),
Spacer(),
Padding(
padding: const EdgeInsets.only(right: 10.0),
child: Row(
children: <Widget>[
GestureDetector(
child: pSelector(),
onTap: () {
print(index);
setState(() {
// final a = widget.students.studentsInfo
// .where((l) =>
// l.id ==
// widget.students.studentsInfo[index].id)
// .toList();
_onSelectedindex = index;
aColor = Colors.grey;
pColor = Colors.green;
nColor = Colors.grey;
});
},
),
SizedBox(width: 12),
GestureDetector(
onTap: () {
setState(() {
print(widget.students.studentsInfo[index].id);
aColor = Colors.red;
pColor = Colors.grey;
nColor = Colors.grey;
});
},
child: aSelector()),
SizedBox(width: 12),
GestureDetector(
child: nSelector(),
onTap: () {
setState(() {
print(widget.students.studentsInfo[index].id);
aColor = Colors.grey;
pColor = Colors.grey;
nColor = Colors.orange;
});
},
),
],
),
)
],
),
);
});
}
hello() {}
Widget pSelector() {
return Padding(
padding: const EdgeInsets.only(top: 5.0),
child: ClipOval(
child: Container(
color: pColor,
height: 30,
width: 30,
child: DottedBorder(
strokeWidth: 2.5,
borderType: BorderType.Circle,
color: Colors.white,
child: Center(
child: Text(
"A",
style: TextStyle(color: Colors.white),
),
),
),
),
),
);
}
Widget aSelector() {
return Padding(
padding: const EdgeInsets.only(top: 5.0),
child: ClipOval(
child: Container(
color: aColor,
height: 30,
width: 30,
child: DottedBorder(
strokeWidth: 2.5,
borderType: BorderType.Circle,
color: Colors.white,
child: Center(
child: Text(
"B",
style: TextStyle(color: Colors.white),
),
),
),
),
),
);
}
Widget nSelector() {
return Padding(
padding: const EdgeInsets.only(top: 5.0),
child: ClipOval(
child: Container(
color: nColor,
height: 30,
width: 30,
child: DottedBorder(
strokeWidth: 2.5,
borderType: BorderType.Circle,
color: Colors.white,
child: Center(
child: Text(
"C",
style: TextStyle(color: Colors.white),
),
),
),
),
),
);
}
}
see Images below image1 image2 image3
I tried to attach streamBuilder with each single item in listview but not working.
I expect when user taps on A/B/C only at that index colours should be change but its changing colours of all the items
You are using the same 3 variables pColor, aColor, nColor for all the items. You should have a list or a map of these variables, one for each item. Or create a separate widget to handle these variables internally on that widget.