flutter CupertinoPicker FixedExtentScrollController change initialItem - flutter

Expanded(
flex: 10,
child: Container(
child: CupertinoPicker(
itemExtent: 50,
onSelectedItemChanged: (int i) {},
scrollController: FixedExtentScrollController(
initialItem: isIndex,
),
useMagnifier: true,
children: appwidget,
))),
I have this code, children is every changed list widgets.
When I change 'appwidget' for list widget, Can I Set initialItem Index?
I can't call FixedExtentScrollController. I have no idea.

First, u need to create a FixedExtentScrollController, which allows u to conveniently work with item indices rather than working with a raw pixel scroll offset as required by the standard ScrollController (source from the Flutter doc):
FixedExtentScrollController? _scrollWheelController;
final String? value;
final String values = ['male','female','other'];
#override
void initState() {
super.initState();
_scrollWheelController = FixedExtentScrollController(
/// Jump to the item index of the selected value in CupertinoPicker
initialItem: value == null ? 0 : values.indexOf(value!),
);
}
Then connect it to the CupertinoPicker so that u can control the scroll view programmatically:
CupertinoPicker.builder(scrollController: _scrollWheelController);
If u want to jump to the item index of the selected value as soon as the ModalBottomSheet pops up, the following code will help u achieve this:
showModalBottomSheet<String>(
context: context,
builder: (_) {
/// If the build() method to render the
/// [ListWheelScrollView] is complete, jump to the
/// item index of the selected value in the controlled
/// scroll view
WidgetsBinding.instance!.addPostFrameCallback(
/// [ScrollController] now refers to a
/// [ListWheelScrollView] that is already mounted on the screen
(_) => _scrollWheelController?.jumpToItem(
value == null ? 0 : values.indexOf(value!),
),
);
return SizedBox(
height: 200,
child: Column(
crossAxisAlignment: CrossAxisAlignment.end,
children: [
Padding(
padding: const EdgeInsets.symmetric(horizontal: 16),
child: TextButton(
onPressed: () => Navigator.pop(context),
child: Text('Done'),
),
),
const Divider(thickness: 1),
Expanded(
child: CupertinoPicker.builder(
/// if [itemExtent] is too low, the content for each
/// item will be squished together
itemExtent: 32,
scrollController: _scrollWheelController,
onSelectedItemChanged: (index) => setState(() => values[index]),
childCount: values.length,
itemBuilder: (_, index) => Center(
child: Text(
valueAsString(values[index]),
style: TextStyle(
color: Theme.of(context).accentColor,
),
),
),
),
),
],
),
);
},
);
WidgetsBinding.instance!.addPostFrameCallback() will ensure all the build() methods of all widgets to be rendered in the current frame is complete. If _scrollWheelController refers to a ListWheelScrollView that is not yet fully built, the jumpToItem() won't work.
Read this thread for more info on how to run method on Widget build complete

Related

How to change the value of 'selectedIndex' from one dart file to the other dart file using flutter GetX?

I have my custom Bottom Navigation Bar in one dart file, i.e. bottomnavbar.dart. And I have list of multiple screens(or pages) in my home.dart file. I am using an .obs variable to store my selected index value.
code from home.dart file:
var selectedIndex = 0.obs;
final screen = [
const Page1(),
const Page2(),
const Page3(),
const Page4(),
const Page5(),
];
...
body: screen[selectedIndex.value],
...
Even if I change the variable value (like 0.obs to 1.obs), page not changing, why??
next of, In my bottomnavbar.dart file, I have extracted and made a widget for my nav bar 'items'. And I have tried to wrap the item widget with Obx:
Widget bnbItems(String image, int index, double height) {
return Obx(
() => InkWell(
splashColor: Theme.of(context).brightness == Brightness.dark
? Colors.white.withOpacity(0.5)
: Colors.pink.withOpacity(0.5),
enableFeedback: true,
onTap: () => setState(() {
selectedIndex.value = index;
_controller.animateTo(index / 4);
// print(selectedIndex);
}),
child: Container(
alignment: Alignment.center,
width: 50,
height: 50,
child: Padding(
padding: const EdgeInsets.only(top: 5.0),
child: Image.asset(
image,
height: height,
),
),
),
),
);}
and I am getting this error:
[Get] the improper use of a GetX has been detected.
You should only use GetX or Obx for the specific widget that will be updated.
If you are seeing this error, you probably did not insert any observable variables into GetX/Obx
or insert them outside the scope that GetX considers suitable for an update
(example: GetX => HeavyWidget => variableObservable).
If you need to update a parent widget and a child widget, wrap each one in an Obx/GetX.
Can anyone give me the solution with some code and explanation? And also how will I be able to set a particular screen as the initial screen?
Replace on tap with this code
onTap: () {
selectedIndex.value = 1; // page index you want to view
},
then remove Obx(()=> on bnbItems widget
Widget bnbItems(String image, int index, double height) {
return InkWell(
splashColor: Theme.of(context).brightness == Brightness.dark
? Colors.white.withOpacity(0.5)
: Colors.pink.withOpacity(0.5),
enableFeedback: true,
onTap: () {
selectedIndex.value = 1; // page index you want to view
},
child: Container(
alignment: Alignment.center,
width: 50,
height: 50,
child: Padding(
padding: const EdgeInsets.only(top: 5.0),
child: Image.asset(
image,
height: height,
),
),
),
);}
then use Obx(()=> wrapper on the body's widget
body: Obx(() => screen[selectedIndex.value]),
why you are using setState in GetX structure?
Try this code for onTap()
onTap: () {
selectedIndex.value = index;
_controller.animateTo(index / 4);
// print(selectedIndex);
},
to set initial screen use index no of that screen in var selectedIndex = 0.obs; instead of 0.

Fill the available space in Listtile

I am new to the flutter and trying to fill the empty space in the listtile. I tried to use dense and visualDensity but with that, I am not getting the required result. Any support and suggestions will be appreciated.
here is my code and output:
Column(
crossAxisAlignment: CrossAxisAlignment.end,
children: [
SizedBox(
height: isLargeScreen ? 300 : 200,
child: ListView.builder(
physics: const ScrollPhysics(),
shrinkWrap: true,
itemCount: tags.length,
itemBuilder: (context, index) {
return CheckboxListTile(
value: tempSelectedTags.contains(tags[index].id),
onChanged: (e) {
setState(() {
if (tempSelectedTags.contains(tags[index].id)) {
tempSelectedTags.remove(tags[index].id);
} else {
tempSelectedTags.add(tags[index].id);
}
});
},
title: Text(
tags[index].name,
style: !tempSelectedTags.contains(tags[index].id)
? theme.textTheme.labelMedium?.copyWith(
color: ThemeConfig.colorTertiary)
: theme.textTheme.titleSmall?.copyWith(
color: ThemeConfig.colorTertiary),
),
);
}),
),
const Spacer(),
Padding(
padding: const EdgeInsets.only(bottom:sdPaddingMedium),
child: SdPrimaryButton(
title: appLocalizations.btnApply,
onPressed: () {
viewModel.setTags(tempSelectedTags);
Navigator.pop(context);
},
),
),
],
)
Output can be seen here
There are two important things that determine the vertical layout in your column.
The whole box has a fixed size
SizedBox(
height: isLargeScreen ? 300 : 200,
There is a flexible space between the checkbox options and the bottom-right button
const Spacer(),
So if you want to remove the space, you can either
reduce the overall box size or
replace the const Spacer with a constant spacing like
SizedBox(height: 50) and also remove the SizedBox, so that the whole box will be content-sized

How to remove item at the top of List View in Flutter?

I have List View and I have inside each item in the list a button called "Delete item". When I press that button inside each item I want to delete only that item from the list.
But it does not delete item, it just display Toast message that I have specified.
How I can solve this?
This is the code:
Widget build(BuildContext context) {
listItems = buildVCsFromAPI(context);
return Container(
child: ListView.builder(
itemBuilder: (context, index) =>
_buildListItem(context, listItems[index], index),
itemCount: listItems.length,
physics: AlwaysScrollableScrollPhysics()),
);
}
Widget _buildListItem(
BuildContext context, _VerifiableCredentialListItem cert, int index) {
return GestureDetector(
child: AnimatedAlign(
curve: Curves.ease,
duration: Duration(milliseconds: 500),
heightFactor: selectedPosition == index ? factorMax : factorMin,
alignment: Alignment.topCenter,
child: Container(
decoration: BoxDecoration(
borderRadius: BorderRadius.all(Radius.circular(10)), //here
color: Colors.white,
boxShadow: [
BoxShadow(
color: Colors.grey.withOpacity(0.5),
offset: Offset(0, -1),
blurRadius: 10.0)
]),
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
HeadingRow(title: cert.fullTitle, appIcon: cert.appIcon),
displayListItem(index, selectedPosition, cert)
],
),
),
),
}
Column displayListItem(
int index, int selectedIndex, _VerifiableCredentialListItem cert) {
CredentialListGroupType groupType = cert.groupType;
return Column(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
SizedBox(
height: UIConstants.s2,
),
buildAnotherWidget(),
SizedBox(
height: UIConstants.s3,
),
buildDeleteAndExportButtons(),
],
);
}
Column buildDeleteAndExportButtons() {
return Column(
children: [
Padding(
padding: EdgeInsets.symmetric(
vertical: UIConstants.s1, horizontal: UIConstants.s2),
child: Row(
children: [
Expanded(
flex: 1,
child: BlueButtonWithIcon(
text: 'Delete item',
icon: 'assets/icons/delete-icon.svg',
onPressed: () {
setState(() {
AppToaster.pop(ToasterType.info, "Delete");
listItems.removeAt(0);
});
},
),
),
SizedBox(width: UIConstants.s1),
Expanded(
flex: 1,
child: BlueButtonWithIcon(
text: 'Export',
icon: 'assets/icons/export.svg',
onPressed: null,
),
)
],
),
),
SizedBox(height: UIConstants.s1)
],
);
}
Calling setState doesn't mean that flutter would actually full repaint the screen it means that it will check your widget tree with the last rendered widget tree and it will paint only the differences and it first compares widgets type and then widget keys to find that there is a difference between the current widget and the previous one and because of this when you remove an item from your list of items flutter checks your returned widgets to the currently rendered widget it doesn't found any difference and it won't repaint the screen and continues showing the last render
So for you to tell the flutter that one of the items in the listView is changed you could assign a uniqueKey key for each list item widget note that for this topic your keys should be unique to the data of that widget otherwise you will face performance issues because if your widget key is changed without any change in the representation of that widget in next time that builds method is called which could happen frequently flutter compares widgets key with the previous widgets key which is rendered to the screen and exist on the render tree and it founds that the keys are different and it repaints that widget which is a redundant operation because your widgets UI and representation are the same
For example, assign a unique id base on the index or content of your data to each data model in the listItems and use that to create a ValueKey() for the widget that is represented by that data
here is a working example of the list which when you click on the list item first list item will be removed
class ListItemDataModel {
final String id;
final Color color;
ListItemDataModel(this.id, this.color);
}
class _MyHomePageState extends State<MyHomePage> {
List<ListItemDataModel> items = [];
#override
void initState() {
super.initState();
items = [
ListItemDataModel("A", Colors.red),
ListItemDataModel("B", Colors.amber),
ListItemDataModel("C", Colors.green),
ListItemDataModel("D", Colors.lightBlueAccent),
ListItemDataModel("E", Colors.pink),
];
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Container(
child: ListView.builder(
itemBuilder: (context, index) {
return GestureDetector(
key: ValueKey(items[index].id),
//Tap to Remove first item from list
onTap: () {
items.removeAt(0);
setState(() {});
},
child: Container(
height: 60,
color: items[index].color,
child: Center(
child: Text(
"This is a unique item with id = ${items[index].id}"),
),
),
);
},
itemCount: items.length,
),
),
);
}
}
So,
We don't have acces to the code above.. so.. where does listItems came from?
Maybe you are retrieving the value of listItems after the init state? if so it's normal that you are retrieving always the same result..
What you should do is the following:
get listItems value from params, global vars, databse ecc
display the list
when you delete a single item you should update the original list
on state updated now the list will be loaded with updated values
If you delete an item from a list but the list is then reloaded in its original form your updates will be lost

Flutter - Add Layers on top of ListView to scroll

I am making a reader app and I have a ListView.builder which is loading multiple Images from the API. Now I want to add a layer on top of the ListView with 2 transparent containers so that when I click the left container the list goes up and when I click the right container the list goes down. I tried adding the ListView to stack but then the scrolling ability of the list is gone. This is my code so far:
return Scaffold(
body: Stack(
children: [
ListView.builder(
controller: _scrollViewController,
cacheExtent: 1000,
itemCount: _picLinkMap.length,
itemBuilder: (BuildContext ctx, int index) {
return buildImageLoader(
_picLinkMap[index], index + 1);
},
),
Row(
children: [
GestureDetector(
child: Container(
width: mySize.width * 0.5,
color: Colors.black38,
),
onTap: () {
print("Tap Left");
},
),
GestureDetector(
child: Container(
width: mySize.width * 0.5,
color: Colors.red.withOpacity(0.5),
),
onTap: () {
print("Tap RIGHT");
},
),
],
),
],
),
);
I only want to scroll about half the height of the screen on tap.
I searched the web but did not find a suitable solution.
ScrollController _scrollViewController = ScrollController();
...
var jumpTo = (MediaQuery.of(context).size.height - 104)/2;
//104 is random number for the rest of the screen widgets (e.g. appbar ..)
_scrollViewController.jumpTo(jumpTo); //pixel value
...
ListView.builder(
controller: _scrollViewController,

SingleChildScrollView not keeping scroll position

I have a SingleChildScrollView widget in my app that contains a Column as a child.
The Column has many children and the last one in the very bottom of the scrolled screen is a StreamBuilder that I use to change a child Image
The issue is that when I tap on the image, the logic of the StreamBuilder works and the image is changed, but then the SingleChildScrollView scrolls a bit up so that the image is not visible and forces the user to scroll down again to be able to see the new loaded image.
Widget _buildScroll() => SingleChildScrollView(
child: Container(
width: 2080,
child: Column(
children: <Widget>[
_buildTopBar(),
_buildMainContent(),
SizedBox(height: 30),
Container(
child: Image.asset(
"assets/images/chart_legend.png",
width: 300,
),
),
SizedBox(height: 30),
Image.asset("assets/images/road_map.png", width: 600),
StreamBuilder<int>(
initialData: 1,
stream: _compareStream,
builder: (context, snapshot) {
if (snapshot.hasData) {
if (snapshot.data == 1) {
return Padding(
padding: const EdgeInsets.only(right: 10),
child: GestureDetector(
child: Image.asset("assets/images/compare1.png"),
onTap: () => _compareSubject.add(2),
),
);
} else if (snapshot.data == 2) {
return Padding(
padding: const EdgeInsets.only(right: 10),
child: GestureDetector(
child: Image.asset("assets/images/compare2.png"),
onTap: () => _compareSubject.add(3),
),
);
} else {
return Padding(
padding: const EdgeInsets.only(right: 10),
child: GestureDetector(
child: Image.asset("assets/images/compare3.png"),
onTap: () => _compareSubject.add(1),
),
);
}
}
return Padding(
padding: const EdgeInsets.only(right: 10),
child: GestureDetector(
child: Image.asset("assets/images/compare1.png"),
onTap: () {},
),
);
}),
],
),
),
);
However, even more weird is that, once I have done tap on all images, they will be showed as expected without scrolling up...meaning that, if there is the second time i tap on a image, the second time the image is replaced the scrolling up in not happening.
The problem here is that the size of your children changes and the SingleChildScrollView cannot handle that.
I think there could be two solutions that might work here:
If you know the sizes of your images before they are loaded, you should enforce it using a SizedBox. This way, the scroll position will stay the same:
SizedBox(
width: 300,
height: 120,
child: StreamBuilder<int>(...),
)
Use ensureVisible that you trigger once the stream builder is updated, which lets you control exactly where the image should be displayed.
You would need to assign a ScrollController to your SingleChildScrollView (controller parameter). Then, you also need a GlobalKey for your StreamBuilder that you want to show (key parameter).
If you have saved instances of the two to variables, you will be able to call the following once your image is loaded:
scrollController.position.ensureVisible(
globalKey.currentContext.findRenderObject(),
alignment: 0.5, // Aligns the image in the middle.
duration: const Duration(milliseconds: 120), // So it does not jump.
);