Flutter Button inside a Wheel ListView Widget - flutter

I am using a ListWheelScrollView more specific a clickable one.
(clickable_list_wheel_view)
I am trying to have a button inside my itemWidget but I can not click it. This is my list:
return ClickableListWheelScrollView(
scrollController: _scrollController,
itemHeight: _itemHeight,
itemCount: months.length,
scrollOnTap: true,
onItemTapCallback: (index) {
print(index)
},
child: ListWheelScrollView.useDelegate(
controller: _scrollController,
itemExtent: _itemHeight,
physics: FixedExtentScrollPhysics(),
diameterRatio: 3,
squeeze: 0.95,
onSelectedItemChanged: (index) {
// print(index);
},
childDelegate: ListWheelChildBuilderDelegate(
builder: (context, index) => MonthCardWidget(
month: months[index],
),
childCount: months.length,
),
),
);
Inside my MonthCardWidget I have this simple button:
Container(
height: 45,
width: 45,
color: Colors.transparent,
child: IconButton(
splashColor: Colors.transparent,
highlightColor: Colors.transparent,
onPressed: () {
print('flip');
},
icon: SvgPicture.asset('assets/images/flip.svg',
height: 20, width: 20),
),
),
Everything is getting displayed just fine but onPressed is never called on my button. I guess the GestureDetector is overriding the button? Is there any workaround for this? I couldn't find anything on this.
The GestureDetector is simply on top of the listView:
#override
Widget build(BuildContext context) {
if (_listHeight == .0) {
return MeasureSize(
child: Container(
child: widget.child,
),
onChange: (value) {
setState(() {
_listHeight = value.height;
});
},
);
}
return GestureDetector(
onTap: _onTap,
onTapUp: (tapUpDetails) {
_tapUpDetails = tapUpDetails?.localPosition;
},
child: widget.child,
);
}

The click will never reach your GestureDetector, you need to specify the click in the ClickableListWheelScrollView here:
onItemTapCallback: (index) {
print(index)
},
If the print you have there isn't printing, then get with the ClickableListWheelScrollView plugin publisher. You'll need logic to reach the changes you want with the button based on the index number passed from the ClickableListWheelScrollView I think....so a map of setState functions might do it.
Think of the button just being a view, and you're just running the function logic in parallel to each button based on its position in the list. The button is buried under the list, you need a layer above the ListWheel, which is the Clickable plugin, but obviously it becomes disconnected from the button, so you need to create logic to link them based on the index value, which is the position of the object in the list.
What I typically do:
onSelectedItemChanged: (index) {
// print(index);
},
This Will return the index position of the item in your list. Use this to change a variable.
Wrap your scroller in a GestureDetector.
In the Gesture Detector, write or call a function that takes your index position variable and performs your function as you desire.
If you need to control where in your list position the scroll begins, or remember its position when you rebuild etc, use a FixedExtentScrollController with an initialItem.
Everything slaves off the index of the onSelectedItemChanged in this way, and you use the FixedExtentScrollController to set a starting position at build. Remember that the list begins at 0.

Related

Pick Value onTap in CupertinoPicker and CupertinoDatePicker

onSelectedItemChanged is working perfectly in CupertinoPicker/CupertinoDatePicker
But I also want to pick value when user clicked the value.
Currently, user must scroll in order to pick the value, and as far as I know CupertinoPicker/CupertinoDatePicker doesn't have onTap, onPressed functions
How can I solve this issue
Unfortunately, the gesture detection inside CupertinoPicker/CupertinoDatePicker is not supported for now. When you trace the code inside CupertinoPicker, it leads to use ListWheelScrollView at the end and it does not respond to the tap event.
Discussion thread on GitHub:
Tapping items is not working in CupertinoPicker
ListWheelScrollView children do not respond on gesture (onTap) events
There is a workaround solution by using package clickable_list_wheel_view (fixed height for the child widget, mentioned here)
I found a workaround by using flutter_portal.
final ScrollController scrollController = FixedExtentScrollController();
#override
Widget build(BuildContext context) {
return Container(
height: 100,
child: Portal(
child: CupertinoPicker.builder(
scrollController: scrollController,
childCount: itemCount,
itemBuilder: (context, index) {
return PortalTarget(
anchor: Aligned(
follower: Alignment.center,
target: Alignment.center,
),
portalFollower: GestureDetector(
behavior: HitTestBehavior.translucent,
onTap: () {
scrollController.animateToItem(index,
duration: Duration(milliseconds: 250),
curve: Curves.decelerate);
},
),
child: Text(index.toString()),
);
},
),
)
);
}
GestureDetector(
behavior: HitTestBehavior.translucent,
onTap: () => // write what you want to do,
child: CupertinoPicker(),
),

flutter CupertinoPicker FixedExtentScrollController change initialItem

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

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,

How to animate widgets automatically in Flutter?

I have a Future which returns a List and using FutureBuilder and inside it a ListView.builder I show data that I got from Future.
But the problem is, whenever Future returns data my list of widgets just pops directly into the screen. I want to give it a animation (fade or curve) which I am not supposed to trigger manually, instead I want that when I got the data from Future my list to pop to screen not direcly but with some latency/fade/curve or any other animation.
How can I achieve that?
You can use a opacity animation, here you can see an example.
Then you can set
bool _visible = true; //Put this inside your initState
AnimatedOpacity(
opacity: _visible ? 1.0 : 0.0,
duration: Duration(milliseconds: 500),
child:ListView.builder(
itemCount: count,
padding: EdgeInsets.all(10.0),
itemBuilder: (BuildContext context, int position) {
return Card(
color: Colors.white,
elevation: 2.0,
child: ListTile(
title: Text(this.list[position].name),
subtitle: Text(this.list[position].type),
trailing: GestureDetector(
child: Icon(Icons.delete, color: Colors.grey,),
onTap: () {
_delete(context, list[position]);
},
),
onTap: () {
debugPrint("ListTile Tapped");
navigateToDetail(this.list[position]);
},
),
);
},
)
),

How to disable the Cupertinopicker in flutter?

I have a CupertinoPicker, on some condition I have to disable the CupertinoPicker.
Checked the CupertinoPicker class and didn't find any disable attribute in that.
If disabling is not possible, Can I Stop scroll on it?
CupertinoPicker(
backgroundColor: null,
itemExtent: PICKER_EXTENT,
useMagnifier: true,
looping: true,
onSelectedItemChanged: (int index) {
print('selected index $index');
},
children: List<Widget>.generate(dataList.length, (int index) {
return Center(
child: Text(dataList[index]),
);
}),
scrollController:
FixedExtentScrollController(initialItem: selectedIndex)),
You can use the AbsorbPointer to enable/disable touch events for any widget.
As per the documentation
When absorbing is true, this widget prevents its subtree from receiving pointer events by terminating hit testing at itself. It still consumes space during layout and paints its child as usual. It just prevents its children from being the target of located events, because it returns true from RenderBox.hitTest.
In your case, wrap the CupertinoPicker with an AbsorbPointer and use the absorbing property to enable/disable touch events for your CupertinoPicker
AbsorbPointer(
absorbing: true,
child: CupertinoPicker(
backgroundColor: null,
itemExtent: 100.0,
useMagnifier: true,
looping: true,
onSelectedItemChanged: (int index) {
print('selected index $index');
},
children: List<Widget>.generate(dataList.length, (int index) {
return Center(
child: Text(dataList[index]),
);
}),
scrollController:
FixedExtentScrollController(initialItem: selectedIndex)),
);
You can