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),
),
],
),
}
}
Related
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.
I Have an (infinitepagedgridview) which I constructed using the "infinite_scroll_pagination" Library
everything is running perfectly for me but I'm facing an issue using the pagingcontroller.refresh()
method, which is triggered using a button which changes a variable state which controls a specific parameter that causes different api results in the (infinitepagedgridview), My issue is that whenever I click the button, the pagingcontroller gets refreshed but the first three elements in the gridview which are the first page results come from the last api request before the button got pressed and the rest of the paginated pages work perfectly.
button code:
onTap: () async {
setState(() => FFAppState().homecat = 'Bags');
setState(() => _pagingController?.refresh());
}
listcode and api call:
child: GestureDetector(
onTap: () => FocusScope.of(context).unfocus(),
child: RefreshIndicator(
onRefresh: () async {
setState(() => _pagingController?.refresh());
await waitForOnePage();
},
child: PagedGridView<ApiPagingParams, dynamic>(
physics: NeverScrollableScrollPhysics(),
shrinkWrap: true,
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
mainAxisSpacing: 0.0,
crossAxisSpacing: 5.0,
crossAxisCount: 2,
childAspectRatio: 0.59,
),
pagingController: () {
if (_pagingController != null) {
return _pagingController!;
}
_pagingController = PagingController(
firstPageKey: ApiPagingParams(
nextPageNumber: 0,
numItems: 0,
lastResponse: null,
),
);
_pagingController!
.addPageRequestListener((nextPageMarker) {
GetAllProductsCall.call(
offset: nextPageMarker.numItems,
categoryCatName: FFAppState().homecat)
.then((gridViewinfiniteGetAllProductsResponse) {
List pageItems = getJsonField(
gridViewinfiniteGetAllProductsResponse.jsonBody,
r'''$.results''',
).toList() as List;
// print('22' + pageItems.toString());
final newNumItems =
nextPageMarker.numItems + pageItems.length;
_pagingController!.appendPage(
pageItems,
(pageItems.length > 0)
? ApiPagingParams(
nextPageNumber:
nextPageMarker.nextPageNumber + 1,
numItems: newNumItems,
lastResponse:
gridViewinfiniteGetAllProductsResponse,
)
: null,
);
});
#override
#override
void didUpdateWidget(PagedGridView oldWidget) {
_pagingController?.refresh();
}
});
return _pagingController!;
}(),
padding: EdgeInsets.zero,
scrollDirection: Axis.vertical,
builderDelegate: PagedChildBuilderDelegate<dynamic>(
// Customize what your widget looks like when it's loading the first page.
firstPageProgressIndicatorBuilder: (_) => Center(
child: SizedBox(
width: 50,
height: 50,
child: CircularProgressIndicator(
color: FlutterFlowTheme.of(context).primaryColor,
),
),
),
itemBuilder: (context, _, kokoIndex) {
final kokoItem =
_pagingController!.itemList![kokoIndex];
return InkWell(
onTap: () async {
await Navigator.push(
context,
MaterialPageRoute(
builder: (context) => ProductDetailsWidget(
id: getJsonField(
kokoItem,
r'''$.id''',
).toString(),
),
),
);
},
child: Container(
width: double.infinity,
height: double.infinity,
decoration: BoxDecoration(
color: FlutterFlowTheme.of(context)
.secondaryBackground,
borderRadius: BorderRadius.circular(12),
),
child: Column(
mainAxisSize: MainAxisSize.max,
children: [
Row(
mainAxisSize: MainAxisSize.max,
mainAxisAlignment:
MainAxisAlignment.spaceAround,
children: [
Expanded(
child: Column(
mainAxisSize: MainAxisSize.max,
children: [
Padding(
padding: EdgeInsetsDirectional
.fromSTEB(0, 0, 0, 0),
child: ClipRRect(
borderRadius:
BorderRadius.circular(0),
child: Image.network(
getJsonField(
kokoItem,
r'''$.thumbnail_img1''',
),
width: MediaQuery.of(context)
.size
.width,
height: MediaQuery.of(context)
.size
.height *
0.3,
fit: BoxFit.cover,
),
),
),
],
),
),
],
),
Row(
mainAxisSize: MainAxisSize.max,
mainAxisAlignment: MainAxisAlignment.center,
children: [
Expanded(
child: AutoSizeText(
getJsonField(
kokoItem,
r'''$.title''',
).toString(),
textAlign: TextAlign.right,
maxLines: 2,
style: FlutterFlowTheme.of(context)
.bodyText1
.override(
fontFamily: 'Cairo',
fontSize: 13,
useGoogleFonts: GoogleFonts
.asMap()
.containsKey(
FlutterFlowTheme.of(
context)
.bodyText1Family),
),
),
),
ToggleIcon(
onPressed: () async {
setState(() =>
FFAppState().smallsize =
!FFAppState().smallsize);
},
value: FFAppState().smallsize,
onIcon: Icon(
Icons.favorite,
color: Colors.black,
size: 25,
),
offIcon: Icon(
Icons.favorite_border,
color: Colors.black,
size: 25,
),
)
],
),
Row(
mainAxisSize: MainAxisSize.max,
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
functions.priceformat(getJsonField(
kokoItem,
r'''$.price''',
)),
style: FlutterFlowTheme.of(context)
.bodyText1,
),
Icon(
FFIcons.kshekel,
color: Color(0xD8000000),
size: 13,
),
],
),
],
),
),
);
},
),
),
),
),
),
],
),
).animateOnActionTrigger(
animationsMap['columnOnActionTriggerAnimation']!,
hasBeenTriggered: hasColumnTriggered),
),
);
}
I tried loading using future or clearing the paging controller before refreshing neither worked,
any help would be much appreciated :)
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,
...
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
),
)
I am using AnimatedPositioned to make animation.
Obx(() {
return AnimatedBuilder(
animation: controller.animationController,
builder: (context, child) =>
AnimatedPositioned(
duration: const Duration(milliseconds: 1000),
bottom: controller.actionsBottom,
child: child!,
),
child:
!controller.trloaded.value
? const SizedBox()
: Column(
children: [
Row(
mainAxisSize: MainAxisSize.min,
children: [
GlobalButton(
title: controller.trLanding('signUp'),
type: ButtonType.outlinedPrimary,
width: 154.w,
height: 56,
onTap: () => controller.goToSignUp(),
),
SizedBox(width: 20.w),
GlobalButton(
borderWidth: 1,
borderColor: AppColors.primary[800]!,
title: controller.trLanding("signIn"),
width: 154.w,
height: 56,
onTap: () => controller.goToSignIn()),
],
),
Container(
margin: const EdgeInsets.only(top: 16, bottom: 16),
child: Row(
children: [
Text(
"By Signing up you agree with our ",
style: textTheme.bodyText2!.copyWith(
color: AppColors.neutral[400],
),
),
GestureDetector(
onTap: () {
// clearData();
push(TermsPage());
},
child: Text(
"Terms and Conditions",
style: textTheme.bodyText2!.copyWith(
color: AppColors.primary[700],
),
),
),
],
),
),
],
),
);
}),
When I run the app by profile mode I got these problems:
To fix that I am using Animation Builder and I added my widgets to its child but I have the above problem. How can I fix that?