State of each widget in ListView.builder is affecting each other. How to differentiate widgets in ListView.builder? - flutter

I am building an app that has gmail type animation in which when the circular avatar button is clicked the List tile gets selected with a rotation animation.
The list of items are built using ListView.builder. But when the circle avatar of a list item is clicked it affects the whole list of items and rotates every circle avatar. From similar questions asked I changed Listtile into a separate stateful widget as every item could hold its own state. But this doesn't change the output.
I didn't get the output even after using keys(Unique and object). I am stuck here for a long time but couldn't figure what and how to do it. Is there any way to differentiate widgets in Listview.builder and maintain their states seperately?
Alternatively when I use checkbox/selectable icon its holding the state for each item induvidually.
class MailList extends StatefulWidget {
int index;
List<MailModel> account;
Animation rotationAnim;
AnimationController circleAvatarController;
OneShotAnimation riveAnimationController1;
OneShotAnimation riveAnimationController2;
List<int> selectedIndexes = [];
UniqueKey key;
MailList({
required this.index,
required this.account,
required this.rotationAnim,
required this.circleAvatarController,
required this.riveAnimationController1,
required this.riveAnimationController2,
required this.selectedIndexes,
required this.key
}):super(key: key);
#override
_MailListState createState() => _MailListState();
}
class _MailListState extends State<MailList> {
#override
Widget build(BuildContext context) {
return Listener(
key: UniqueKey(),
child: Dismissible(
key: ValueKey(widget.index),
child: GestureDetector(
key: ValueKey(widget.index),
child: Padding(
padding: const EdgeInsets.symmetric(vertical: 10, horizontal: 15.0),
child: Container(
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(20.0),
shape: BoxShape.rectangle,
color: widget.account[widget.index].isSelected ? Colors.blue.withOpacity(0.2): null
),
height: 70,
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
Column(
children: [
GestureDetector(
child: AnimatedBuilder(
builder: (context, child){
return Transform(
alignment: Alignment.center,
child: CircleAvatar(
key: ValueKey(widget.index),
backgroundColor: widget.account[widget.index].isSelected ? widget.account[widget.index].defaultOnSelectedColor : widget.account[widget.index].senderInfo.profileColor.withOpacity(0.75),
child: TweenAnimationBuilder(
tween: Tween<double>(begin: 0, end: 1),
builder: (context, double anim, child){
return widget.rotationAnim.value > 90 && widget.account[widget.index].isSelected
? Opacity(
child: Icon(
widget.account[widget.index].defaultOnSelectedIcon,
color: Colors.white,
),
opacity: anim,
)
: Text(
widget.account[widget.index].senderInfo.senderName[0].toString(),
style: const TextStyle(
fontWeight: FontWeight.bold,
fontSize: 21.0,
color: Colors.white
));
},
duration: const Duration(seconds: 1),
),
),
transform: /*isSelected(widget.index) ? (Matrix4.identity()
..setEntry(3, 2, 0.001)
..rotateY((widget.rotationAnim.value) / 180 * math.pi))
: (Matrix4.identity()
..setEntry(3, 2, 0.001)
..rotateY(0 / 180 * math.pi))*/
Matrix4.identity()
..setEntry(3, 2, 0.001)
..rotateY((widget.rotationAnim.value) / 180 * math.pi),
);
},
animation: widget.circleAvatarController,
),
onTap: (){
selectUnSelect(widget.index);
widget.account[widget.index].isSelected = !widget.account[widget.index].isSelected;
widget.account[widget.index].isSelected ? widget.circleAvatarController.forward() : widget.circleAvatarController.reverse();
setState(() {
});
},
)
],
mainAxisAlignment: MainAxisAlignment.start,
),
const SizedBox(
width: 10.0,
),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Expanded(
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
widget.account[widget.index].senderInfo.senderName,
style: TextStyle(
fontSize: 16.0,
fontWeight: widget.account[widget.index].isSeen
? FontWeight.normal
: FontWeight.bold
),
),
Text(
"10:00"
)
],
)
),
const SizedBox(
height: 5.0,
),
Text(
widget.account[widget.index].title,
overflow: TextOverflow.ellipsis,
style: TextStyle(
fontSize: 14.5,
fontWeight: widget.account[widget.index].isSeen
? FontWeight.normal
: FontWeight.bold
),
),
const SizedBox(
height: 5.0,
),
Expanded(
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
Expanded(
child: Text(
widget.account[widget.index].content,
overflow: TextOverflow.ellipsis,
),
),
GestureDetector(
child: Container(
child: widget.account[widget.index].starred ? Icon(
Icons.star,
color: Colors.orange[300],
):
Icon(
Icons.star_border
),
),
onTap: (){
setState(() {
widget.account[widget.index].starred = !widget.account[widget.index].starred;
});
},
)
],
),
),
const SizedBox(
height: 5.0,
),
],
),
)
],
),
),
),
onTap: (){
},
),
background: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Expanded(
child: dragWidget(
left: 8.0,
controller: widget.riveAnimationController1
)),
],
),
secondaryBackground: Container(
alignment: Alignment.centerRight,
color: Colors.green,
child: Padding(
padding: EdgeInsets.only(
right: 8.0
),
child: SizedBox(
height: 50.0,
width: 50.0,
child: RiveAnimation.asset(
"assets/gmail_drag_anim.riv",
controllers: [
widget.riveAnimationController2
],
animations: [
"Animation 1"
],
fit: BoxFit.fill,
//antialiasing: false,
),
),
),
),
onDismissed: (direction){
widget.account.removeAt(widget.index);
},
),
);
}
bool isSelected(int index){
return widget.selectedIndexes.contains(index);
}
selectUnSelect(int index){
if(isSelected(index)){
widget.circleAvatarController.reverse();
Future.delayed(Duration(milliseconds: 320), (){
widget.selectedIndexes.remove(index);
setState(() {
});
});
}else{
widget.selectedIndexes.add(index);
}
}
Widget dragWidget({
double? left,
double? right,
required OneShotAnimation controller
}){
return Container(
alignment: right != null ? Alignment.centerRight : Alignment.centerLeft,
color: Colors.green,
child: Padding(
padding: EdgeInsets.only(
left: left != null ? left : 0.0,
right: right != null ? right: 0.0
),
child: SizedBox(
height: 50.0,
width: 50.0,
child: RiveAnimation.asset(
"assets/gmail_drag_anim.riv",
controllers: [
controller
],
animations: [
"Animation 1"
],
fit: BoxFit.fill,
//antialiasing: false,
),
),
),
);
}
} ```
// ListView.builder:
SliverList(
delegate: SliverChildBuilderDelegate(
(context, index){
return MailList(
key: GlobalKey<_MailListState>(),
index: index,
account: account1_mail_data,
rotationAnim: rotationAnim,
circleAvatarController: circleAvatarController,
riveAnimationController1: _riveAnimationController1,
riveAnimationController2: _riveAnimationController2,
selectedIndexes: selectedIndexes
);
},
childCount: account1_mail_data.length
),
)

Related

How to avoid RenderFlow error when keyboard appears in Flutter

I am designing a screen with two main sections:
A carousel slider with a TextField below it
A button
I want the button to be at the bottom of the screen.
To achieve this, I have wrapped the column for 1. in a Flexible widget and set the mainAxisSize for the Column to max, and the mainAxis size for the Column containing the button to min.
Now when I click on the TextField, the keyboard appears, and I receive a Bottom Overflowed error with the Button appearing on top of the TextField.
How do I ensure that the Button stays at the bottom of the screen when the keyboard appears? I have tried wrapping both the Columns in another Column which in turn had been wrapped by a SingleChildScrollView widget, but that overrides the MainAxisSize.max property apparently, and renders the lower Column (containing the Button) just below the upper Column as seen below.
My Code:
class SigninScreen extends StatefulWidget {
const SigninScreen({super.key});
#override
State<SigninScreen> createState() => _SigninScreenState();
}
class _SigninScreenState extends State<SigninScreen> {
int _currentCarouselIndex = 0;
List<Widget> indicators(imagesLength, currentIndex) {
return List<Widget>.generate(imagesLength, (index) {
return Container(
margin: EdgeInsets.symmetric(
vertical: 1.h,
horizontal: 2,
),
width: currentIndex == index ? 15 : 10,
height: 3,
decoration: BoxDecoration(
color: currentIndex == index ? primary : grey,
borderRadius: const BorderRadius.all(
Radius.circular(2),
),
),
);
});
}
TextEditingController phoneController = TextEditingController();
#override
void dispose() {
phoneController.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: SafeArea(
child: GestureDetector(
onTap: (){ FocusManager.instance.primaryFocus?.unfocus();},
behavior: HitTestBehavior.opaque,
child: SingleChildScrollView(
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Flexible(
fit: FlexFit.loose,
child: Column(
mainAxisSize: MainAxisSize.max,
children: [
CarouselSlider(
items: carouselImageList.map<Widget>((i){
return Builder(
builder: (context){
return Container(
width: 100.w,
height: 83.w,
decoration: BoxDecoration(
image: DecorationImage(image: AssetImage(i), fit: BoxFit.fill),
),
);
}
);
}).toList(),
options: CarouselOptions(
height: 83.w,
aspectRatio: 1/0.83,
autoPlay: true,
autoPlayInterval: const Duration(seconds: 3),
initialPage: 0,
viewportFraction: 1,
onPageChanged: (index, timed) {
setState(() {
_currentCarouselIndex = index;
});
}
),
),
SizedBox(height: 1.h,),
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: indicators(
carouselImageList.length, _currentCarouselIndex),
),
SizedBox(height: 2.h,),
Padding(
padding: EdgeInsets.symmetric(horizontal: 5.w),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
Text(isEnglish ? "Enter your phone number" : "अपना फ़ोन नंबर दर्ज करें", style: globalTextStyle.copyWith(fontSize: 5.w, fontWeight: FontWeight.bold),),
],
),
SizedBox(height: 1.h),
PhoneNumberField(phoneController: phoneController),
SizedBox(height: 1.h),
Text(isEnglish ? "OTP will be sent on this number." : "इस नंबर पर ओटीपी भेजा जाएगा।", style: globalTextStyle.copyWith(fontSize: 3.w,),),
],
),
),
],
),
),
Column(
mainAxisSize: MainAxisSize.min,
children: [
CustomButton(width: 90.w, height: 15.w, color: primary, onTap: (){
// Navigator.pushNamed(context, otp);
(phoneController.text.length == 10) ?
Navigator.push(context, MaterialPageRoute(builder: (context) =>
OTPScreen(phoneNumber: "+91${phoneController.text}")))
: ShowSnackbar.showSnackBar(context, isEnglish ? "Enter a valid 10 digit phone number." : "एक मान्य 10 अंकों का फ़ोन नंबर दर्ज करें।");
}, text: isEnglish ? "Get OTP" : "ओटीपी प्राप्त करें", fontColor: white, borderColor: primary,),
SizedBox(height: 1.h,),
Text(isEnglish ? "By signing up, you agree to our Terms and Services" : "साइन अप करके, आप हमारी शर्तों से सहमत होते हैं और सेवाएं", style: globalTextStyle.copyWith(fontSize: 2.5.w,),),
SizedBox(height: 2.h,),
],
),
],
),
),
),
),
);
}
}
How do I get the Button to stay at the bottom of the screen even when the keyboard appears?
Remove the Flexible and try the below code.
#override
Widget build(BuildContext context) {
return Scaffold(
body: SafeArea(
child: GestureDetector(
onTap: () {
FocusManager.instance.primaryFocus?.unfocus();
},
behavior: HitTestBehavior.opaque,
child: SingleChildScrollView(
child: SizedBox(
height: MediaQuery.of(context).size.height,
width: MediaQuery.of(context).size.width,
child: Column(
children: [
CarouselSlider(
items: carouselImageList.map<Widget>((i) {
return Builder(builder: (context) {
return Container(
width: 100.w,
height: 100.w,
decoration: BoxDecoration(
image: DecorationImage(
image: AssetImage(i), fit: BoxFit.fill),
),
);
});
}).toList(),
options: CarouselOptions(
height: 83.w,
aspectRatio: 1 / 0.83,
autoPlay: true,
autoPlayInterval: const Duration(seconds: 3),
initialPage: 0,
viewportFraction: 1,
onPageChanged: (index, timed) {
setState(() {
_currentCarouselIndex = index;
});
}),
),
SizedBox(
height: 1.h,
),
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: indicators(
carouselImageList.length, _currentCarouselIndex),
),
SizedBox(
height: 2.h,
),
Padding(
padding: EdgeInsets.symmetric(horizontal: 5.w),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
Text(
"Enter your phone number",
style: TextStyle(
fontSize: 5.w, fontWeight: FontWeight.bold),
),
],
),
SizedBox(height: 1.h),
TextFormField(
controller: phoneController,
keyboardType: TextInputType.phone,
),
SizedBox(height: 1.h),
Text(
"OTP will be sent on this number.",
style: TextStyle(
fontSize: 3.w,
),
),
],
),
),
Expanded(
child: Container(),
),
Column(
mainAxisSize: MainAxisSize.min,
children: [
CustomButton(
width: 90.w,
height: 15.w,
color: primary!,
onTap: () {},
text: "Get OTP",
fontColor: white!,
borderColor: primary!,
),
SizedBox(
height: 1.h,
),
Text(
"By signing up, you agree to our Terms and Services",
style: TextStyle(
fontSize: 2.5.w,
),
),
SizedBox(
height: 2.h,
),
],
),
],
),
),
),
),
),
);
}
In your Scaffold set resizeToAvoidBottomInset: property to false I think this will help. Otherwise I observed that the render overflow error is from the column that contains the PhoneNumberField so try Increasing the height of the SizedBox after the PhoneNumberField if the first method doesn't work.

Flutter: RenderFlex children have non-zero flex but incoming height constraints are unbounded. Using Expandable listview

I want to design this layout:
I am using a expandable listview inside a column. I was add ListView.builder inside Expanded but still problem continue.
What is the wrong?
This is my code:
Parent page:
class LearningCoursePage extends StatefulWidget {
String courseId;
LearningCoursePage({Key? key, required this.courseId}) : super(key: key);
#override
State<LearningCoursePage> createState() => _LearningCoursePageState();
}
class _LearningCoursePageState extends State<LearningCoursePage> {
CourseModel? courseModel;
Future<void> _loadResource() async {
courseModel = await Get.find<CourseController>().getCourseById(widget.courseId);
}
#override
Widget build(BuildContext context) {
return FutureBuilder(
future: _loadResource(),
builder: (BuildContext context, AsyncSnapshot snapshot) {
return Scaffold(
appBar: AppBar(
centerTitle: true,
backgroundColor: AppColors.yellowColor,
title: courseModel != null ? Text(courseModel!.name.toString()) : Text("Loading..."),
leading: IconButton(
icon: Icon(Icons.close),
onPressed: () {},
),
),
body: GetBuilder<AuthController>(builder: (authController) {
return authController.userLoggedIn() ? Container(
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(Dimensions.radius20),
boxShadow: [
BoxShadow(
color: Color(0xFFe8e8e8),
blurRadius: 5.0,
offset: Offset(0, 5)
),
BoxShadow(
color: Colors.white,
offset: Offset(-5, 0)
),
BoxShadow(
color: Colors.white,
offset: Offset(5, 0)
),
]
),
child: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
SizedBox(height: 40,),
BigText(text: "Vui lòng đăng nhập để xác minh tài khoản"),
SizedBox(height: 20,),
SizedBox(height: 20,),
GestureDetector(
onTap: (){
Get.toNamed(RouteHelper.getSignInPage());
},
child: Container(
width: Dimensions.screenWidth/2,
height: Dimensions.screenHeight/13,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(Dimensions.radius15),
color: AppColors.yellowColor
),
child: Center(
child: BigText(
text: "ĐĂNG NHẬP",
size: Dimensions.font20+Dimensions.font20/2,
color: Colors.white,
),
),
),
),
SizedBox(height: 40,),
],
),
),
) :
SingleChildScrollView(
child: Container(
child: Column(
children: [
Container(
height: 400,
decoration: BoxDecoration(
image: DecorationImage(
fit: BoxFit.cover,
image: AssetImage("assets/image/test.jpg")
)
),
),
Container(
width: double.infinity,
color: AppColors.mainBlackColor,
child: Container(
margin: EdgeInsets.only(left: Dimensions.width20, right: Dimensions.width20),
child: Column(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
SizedBox(height: 30,),
BigText(text: (courseModel != null ? courseModel!.name.toString().toUpperCase() : "Loading..."), size: 20, color: AppColors.whiteColor,),
SizedBox(height: 30,),
BigText(text: "Bạn hoàn thành 3 trong 59 bài giảng", size: 16, color: AppColors.whiteColor,),
SizedBox(height: 30,),
LinearPercentIndicator(
animation: true,
animationDuration: 1000,
lineHeight: 40.0,
backgroundColor: Colors.white,
percent: 0.2,
padding: EdgeInsets.only(right: 0),
center: Text("20.0%"),
trailing: Container(
color: AppColors.yellowColor,
padding: EdgeInsets.only(left: 10, right: 10),
alignment: Alignment.center,
height: 40,
child: SmallText(text: "hoàn thành", size: 16, color: AppColors.whiteColor,),
),
linearStrokeCap: LinearStrokeCap.butt,
progressColor: AppColors.yellowColor,
),
SizedBox(height: 30,),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
SelectButtonWidget(text: 'Vào học ngay', isSelect: true, width: Dimensions.screenWidth/2.5,),
SelectButtonWidget(text: 'Hướng dẫn học', width: Dimensions.screenWidth/2.5,),
],
),
SizedBox(height: 30,),
SelectButtonWidget(text: 'Xem mẫu chứng chỉ hoàn thành',),
SizedBox(height: 60,),
],
),
)
),
Container(
width: double.infinity,
color: AppColors.greyColor,
child: Container(
margin: EdgeInsets.only(left: Dimensions.width20, right: Dimensions.width20),
child: Column(
children: [
SizedBox(height: 30,),
LearningCourseBody(courseModel: courseModel,)
],
),
),
)
],
),
),
);
}),
);
},
);
}
}
The body page (cluster layout with gray background color):
class LearningCourseBody extends StatefulWidget {
CourseModel? courseModel;
LearningCourseBody({Key? key, this.courseModel}) : super(key: key);
#override
State<LearningCourseBody> createState() => _LearningCourseBodyState();
}
class _LearningCourseBodyState extends State<LearningCourseBody> {
#override
Widget build(BuildContext context) {
return Column(
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
SelectButtonWidget(text: 'Tổng quan', isSelect: true, width: Dimensions.screenWidth/3.5,),
SelectButtonWidget(text: 'Bài học', textColor: AppColors.mainBlackColor, width: Dimensions.screenWidth/3.5,),
SelectButtonWidget(text: 'Tài liệu', textColor: AppColors.mainBlackColor, width: Dimensions.screenWidth/3.5,),
],
),
SizedBox(height: 30,),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
SizedBox(width: 5,),
SelectButtonWidget(text: 'Hỏi & đáp', textColor: AppColors.mainBlackColor, width: Dimensions.screenWidth/3.5,),
SizedBox(width: 5,),
SelectButtonWidget(text: 'Ghi chép', textColor: AppColors.mainBlackColor, width: Dimensions.screenWidth/3.5,),
SizedBox(width: 5,),
],
),
SizedBox(height: 60,),
// OverviewCourse(courseModel: widget.courseModel, rating: 5,)
(widget.courseModel != null && widget.courseModel!.particalsCourse != null) ? Expanded(child: ExpandableListViewWidget(courseModel: widget.courseModel!,)) : Text('Loading....'),
],
);
}
}
And this is the expandable lisview:
class ExpandableListViewWidget extends StatefulWidget {
CourseModel courseModel;
ExpandableListViewWidget({Key? key, required this.courseModel}) : super(key: key);
#override
State<ExpandableListViewWidget> createState() => _ExpandableListViewWidgetState();
}
class _ExpandableListViewWidgetState extends State<ExpandableListViewWidget> {
List<Widget> _buildExpandList(int index) {
List<Widget> res = [];
if (widget.courseModel.particalsCourse![index].lessons == null) return res;
for (var sub in widget.courseModel.particalsCourse![index].lessons!) {
var s = ListTile(
title: Text(sub.title!),
leading: Icon(Icons.play_circle),
trailing: sub.isTrial! ? SmallText(text: 'học thử', color: AppColors.yellowColor,) : Text(sub.timeLearning!.toString()),
);
res.add(s);
}
return res;
}
#override
Widget build(BuildContext context) {
List<bool> tileExpanded = List<bool>.generate(widget.courseModel.particalsCourse!.length, (index) => false);
return ListView.builder(
itemCount: widget.courseModel.particalsCourse!.length,
itemBuilder: (context, index) {
return Container(
child: ExpansionTile(
title: Text(widget.courseModel.particalsCourse![index].title!),
controlAffinity: ListTileControlAffinity.leading,
leading: Icon(
tileExpanded[index] ? Icons.arrow_drop_up : Icons.arrow_drop_down
),
children: _buildExpandList(index),
onExpansionChanged: (bool expanded) {
setState(() => tileExpanded[index] = expanded);
},
),
);
}
);
}
}
Hope your help? Thanks!!!
Finally, I was resolve this problem. This is my step:
Step 1: Column use mainAxisSize: MainAxisSize.min
Step 2: Change Expanded to Flexible
Step 3: Adding this two lines in ExpandableListViewWidget
ListView.builder(
scrollDirection: Axis.vertical,
shrinkWrap: true,
...

How to stack the items inside a "Listview.builder" on top of each other? flutter

I want to stack the items inside a Listview.builder but when I try to do so with the Align() class and set its heightfactor: , it gives padding to the top and bottom and I don't want that, does anyone have any idea how do I stack them properly?
My center SyncfusionLinearGauge:
//Center SyncfusionLinearGauge
Expanded(
child: ScrollablePositionedList.builder(
itemScrollController: itemController,
itemCount: _provider.loadedContestants.length,
scrollDirection: Axis.vertical,
shrinkWrap: true,
itemBuilder: (context, index) {
final data = _provider.loadedContestants[index];
return Align(
heightFactor: 0.0001,
child: SfLinearGauge(
// showLabels: false,
// showTicks: false,
// showAxisTrack: false,
axisLabelStyle: TextStyle(color: Colors.white),
majorTickStyle: LinearTickStyle(
color: Colors.white, length: 10),
minorTickStyle:
LinearTickStyle(color: Colors.white),
axisTrackStyle:
LinearAxisTrackStyle(color: Colors.red),
orientation: LinearGaugeOrientation.vertical,
minimum: 0,
maximum: 100,
animationDuration: 500,
markerPointers: [
//User pointer
LinearWidgetPointer(
value: data.points,
position: LinearElementPosition.cross,
offset: 50.0,
animationDuration: 2000,
child: UserCardWidget(
data: data,
),
),
],
),
);
},
),
),
My user card:
class UserCardWidget extends StatelessWidget {
UserCardWidget({Key key, this.data}) : super(key: key);
DemoUserModel data;
#override
Widget build(BuildContext context) {
bool _isKevinTeam = data.team == 'Kevin';
return SingleChildScrollView(
child: Padding(
padding: const EdgeInsets.only(left: 167.0),
child: Row(
mainAxisAlignment: MainAxisAlignment.start,
// mainAxisSize: MainAxisSize.min,
children: [
Container(
width: 15,
height: 15,
decoration: BoxDecoration(
color: const Color(0xff2da162),
borderRadius: BorderRadius.all(
Radius.circular(50),
),
),
),
Card(
child: Padding(
padding: const EdgeInsets.all(5.0),
child: Text(
'${data.firstName.toString()}M',
style: TextStyle(
color: Colors.black,
fontWeight: FontWeight.w700,
fontFamily: "SFProText",
fontStyle: FontStyle.normal,
fontSize: 13.0,
),
),
),
),
],
),
),
);
}
}
What I want:
What I have:
What I ended up doing was removing the listview.builder all together and mapped the list in the markerPointers:[], so this is the result.
My LinearGauge:
Expanded(
flex: 4,
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Expanded(
child: SizedBox(
height: size.height * 0.57,
child: SfLinearGauge(
showLabels: false,
showTicks: false,
showAxisTrack: false,
orientation: LinearGaugeOrientation.vertical,
minimum: 0,
maximum: 100,
animationDuration: 500,
markerPointers: _sortedList
.asMap()
.map(
(i, data) {
return MapEntry(
i++,
LinearWidgetPointer(
value: data.points,
position: LinearElementPosition.cross,
animationDuration: 2000,
offset: 500,
child: UserCardWidget(
data: data,
index: i,
isExpandedMethod: _isExpanded,
),
),
);
},
)
.values
.toList(),
),
),
),
],
),
),
This is the result:

How to navigate with the side menu with just one screen

I have a demand to perform the navigation of this left side menu. This screen is already divided into 3, but I need the side menu only to update the second (middle). And I need the third one to receive the index data from the second (middle).
main_scren.dart
class MainScreen extends StatelessWidget {
#override
Widget build(BuildContext context) {
// It provide us the width and height
Size _size = MediaQuery.of(context).size;
return Scaffold(
body: Responsive(
// Let's work on our mobile part
mobile: ListOfChannels(),
tablet: Row(
children: [
Expanded(
flex: 6,
child: ListOfChannels(),
),
Expanded(
flex: 9,
child: ChannelScreen(),
),
],
),
desktop: Row(
children: [
// Once our width is less then 1300 then it start showing errors
// Now there is no error if our width is less then 1340
Expanded(
flex: _size.width > 1340 ? 2 : 4,
child: SideMenu(),
),
Expanded(
flex: _size.width > 1340 ? 3 : 5,
child: ListOfChannels(),
),
Expanded(
flex: _size.width > 1340 ? 8 : 10,
child: ChannelScreen(),
),
],
),
),
);
}
}
Here is the code for my side_menu.dart
#override
Widget build(BuildContext context) {
return Container(
height: double.infinity,
padding: EdgeInsets.only(top: kIsWeb ? kDefaultPadding : 0),
color: kBgLightColor,
child: SafeArea(
child: SingleChildScrollView(
padding: EdgeInsets.symmetric(horizontal: kDefaultPadding),
child: Column(
children: [
Row(
children: [
Spacer(),
Image.asset(
"assets/images/logo_lado.png",
width: 100,
),
Spacer(),
// We don't want to show this close button on Desktop mood
if (!Responsive.isDesktop(context)) CloseButton(),
],
),
SizedBox(height: kDefaultPadding),
SizedBox(height: kDefaultPadding),
CircleAvatar(
maxRadius: 65,
backgroundColor: Colors.transparent,
backgroundImage: AssetImage("assets/images/user_3.png"),
),
SizedBox(height: kDefaultPadding),
SizedBox(height: kDefaultPadding),
FlatButton.icon(
minWidth: double.infinity,
padding: EdgeInsets.symmetric(
vertical: kDefaultPadding,
),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(10),
),
color: kPrimaryColor,
onPressed: () {},
icon: WebsafeSvg.asset("assets/Icons/Edit.svg", width: 16),
label: Text(
"Meu Perfil",
style: TextStyle(color: Colors.white),
),
).addNeumorphism(
topShadowColor: Colors.white,
bottomShadowColor: Color(0xFF234395).withOpacity(0.2),
),
SizedBox(height: kDefaultPadding),
FlatButton.icon(
minWidth: double.infinity,
padding: EdgeInsets.symmetric(
vertical: kDefaultPadding,
),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(10),
),
color: kBgDarkColor,
onPressed: () async {
bool saiu = await logout();
if (saiu) {
Navigator.pushReplacement(
context,
MaterialPageRoute(builder: (context) => LoginPage())
);
}
},
icon: WebsafeSvg.asset("assets/Icons/Download.svg", width: 16),
label: Text(
"Sair",
style: TextStyle(color: kTextColor),
),
).addNeumorphism(),
SizedBox(height: kDefaultPadding * 2),
// Menu Items
SideMenuItem(
press: () {
},
title: "On Demand",
iconSrc: "assets/Icons/new_releases_black_24dp.svg",
isActive: false,
//itemCount: 3,
),
SideMenuItem(
press: () {},
title: "Assistir TV",
iconSrc: "assets/Icons/tv_black_24dp.svg",
isActive: true,
),
SideMenuItem(
press: () {},
title: "Favoritos",
iconSrc: "assets/Icons/star_border_black_24dp.svg",
isActive: false,
),
//SizedBox(height: kDefaultPadding * 2),
// Tags
//Tags(),
],
),
),
),
);
And this is the screen that is in error, from which I need to receive the channel list data.
channel_screen.dart
class ChannelScreen extends StatelessWidget {
const ChannelScreen({
Key key,
this.channel,
}) : super(key: key);
final Channel channel;
#override
Widget build(BuildContext context) {
return Scaffold(
body: Container(
color: Colors.white,
child: SafeArea(
child: Column(
children: [
//Header(),
//Divider(thickness: 1),
Expanded(
child: SingleChildScrollView(
padding: EdgeInsets.all(kDefaultPadding),
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
CircleAvatar(
maxRadius: 24,
backgroundColor: Colors.transparent,
backgroundImage: NetworkImage(channel.midiaImagemUrl),
),
SizedBox(width: kDefaultPadding),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
Expanded(
child: Column(
crossAxisAlignment:
CrossAxisAlignment.start,
children: [
Text.rich(
TextSpan(
text: channel.midiaTitulo,
style: Theme.of(context)
.textTheme
.button,
children: [
TextSpan(
text:
"",
style: Theme.of(context)
.textTheme
.caption),
],
),
),
Text(
channel.midiaTitulo,
style: Theme.of(context)
.textTheme
.headline6,
)
],
),
),
SizedBox(width: kDefaultPadding / 2),
Text(
"10:30 - 12:00",
style: Theme.of(context).textTheme.caption,
),
],
),
SizedBox(height: kDefaultPadding),
LayoutBuilder(
builder: (context, constraints) => SizedBox(
width: constraints.maxWidth > 850
? 800
: constraints.maxWidth,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
"Aqui ficará o EPG List",
style: TextStyle(
height: 1.5,
color: Color(0xFF4D5875),
fontWeight: FontWeight.w300,
),
),
SizedBox(height: kDefaultPadding),
Divider(thickness: 1),
SizedBox(height: kDefaultPadding / 2),
SizedBox(
height: 500,
width: 500,
child: StaggeredGridView.countBuilder(
physics: NeverScrollableScrollPhysics(),
crossAxisCount: 1,
itemCount: 1,
itemBuilder:
(BuildContext context, int index) =>
ClipRRect(
borderRadius:
BorderRadius.circular(8),
child: Image.asset(
"assets/images/Img_$index.png",
fit: BoxFit.cover,
),
),
staggeredTileBuilder: (int index) =>
StaggeredTile.count(
2,
index.isOdd ? 2 : 1,
),
mainAxisSpacing: kDefaultPadding,
crossAxisSpacing: kDefaultPadding,
),
)
],
),
),
),
],
),
),
],
),
),
)
],
),
),
),
);
}
}
Any help will be appreciated.
The basic problem here is that ChannelScreen has a final channel property, but the property isn't passed into the constructor - so it's always null when you try to access it.
The most straightforward solution is to convert your MainScreen to a StatefulWidget, with a mutable channel property. Then add a function onChannelSelected to ListOfChannels to handle when a user selects a new channel from that view - You could then update the state from that handler.
For a bare minimum example:
class ListOfChannels extends StatelessWidget {
final void Function(Channel) onChannelSelected;
ListOfChannels({required this.onChannelSelected});
#override
Widget build(BuildContext context) {
return Column(
children: channels.map((channel) => FlatButton(
onPressed: () { this.onChannelSelected(channel) },
))).toList(),
);
}
}
class _MainScreenState extends State<MainScreen> {
Channel channel; // Initialize to an appropriate value, or handle null case
#override
Widget build(BuildContext context) {
// Desktop section
Row(
children: [
// Once our width is less then 1300 then it start showing errors
// Now there is no error if our width is less then 1340
Expanded(
flex: _size.width > 1340 ? 2 : 4,
child: SideMenu(),
),
Expanded(
flex: _size.width > 1340 ? 3 : 5,
child: ListOfChannels(
onChannelSelected: (channel) {
setState(() { this.channel = channel; });
}
),
),
Expanded(
flex: _size.width > 1340 ? 8 : 10,
child: ChannelScreen(channel: channel),
),
],
),
}
}

Flutter UI List Item

I am using the below code but not able to achieve the desired result, I am new to the flutter world so let me know where to improve to get the desired result. Here is the source code of what I have done.
return Expanded(
child: GridView.builder(
controller: _scrollController,
gridDelegate:
SliverGridDelegateWithFixedCrossAxisCount(crossAxisCount: 2),
itemCount: albumList.length,
itemBuilder: (BuildContext context, int index) {
return buildRow(index);
},
),
);
Widget buildRow(int index) {
return AlbumTile(
index: index,
albumList: albumList,
deleteAlbum: _deleteAlbum,
);
}
This the Album Tile
class AlbumTile extends StatelessWidget {
AlbumTile(
{Key key,
#required this.index,
#required this.albumList,
#required this.deleteAlbum})
: super(key: key);
final int index;
final List<Album> albumList;
final Function deleteAlbum;
#override
build(BuildContext context) {
String thumb;
if (albumList.elementAt(index).thumbUrl != "") {
thumb = WEBSERVICE_IMAGES +
albumList.elementAt(index).userId.toString() +
'/' +
albumList.elementAt(index).id.toString() +
'/' +
albumList.elementAt(index).thumbUrl;
} else {
thumb = "https://cdn.pixabay.com/photo/2015/12/01/20/28/road-1072823__340.jpg";
}
return GestureDetector(
onTap: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) =>
new AlbumPage(tabIndex: index, albumList: albumList),
),
);
},
child: Container(
// height of the card which contains full item
height: MediaQuery.of(context).size.height * 0.4,
width: MediaQuery.of(context).size.width * 0.28,
// this is the background image code
decoration: BoxDecoration(
image: DecorationImage(
fit: BoxFit.cover,
// url is my own public image url
image: NetworkImage(thumb),
),
borderRadius: BorderRadius.circular(12.0)),
// this is the item which is at the bottom
child: Align(
// aligment is required for this
alignment: Alignment.bottomLeft,
// items height should be there, else it will take whole height
// of the parent container
child: Container(
padding: EdgeInsets.only(left: 10.0, right: 0.0),
height: MediaQuery.of(context).size.height * 0.1,
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Expanded(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
// Text() using hard coded text right now
Text(albumList.elementAt(index).name,
style: TextStyle(
fontSize: 18.5,
color: Colors.white,
fontWeight: FontWeight.w500)),
SizedBox(height: 3.0),
Text(albumList.elementAt(index).photos.toString() +' photos',
style: TextStyle(
fontSize: 12.5,
color: Colors.white,
fontWeight: FontWeight.w500))
],
),
),
// pop-up item
PopupMenuButton(
icon: Icon(Icons.more_vert, color: Colors.white),
itemBuilder: (_) => <PopupMenuItem<String>>[
new PopupMenuItem<String>(
child: Row(
children: <Widget>[
Icon(Icons.delete),
Text(
'Delete Album',
),
],
),
value: 'Delete',
),
],
onSelected: (value) {
deleteAlbum(albumList.elementAt(index).id, index);
},
),
],
),
),
),
),
);
GestureDetector(
onTap: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) =>
new AlbumPage(tabIndex: index, albumList: albumList),
),
);
},
child: Padding(
padding: const EdgeInsets.all(12.0),
child: Stack(
fit: StackFit.loose,
children: <Widget>[
ClipRRect(
borderRadius: new BorderRadius.circular(8.0),
child: FadeInImage.assetNetwork(
height: 1000,
placeholder: kPlaceHolderImage,
image: thumb,
fit: BoxFit.cover,
),
),
Align(
alignment: Alignment.bottomLeft,
child: Row(
children: [
Expanded(
child: Padding(
padding: const EdgeInsets.fromLTRB(8, 0, 0, 0),
child: Text(
albumList.elementAt(index).name,
style: TextStyle(
color: Colors.white,
fontSize: 18.5,
),
textAlign: TextAlign.left,
),
),
),
PopupMenuButton(
icon: ImageIcon(
AssetImage("graphics/horizontal_dots.png"),
color: Colors.white,
),
itemBuilder: (_) => <PopupMenuItem<String>>[
new PopupMenuItem<String>(
child: Row(
children: <Widget>[
Icon(Icons.delete),
Text(
'Delete Album',
),
],
),
value: 'Delete',
),
],
onSelected: (value) {
deleteAlbum(albumList.elementAt(index).id, index);
}),
],
),
),
],
),
),
);
}
}
Thank you in advance.
Welcome to the flutter. Amazing platform to start you career on building cross-platform mobile applications.
My code will look a bit different to you, but trust me, this will work out for you.
Please note: You need to change some parts, like changing the image url for NetworkImage(), onTap function, Text() content etc. But not much changes in the Whole Widget code. So please look for those, and make changes accordingly. You will get there :)
GestureDetector(
onTap: () => print('Works!'), // <-- onTap change, I have used print()
child: Container(
// height of the card which contains full item
height: MediaQuery.of(context).size.height * 0.4,
width: MediaQuery.of(context).size.width * 0.28,
// this is the background image code
decoration: BoxDecoration(
image: DecorationImage(
fit: BoxFit.cover,
// url is my own public image url
image: NetworkImage('https://cdn.pixabay.com/photo/2015/12/01/20/28/road-1072823__340.jpg')
),
borderRadius: BorderRadius.circular(12.0)
),
// this is the item which is at the bottom
child: Align(
// aligment is required for this
alignment: Alignment.bottomLeft,
// items height should be there, else it will take whole height
// of the parent container
child: Container(
padding: EdgeInsets.only(left: 10.0, right: 0.0),
height: MediaQuery.of(context).size.height * 0.1,
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Expanded(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
// Text() using hard coded text right now
Text('Potraits', style: TextStyle(fontSize: 20.0, color: Colors.white, fontWeight: FontWeight.w500)),
SizedBox(height: 3.0),
Text('150 photos', style: TextStyle(fontSize: 17.0, color: Colors.white, fontWeight: FontWeight.w500))
]
)
),
// pop-up item
PopupMenuButton(
icon: Icon(Icons.more_vert, color: Colors.white),
itemBuilder: (_) => <PopupMenuItem<String>>[
new PopupMenuItem<String>(
child: Row(
children: <Widget>[
Icon(Icons.delete),
Text(
'Delete Album',
),
],
),
value: 'Delete',
),
],
onSelected: (value) {
//your function
}
)
]
)
)
)
)
)
Result
EDITS FOR WHOLE UI
So, the change required is in your buildRow Widget. You just need to give some paddings on your sides, and you are pretty much solid. Let me know
Widget buildRow(int index) {
return Padding(
padding: EdgeInsets.all(10.0),
child: AlbumTile(
index: index,
albumList: albumList,
deleteAlbum: _deleteAlbum,
)
);
}
And if you are unsatisfied with the spacings, just keep playing with the EdgeInsets painting class. I hope that helps. Please let me know. :)