Flutter listView isn't updating after deleting items - flutter

I'm using a list view to display some items and also I'm using flutter_tindercard
to approve or refuse items when an item is approved it should be added to the approved items list then removed from the list view , and the same for refused items , but the issue when the item is refused or approved it is not removing from the list view.
PS: the bottom elements are not moving to the top
my code:
import 'package:flutter/material.dart';
import 'package:flutter_tindercard/flutter_tindercard.dart';
class ExampleHomePage extends StatefulWidget {
#override
_ExampleHomePageState createState() => _ExampleHomePageState();
}
class _ExampleHomePageState extends State<ExampleHomePage>
with TickerProviderStateMixin {
List approved = [];
List refused = [];
List<String> welcomeImages = [
"https://images.unsplash.com/photo-1631636176993-759dea1a1300?ixid=MnwxMjA3fDB8MHxlZGl0b3JpYWwtZmVlZHw0fHx8ZW58MHx8fHw%3D&ixlib=rb-1.2.1&auto=format&fit=crop&w=500&q=60",
"https://images.unsplash.com/photo-1631634176568-f543af6a41de?ixid=MnwxMjA3fDB8MHxlZGl0b3JpYWwtZmVlZHw3fHx8ZW58MHx8fHw%3D&ixlib=rb-1.2.1&auto=format&fit=crop&w=500&q=60",
"https://images.unsplash.com/photo-1631691971564-adf9419d904e?ixid=MnwxMjA3fDB8MHxlZGl0b3JpYWwtZmVlZHwxM3x8fGVufDB8fHx8&ixlib=rb-1.2.1&auto=format&fit=crop&w=500&q=60",
"https://images.unsplash.com/photo-1631641906574-24adb8594649?ixid=MnwxMjA3fDB8MHxlZGl0b3JpYWwtZmVlZHwxMnx8fGVufDB8fHx8&ixlib=rb-1.2.1&auto=format&fit=crop&w=500&q=60",
"https://images.unsplash.com/photo-1593642702821-c8da6771f0c6?ixid=MnwxMjA3fDF8MHxlZGl0b3JpYWwtZmVlZHwxMXx8fGVufDB8fHx8&ixlib=rb-1.2.1&auto=format&fit=crop&w=500&q=60",
"https://images.unsplash.com/photo-1631621461675-e61a1f28d3d6?ixid=MnwxMjA3fDB8MHxlZGl0b3JpYWwtZmVlZHw0NHx8fGVufDB8fHx8&ixlib=rb-1.2.1&auto=format&fit=crop&w=500&q=60"
];
List<TinderCarder> cards = [];
CardController controller = CardController(); //Use this to trigger swap.
#override
void initState() {
// TODO: implement initState
super.initState();
cards = welcomeImages.map((element) {
return TinderCarder(
image: element,
controller: controller,
onLeftPress: () {
setState(() {
cards.removeAt(element.indexOf(element));
print('index is: ' + "${element.indexOf(element)}");
print('list length: ' + cards.length.toString());
});
},
onRightPress: () {
setState(() {
cards.removeAt(element.indexOf(element));
print('index is: ' + "${element.indexOf(element)}");
print('list length: ' + cards.length.toString());
});
},
);
}).toList();
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: Container(
width: double.infinity,
child: ListView(
children: cards,
),
),
);
}
}
class TinderCarder extends StatelessWidget {
final String image;
Function onLeftPress;
Function onRightPress;
var controller;
TinderCarder(
{required this.image,
this.controller,
required this.onLeftPress,
required this.onRightPress});
#override
Widget build(BuildContext context) {
return Container(
height: 200,
child: TinderSwapCard(
orientation: AmassOrientation.BOTTOM,
totalNum: 1,
stackNum: 3,
swipeEdge: 4.0,
maxWidth: double.infinity,
maxHeight: MediaQuery.of(context).size.width * 0.9,
minWidth: MediaQuery.of(context).size.width * 0.8,
minHeight: MediaQuery.of(context).size.width * 0.8,
cardBuilder: (context, index) => Card(
child: Image.network(
image,
width: double.infinity,
fit: BoxFit.cover,
),
),
cardController: controller,
swipeUpdateCallback: (DragUpdateDetails details, Alignment align) {
/// Get swiping card's alignment
if (align.x < 0) {
} else if (align.x > 0) {}
},
swipeCompleteCallback: (CardSwipeOrientation orientation, int index) {
print(orientation.toString());
if (orientation == CardSwipeOrientation.LEFT) {
print("Card is LEFT swiping");
// print(welcomeImages.length);
onLeftPress();
} else if (orientation == CardSwipeOrientation.RIGHT) {
print("Card is RIGHT swiping");
// print(welcomeImages.length);
onRightPress();
}
},
),
);
}
}

Edited
Try with setstate
import 'package:flutter/material.dart';
import 'package:flutter_tindercard/flutter_tindercard.dart';
class ExampleHomePage extends StatefulWidget {
#override
_ExampleHomePageState createState() => _ExampleHomePageState();
}
class _ExampleHomePageState extends State<ExampleHomePage>
with TickerProviderStateMixin {
List approved = [];
List refused = [];
List<String> welcomeImages = [
"https://images.unsplash.com/photo-1631636176993-759dea1a1300?ixid=MnwxMjA3fDB8MHxlZGl0b3JpYWwtZmVlZHw0fHx8ZW58MHx8fHw%3D&ixlib=rb-1.2.1&auto=format&fit=crop&w=500&q=60",
"https://images.unsplash.com/photo-1631634176568-f543af6a41de?ixid=MnwxMjA3fDB8MHxlZGl0b3JpYWwtZmVlZHw3fHx8ZW58MHx8fHw%3D&ixlib=rb-1.2.1&auto=format&fit=crop&w=500&q=60",
"https://images.unsplash.com/photo-1631691971564-adf9419d904e?ixid=MnwxMjA3fDB8MHxlZGl0b3JpYWwtZmVlZHwxM3x8fGVufDB8fHx8&ixlib=rb-1.2.1&auto=format&fit=crop&w=500&q=60",
"https://images.unsplash.com/photo-1631641906574-24adb8594649?ixid=MnwxMjA3fDB8MHxlZGl0b3JpYWwtZmVlZHwxMnx8fGVufDB8fHx8&ixlib=rb-1.2.1&auto=format&fit=crop&w=500&q=60",
"https://images.unsplash.com/photo-1593642702821-c8da6771f0c6?ixid=MnwxMjA3fDF8MHxlZGl0b3JpYWwtZmVlZHwxMXx8fGVufDB8fHx8&ixlib=rb-1.2.1&auto=format&fit=crop&w=500&q=60",
"https://images.unsplash.com/photo-1631621461675-e61a1f28d3d6?ixid=MnwxMjA3fDB8MHxlZGl0b3JpYWwtZmVlZHw0NHx8fGVufDB8fHx8&ixlib=rb-1.2.1&auto=format&fit=crop&w=500&q=60"
];
List<TinderCarder> cards = [];
CardController controller = CardController(); //Use this to trigger swap.
#override
void initState() {
// TODO: implement initState
super.initState();
cards = welcomeImages.map((element) {
return TinderCarder(
image: element,
controller: controller,
onLeftPress: () {
setState(() {
cards.removeAt(element.indexOf(element));
print('index is: ' + "${element.indexOf(element)}");
print('list length: ' + cards.length.toString());
});
},
onRightPress: () {
setState(() {
cards.removeAt(element.indexOf(element));
print('index is: ' + "${element.indexOf(element)}");
print('list length: ' + cards.length.toString());
});
},
);
}).toList();
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: Container(
width: double.infinity,
child: SingleChildScrollView(
child: ListView(
shrinkWrap: true,
key: Key(cards.length.toString()),
physics: ScrollPhysics(),
children: cards,
),
),
),
);
}
}
class TinderCarder extends StatelessWidget {
final String image;
Function onLeftPress;
Function onRightPress;
var controller;
TinderCarder(
{ this.image,
this.controller,
this.onLeftPress,
this.onRightPress});
#override
Widget build(BuildContext context) {
return Container(
height: 200,
child: TinderSwapCard(
orientation: AmassOrientation.BOTTOM,
totalNum: 1,
stackNum: 3,
swipeEdge: 4.0,
maxWidth: MediaQuery.of(context).size.width * 0.9,
maxHeight: MediaQuery.of(context).size.width * 0.9,
minWidth: MediaQuery.of(context).size.width * 0.8,
minHeight: MediaQuery.of(context).size.width * 0.8,
cardBuilder: (context, index) => Card(
child: Image.network(
image,
width: double.infinity,
fit: BoxFit.cover,
),
),
cardController: controller,
swipeUpdateCallback: (DragUpdateDetails details, Alignment align) {
/// Get swiping card's alignment
if (align.x < 0) {
} else if (align.x > 0) {}
},
swipeCompleteCallback: (CardSwipeOrientation orientation, int index) {
print(orientation.toString());
if (orientation == CardSwipeOrientation.LEFT) {
print("Card is LEFT swiping");
// print(welcomeImages.length);
onLeftPress();
} else if (orientation == CardSwipeOrientation.RIGHT) {
print("Card is RIGHT swiping");
// print(welcomeImages.length);
onRightPress();
}
},
),
);
}
}

If you are using stateful widget then apply setstate() to build again your widget.
If you want to use stateless widget than use state management like GetX or provider.
For GetX create observable list in controller and wrap widget with obx so whenever list is updated the obx will build that particular widget for you again.
To Learn GetX Properly go through this playlist created by tadas petra.
https://www.youtube.com/playlist?list=PL26uY6-lIzqkmvpNr9gMCrIvl8k5Mqrhs

Related

Resize ListView during scrolling in Flutter

I'm trying to build a screen where two vertically stacked ListViews cause themselves to grow and shrink as a result of being scrolled. Here is an illustration:
The initial state is that both lists take up 50% of the top and bottom of the screen respectively. When the user starts dragging the top list downward (to scroll up) it will initially cause the list to expand to take up 75% of the screen before the normal scrolling behavior starts; when the user changes direction, dragging upwards (to scroll down), then as they get to the bottom of the list it will cause the list to shrink back up to only taking up 50% of the screen (the initial state).
The bottom list would work similarly, dragging up would cause the list to expand upwards to take up 75% of the screen before the normal scrolling behavior starts; when the user changes direction, dragging downwards (to scroll up), then as they get to the top of the list it will shrink back to 50% of the screen.
Here is an animation of what it should look like:
https://share.cleanshot.com/mnZhJF8x
My question is, what is the best widget combination to implement this and how do I tie the scrolling events with resizing the ListViews?
So far, this is as far as I've gotten:
Column(
children: [
SizedBox(
height: availableHeight / 2,
child: ListView(...)
),
Expanded(child: ListView(...)),
],
),
In terms of similar behavior, it appears that the CustomScrollView and SliverAppBar have some of the elements in scrolling behaving I'm going after but it's not obvious to me how to convert that into the the two adjacent lists view I described above.
Any advice would be greatly appreciated, thank you!
hi Check this,
Column(
children: [
Expanded (
flex:7,
child: Container(
child: ListView.builder(
itemCount:50,
itemBuilder: (BuildContext context, int index) {
return ListTile(
leading: const Icon(Icons.list),
trailing: const Text(
"GFG",
style: TextStyle(color: Colors.green, fontSize: 15),
),
title: Text("List item $index"));
}),
),
),
Expanded (
flex:3,
child: Container(
child: ListView.builder(
itemCount:50,
itemBuilder: (BuildContext context, int index) {
return ListTile(
leading: const Icon(Icons.list),
trailing: const Text(
"GFG",
style: TextStyle(color: Colors.green, fontSize: 15),
),
title: Text("aaaaaaaaa $index"));
}),
),
),
],
),
edit: refactored and maybe better version:
import 'package:flutter/material.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'ExtentableTwoRowScrollable Demo',
home: Scaffold(
body: LayoutBuilder(
builder: (BuildContext context, BoxConstraints constraints) {
return ExtentableTwoRowScrollable(
height: constraints.maxHeight,
);
}),
),
);
}
}
// sorry for the name :)
class ExtentableTwoRowScrollable extends StatefulWidget {
const ExtentableTwoRowScrollable({
super.key,
required this.height,
this.minHeight = 150.0,
});
final double height;
final double minHeight;
#override
State<ExtentableTwoRowScrollable> createState() =>
_ExtentableTwoRowScrollableState();
}
class _ExtentableTwoRowScrollableState extends State<ExtentableTwoRowScrollable>
with SingleTickerProviderStateMixin {
final upperSizeNotifier = ValueNotifier(0.0);
final lowerSizeNotifier = ValueNotifier(0.0);
var upperHeight = 0.0;
var dragOnUpper = true;
void incrementNotifier(ValueNotifier notifier, double increment) {
if (notifier.value + increment >= widget.height - widget.minHeight) return;
if (notifier.value + increment < widget.minHeight) return;
notifier.value += increment;
}
bool handleVerticalDrag(ScrollNotification notification) {
if (notification is ScrollStartNotification &&
notification.dragDetails != null) {
if (notification.dragDetails!.globalPosition.dy <
upperSizeNotifier.value) {
dragOnUpper = true;
} else {
dragOnUpper = false;
}
}
if (notification is ScrollUpdateNotification) {
final delta = notification.scrollDelta ?? 0.0;
if (dragOnUpper) {
if (notification.metrics.extentAfter != 0) {
incrementNotifier(upperSizeNotifier, delta.abs());
incrementNotifier(lowerSizeNotifier, -1 * delta.abs());
} else {
incrementNotifier(upperSizeNotifier, -1 * delta.abs());
incrementNotifier(lowerSizeNotifier, delta.abs());
}
}
if (!dragOnUpper) {
if (notification.metrics.extentBefore != 0) {
incrementNotifier(upperSizeNotifier, -1 * delta.abs());
incrementNotifier(lowerSizeNotifier, delta.abs());
} else {
incrementNotifier(upperSizeNotifier, delta.abs());
incrementNotifier(lowerSizeNotifier, -1 * delta.abs());
}
}
}
return true;
}
#override
Widget build(BuildContext context) {
// initialize ratio of lower and upper, f.e. here 50:50
upperSizeNotifier.value = widget.height / 2;
lowerSizeNotifier.value = widget.height / 2;
return NotificationListener(
onNotification: handleVerticalDrag,
child: Column(
children: [
ValueListenableBuilder<double>(
valueListenable: upperSizeNotifier,
builder: (context, value, child) {
return Container(
color: Colors.greenAccent,
height: value,
child: ListView.builder(
shrinkWrap: true,
itemCount: 40,
itemBuilder: (BuildContext context, int index) {
return ListTile(
leading: const Icon(Icons.list),
title: Text("upper ListView $index"));
},
),
);
},
),
ValueListenableBuilder<double>(
valueListenable: lowerSizeNotifier,
builder: (context, value, child) {
return Container(
color: Colors.blueGrey,
height: value,
child: ListView.builder(
shrinkWrap: true,
itemCount: 40,
itemBuilder: (BuildContext context, int index) {
return ListTile(
leading: const Icon(Icons.list),
title: Text("lower ListView $index"));
},
),
);
},
),
],
),
);
}
}
here is the older post:
so, here's my shot on this. There might be a less complicated solution of course but I think it's somewhat understandable. At least I've tried to comment good enough.
Let me know if it works for you.
import 'package:flutter/material.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'ExtentableTwoRowScrollable Demo',
home: Scaffold(
body: LayoutBuilder(
builder: (BuildContext context, BoxConstraints constraints) {
return ExtentableTwoRowScrollable(
height: constraints.maxHeight,
);
}),
),
);
}
}
// sorry for the name :)
class ExtentableTwoRowScrollable extends StatefulWidget {
const ExtentableTwoRowScrollable({
super.key,
required this.height,
this.minHeightUpper = 300.0,
this.minHeightLower = 300.0,
});
final double height;
final double minHeightUpper;
final double minHeightLower;
#override
State<ExtentableTwoRowScrollable> createState() =>
_ExtentableTwoRowScrollableState();
}
class _ExtentableTwoRowScrollableState extends State<ExtentableTwoRowScrollable>
with SingleTickerProviderStateMixin {
final upperSizeNotifier = ValueNotifier(0.0);
final lowerSizeNotifier = ValueNotifier(0.0);
var upperHeight = 0.0;
var dragOnUpper = true;
bool handleVerticalDrag(ScrollNotification notification) {
if (notification is ScrollStartNotification &&
notification.dragDetails != null)
// only act on ScrollStartNotification events with dragDetails
{
if (notification.dragDetails!.globalPosition.dy <
upperSizeNotifier.value) {
dragOnUpper = true;
} else {
dragOnUpper = false;
}
}
if (notification is ScrollUpdateNotification &&
notification.dragDetails != null)
// only act on ScrollUpdateNotification events with dragDetails
{
if (dragOnUpper) {
// dragging is going on, was started on upper ListView
if (notification.dragDetails!.delta.direction > 0)
// dragging backward/downwards
{
if (lowerSizeNotifier.value >= widget.minHeightLower)
// expand upper until minHeightLower gets hit
{
lowerSizeNotifier.value -= notification.dragDetails!.delta.distance;
upperSizeNotifier.value += notification.dragDetails!.delta.distance;
}
} else
// dragging forward/upwards
{
if (notification.metrics.extentAfter == 0.0 &&
upperSizeNotifier.value > widget.minHeightUpper)
// when at the end of upper shrink it until minHeightUpper gets hit
{
lowerSizeNotifier.value += notification.dragDetails!.delta.distance;
upperSizeNotifier.value -= notification.dragDetails!.delta.distance;
}
}
}
if (!dragOnUpper) {
// dragging is going on, was started on lower ListView
if (notification.dragDetails!.delta.direction > 0)
// dragging backward/downwards
{
if (notification.metrics.extentBefore == 0.0 &&
lowerSizeNotifier.value > widget.minHeightLower)
// when at the top of lower shrink it until minHeightLower gets hit
{
lowerSizeNotifier.value -= notification.dragDetails!.delta.distance;
upperSizeNotifier.value += notification.dragDetails!.delta.distance;
}
} else
// dragging forward/upwards
{
if (upperSizeNotifier.value >= widget.minHeightUpper)
// expand lower until minHeightUpper gets hit
{
lowerSizeNotifier.value += notification.dragDetails!.delta.distance;
upperSizeNotifier.value -= notification.dragDetails!.delta.distance;
}
}
}
}
return true;
}
#override
Widget build(BuildContext context) {
// initialize ratio of lower and upper, f.e. here 50:50
upperSizeNotifier.value = widget.height / 2;
lowerSizeNotifier.value = widget.height / 2;
return NotificationListener(
onNotification: handleVerticalDrag,
child: Column(
children: [
ValueListenableBuilder<double>(
valueListenable: upperSizeNotifier,
builder: (context, value, child) {
return Container(
color: Colors.greenAccent,
height: value,
child: ListView.builder(
shrinkWrap: true,
itemCount: 40,
itemBuilder: (BuildContext context, int index) {
return ListTile(
leading: const Icon(Icons.list),
title: Text("upper ListView $index"));
},
),
);
},
),
ValueListenableBuilder<double>(
valueListenable: lowerSizeNotifier,
builder: (context, value, child) {
return Container(
color: Colors.blueGrey,
height: value,
child: ListView.builder(
shrinkWrap: true,
itemCount: 40,
itemBuilder: (BuildContext context, int index) {
return ListTile(
leading: const Icon(Icons.list),
title: Text("lower ListView $index"));
},
),
);
},
),
],
),
);
}
}
I think it's working okayish so far but supporting the "fling" effect - I mean the acceleration when users shoot the scrollable until simulated physics slows it down again - would be really nice, too.
First, initialise two scroll controllers for two of your listviews. Then register a post-frame callback by using WidgetsBinding.instance.addPostFrameCallback to make sure that the scroll controller has been linked to a scroll view. Next, setup scroll listeners in that callback.
To listen to scrolling update you can use scrollController.addListener. Then use if-else cases to catch the position of the scroll, if scroll position equals to maxScrollExtent then the user scrolled bottom and its the other way round for minScrollExtent. Check my edited implementation below:
class HomeScreen extends StatefulWidget {
const HomeScreen({Key? key}) : super(key: key);
#override
State<HomeScreen> createState() => _HomeScreenState();
}
class _HomeScreenState extends State<HomeScreen> {
final ScrollController _scrollCtrl1 = ScrollController();
final ScrollController _scrollCtrl2 = ScrollController();
double height1 = 300;
double height2 = 300;
bool isLoading = true;
#override
void initState() {
WidgetsBinding.instance.addPostFrameCallback((timeStamp) {
setState(() {
isLoading = false;
height1 = SizeConfig.blockSizeVertical! * 50;
height2 = SizeConfig.blockSizeVertical! * 50;
});
_scrollCtrl1.addListener(() {
if (_scrollCtrl1.position.pixels == _scrollCtrl1.position.maxScrollExtent) {
setState(() {
height1 = SizeConfig.blockSizeVertical! * 25;
height2 = SizeConfig.blockSizeVertical! * 75;
});
}
if (_scrollCtrl1.position.pixels == _scrollCtrl1.position.minScrollExtent) {
setState(() {
height1 = SizeConfig.blockSizeVertical! * 75;
height2 = SizeConfig.blockSizeVertical! * 25;
});
}
});
_scrollCtrl2.addListener(() {
if (_scrollCtrl2.position.pixels == _scrollCtrl2.position.maxScrollExtent) {
setState(() {
height1 = SizeConfig.blockSizeVertical! * 25;
height2 = SizeConfig.blockSizeVertical! * 75;
});
}
if (_scrollCtrl2.position.pixels == _scrollCtrl2.position.minScrollExtent) {
setState(() {
height1 = SizeConfig.blockSizeVertical! * 75;
height2 = SizeConfig.blockSizeVertical! * 25;
});
}
});
});
super.initState();
}
#override
Widget build(BuildContext context) {
SizeConfig().init(context);
return Scaffold(
body: !isLoading ? Column(
children: [
AnimatedContainer(
color: Colors.blueGrey,
height: height1,
duration: const Duration(seconds: 1),
curve: Curves.fastOutSlowIn,
child: ListView.builder(
itemCount: 50,
padding: EdgeInsets.zero,
controller: _scrollCtrl1,
itemBuilder: (BuildContext context, int index) {
return ListTile(
leading: const Icon(Icons.list),
dense: true,
trailing: const Text(
"GFG",
style: TextStyle(color: Colors.green, fontSize: 15),
),
title: Text("List item $index"));
}),
),
AnimatedContainer(
height: height2,
color: Colors.deepPurpleAccent,
duration: const Duration(seconds: 1),
curve: Curves.fastOutSlowIn,
child: ListView.builder(
itemCount: 50,
padding: EdgeInsets.zero,
controller: _scrollCtrl2,
itemBuilder: (BuildContext context, int index) {
return ListTile(
dense: true,
leading: const Icon(Icons.list),
trailing: const Text(
"GFG",
style: TextStyle(color: Colors.green, fontSize: 15),
),
title: Text("aaaaaaaaa $index"));
}),
),
],
) : const Center(child: CircularProgressIndicator(),),
);
}
}
class SizeConfig {
static MediaQueryData? _mediaQueryData;
static double? screenWidth;
static double? screenHeight;
static double? blockSizeHorizontal;
static double? blockSizeVertical;
/// This class measures the screen height & width.
/// Remember: Always call the init method at the start of your application or in main
void init(BuildContext? context) {
_mediaQueryData = MediaQuery.of(context!);
screenWidth = _mediaQueryData?.size.width;
screenHeight = _mediaQueryData?.size.height;
blockSizeHorizontal = (screenWidth! / 100);
blockSizeVertical = (screenHeight! / 100);
}
}

How can I display indexed list of images in flutter?

I want to create widget which images are displayed in a row.
Width of each images are same as device width.
So I created listview which has image widget.
but some of images are bigger than device height and image is cropped.
What can I do for my flutter project?
This is what I tried.
ListView.builder(
shrinkWrap: true,
scrollDirection:
widget.options.isHorizontal! ? Axis.horizontal : Axis.vertical,
physics:
widget.options.isHorizontal! ? const PageScrollPhysics() : null,
itemCount: widget.items?.length,
itemBuilder: (BuildContext context, int index) {
return Image.memory(
widget.items![index],
width: widget.options.isHorizontal!
? null
: MediaQuery.of(context).size.width,
height: widget.options.isHorizontal!
? MediaQuery.of(context).size.height
: null,
fit:
widget.options.isHorizontal! ? BoxFit.contain : BoxFit.fitWidth,
);
},
)
you can use ConstrainedBox
this is example code.
The JumpTo part of this example is not perfect yet, I hope you can realize it by learning ScrollNotification.
import 'package:flutter/material.dart';
class MyWidget extends StatefulWidget {
const MyWidget({super.key});
#override
State<MyWidget> createState() => _MyWidgetState();
}
class _MyWidgetState extends State<MyWidget> {
late ScrollController _controller;
bool lock = false;
#override
void initState() {
_controller = ScrollController();
super.initState();
}
#override
void dispose() {
_controller.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
var maxWidth = MediaQuery.of(context).size.width;
var maxHeight = MediaQuery.of(context).size.height;
return Scaffold(
body: NotificationListener<ScrollNotification>(
child: ListView.builder(
controller: _controller,
physics: ClampingScrollPhysics(),
itemCount: 20,
itemExtent: maxWidth,
itemBuilder: (context, index) {
return ConstrainedBox(
constraints:
BoxConstraints(maxWidth: maxWidth, maxHeight: maxHeight),
child: Image.network('https://picsum.photos/600'));
},
scrollDirection: Axis.horizontal,
),
onNotification: (ScrollNotification notification) {
if (notification is ScrollStartNotification) {
lock = false;
}
if (notification is ScrollEndNotification &&
!lock &&
_controller.position.isScrollingNotifier.value &&
_controller.position.hasPixels) {
lock = true;
var current = _controller.offset;
var diffIndex = (current / maxWidth).floor();
var isNext = ((current / maxWidth).toDouble() * 0.001) > 0.5;
var nexPosition = 0.0;
if (isNext) {
nexPosition = (diffIndex + 1) * maxWidth;
} else {
nexPosition = diffIndex * maxWidth;
}
_controller.removeListener(() {});
Future.delayed(Duration.zero, () {
// _controller.jumpTo(nexPosition);
_controller.animateTo(nexPosition,
duration: Duration(milliseconds: 400),
curve: Curves.fastOutSlowIn);
}).whenComplete(() => _controller.addListener(() {}));
}
return lock;
},
),
);
}
}

Disable scrolling of CustomScrollView while scrolling without setState Flutter

I have multiple widget and lists within CustomScrollView and I would like to stop CustomScrollView to scroll while scrolling on some pixels bound condition.
I can use NeverScrollPhysics() to stop it but I don't want to use setState() function here because the CustomScrollview content with lists is big enough to make the screen laggy while reloading on scroll.
Also tried with Provider but the builder is providing only a child widget which is not working with sliver list.
Here is the code using setState() :
NotificationListener(
onNotification: (ScrollNotification notif) {
if(notif is ScrollUpdateNotification) {
if (canScroll && notif.metrics.pixels > 100) {
canScroll = false;
setState(() {});
}
}
if(notif is ScrollEndNotification) {
if(!canScroll) {
canScroll = true;
setState(() {});
}
}
return true;
},
child: CustomScrollView(
shrinkWrap: true,
physics: canScroll ? BouncingScrollPhysics() : NeverScrollableScrollPhysics(),
slivers: [
SliverToBoxAdapter(),
List(),
List(),
],
),
),
Is there a way to reload only the CustomScrollView without its child ? Otherwise any workaround to prevent scrolling in this case ?
Thanks for help
When the build method is called, all widgets in that build method will be rebuild except for const widgets, but const widget cannot accept dynamic arguments (only a constant values).
Riverpod provides a very good solution in this case,
With ProviderScope you can pass arguments by inherited widget instead of widget constructor (as when passing arguments using navigation) so the contractor can be const.
Example :
Data module
TLDR you need to use Freezed package or override the == operator and the hashCode almost always because of dart issue.
class DataClass {
final int age;
final String name;
const DataClass(this.age, this.name);
#override
bool operator ==(Object other) {
if (identical(this, other)) return true;
return other is DataClass && other.age == age && other.name == name;
}
#override
int get hashCode => age.hashCode ^ name.hashCode;
}
setting our ScopedProvider as a global variable
final dataClassScope = ScopedProvider<DataClass>(null);
The widget we use in our list
class MyChildWidget extends ConsumerWidget {
const MyChildWidget();
#override
Widget build(BuildContext context, ScopedReader watch) {
final data = watch(dataClassScope);
// Note for better optimaization
// in case you are sure the data you are passing to this widget wouldn't change
// you can just use StatelessWidget and set the data as:
// final data = context.read(dataClassScope);
// use ConsumerWidget (or Consumer down in this child widget tree) if the data has to change
print('widget with name\n${data.name} rebuild');
return SliverToBoxAdapter(
child: Padding(
padding: const EdgeInsets.symmetric(vertical: 40, horizontal: 20),
child: Text(
'Name : ${data.name}\nAge ${data.age}',
textAlign: TextAlign.center,
),
),
);
}
}
finally the main CustomScrollView widget
class MyMainWidget extends StatefulWidget {
const MyMainWidget();
#override
State<MyMainWidget> createState() => _MyMainWidgetState();
}
class _MyMainWidgetState extends State<MyMainWidget> {
bool canScroll = true;
void changeCanScrollState() {
setState(() => canScroll = !canScroll);
print('canScroll $canScroll');
}
final dataList = List.generate(
20,
(index) => DataClass(10 * index, '$index'),
);
#override
Widget build(BuildContext context) {
return Scaffold(
body: GestureDetector(
behavior: HitTestBehavior.opaque,
onTap: () {
changeCanScrollState();
},
child: CustomScrollView(
shrinkWrap: true,
physics: canScroll
? BouncingScrollPhysics()
: NeverScrollableScrollPhysics(),
slivers: [
for (int i = 0; i < dataList.length; i++)
ProviderScope(
overrides: [
dataClassScope.overrideWithValue(dataList[i]),
],
child: const MyChildWidget(),
),
],
),
),
);
}
}
Don't forget to wrap the MaterialApp with ProviderScope.
runApp(
ProviderScope(
child: MyApp(),
),
);
Try this solution use const constructor for child widget so it won't rebuild unless widget changed
class MyHomePage extends StatelessWidget {
ValueNotifier<ScrollPhysics> canScroll =
ValueNotifier(const BouncingScrollPhysics());
MyHomePage({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return Scaffold(
body: NotificationListener(
onNotification: (ScrollNotification notif) {
if (notif is ScrollUpdateNotification) {
if (canScroll.value.runtimeType == BouncingScrollPhysics &&
notif.metrics.pixels > 100) {
canScroll.value = const NeverScrollableScrollPhysics();
debugPrint("End false");
}
}
if (notif is ScrollEndNotification) {
if (canScroll.value.runtimeType == NeverScrollableScrollPhysics) {
debugPrint("End");
Future.delayed(const Duration(milliseconds: 300),
() => canScroll.value = const BouncingScrollPhysics());
debugPrint("End1");
}
}
return true;
},
child: ValueListenableBuilder(
valueListenable: canScroll,
builder:
(BuildContext context, ScrollPhysics scrollType, Widget? child) =>
CustomScrollView(
physics: scrollType,
slivers: [
SliverToBoxAdapter(
child: Container(
height: 200,
color: Colors.black,
),
),
SliverToBoxAdapter(
child: Column(
children: [
Container(
height: 100,
color: Colors.blue,
),
Container(
height: 200,
color: Colors.grey,
),
Container(
height: 200,
color: Colors.blue,
),
Container(
height: 200,
color: Colors.grey,
),
Container(
height: 200,
color: Colors.blue,
),
],
),
),
],
),
),
),
);
}
}
Are you just need to stop the user from scrolling it? I think you can try to controller the list to a fixed position by using jumoTo.
...
final _controller = ScrollController();
#override
Widget build(BuildContext context) {
return NotificationListener(
onNotification: (ScrollNotification notif) {
if (notif is ScrollUpdateNotification) {
if (notif.metrics.pixels > 100) {
_controller.jumpTo(100)
}
}
return true;
},
child: CustomScrollView(
controller: _controller,
...

Rive's stateMachine with TextFiled

I Like to create animation that will follow the TextField text, here is video from
Flutter YouTube.
Now how can I follow the Target.
Here is My rive file on rive.app or GitHub and Design.
We need a StateMachineController and SMIInput<double> that will be responsible to follow the text.
Result
OutPutVideo
Result with Slider
You can follow the GitHub Repository or
class TextFieldWithRive extends StatefulWidget {
TextFieldWithRive({Key? key}) : super(key: key);
#override
_TextFieldWithRiveState createState() => _TextFieldWithRiveState();
}
class _TextFieldWithRiveState extends State<TextFieldWithRive> {
StateMachineController? controller;
SMIInput<double>? valueController;
Artboard? _riveArtboard;
double sliderVal = 0.0;
/// change value based on size>Width
final double strengthOverTextFiled = 1.5;
final TextEditingController textEditingController = TextEditingController();
#override
void initState() {
super.initState();
rootBundle.load("rives/eyeMovement.riv").then((value) async {
final file = RiveFile.import(value);
final artboard = file.mainArtboard;
controller = StateMachineController.fromArtboard(artboard, "eyeMovement");
if (controller != null) {
print("got state");
setState(() {
artboard.addController(controller!);
valueController = controller!.findInput('moevement_controll');
controller!.inputs.forEach((element) {
print(element.name);
});
});
}
_riveArtboard = artboard;
});
///* eye controll with textFiled
textEditingController.addListener(() {
print(textEditingController.text);
if (valueController != null) {
valueController!.value =
textEditingController.text.length * strengthOverTextFiled;
}
});
}
#override
void dispose() {
textEditingController.removeListener(() {});
textEditingController.dispose();
controller!.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: LayoutBuilder(
builder: (context, constraints) => Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
SizedBox(
height: constraints.maxWidth * .5,
width: constraints.maxWidth * .5,
child: _riveArtboard == null
? CircularProgressIndicator()
: Rive(
artboard: _riveArtboard!,
),
),
SizedBox(
width: constraints.maxWidth * .8,
child: TextField(
controller: textEditingController,
decoration: InputDecoration(hintText: "keep typing"),
),
),
],
),
),
),
);
}
}

How to create a listview that makes centering the desired element

I'm doing something similar to this video: https://youtu.be/fpqHUp4Sag0
With the following code I generate the listview but when using the controller in this way the element is located at the top of the listview and I need it to be centered
Widget _buildLyric() {
return ListView.builder(
itemBuilder: (BuildContext context, int index) => _buildPhrase(lyric[index]),
itemCount: lyric.length,
itemExtent: 90.0,
controller: _scrollController,
);
}
void goToNext() {
i += 1;
if (i == lyric.length - 1) {
setState(() {
finishedSync = true;
});
}
syncLyric.addPhrase(
lyric[i], playerController.value.position.inMilliseconds);
_scrollController.animateTo(i*90.0,
curve: Curves.ease, duration: new Duration(milliseconds: 300));
}
Using center and shrinkWrap: true
Center(
child: new ListView.builder(
shrinkWrap: true,
itemCount: list.length,
itemBuilder: (BuildContext context, int index) {
return Text("Centered item");
},
),
);
You're going to have to do some math! (Nooooo, not the mathssssss).
It seems as though your goToNext() function is called while the app is running, rather than during build time. This makes it a little easier - you can simply use context.size. Otherwise you'd have to use a LayoutBuilder and maxHeight.
You can then divide this in two to get the half, then add/subtract whatever you need to get your item positioned how you want (since you've specified it's height as 90 in the example, I assume you could use 45 to get what you want).
Here's an example you can paste into a file to run:
import 'dart:async';
import 'package:flutter/material.dart';
void main() => runApp(Wid());
class Wid extends StatelessWidget {
#override
Widget build(BuildContext context) {
return new MaterialApp(
home: Scaffold(
appBar: AppBar(
title: Text("Scrolling by time"),
),
body: new Column(
children: <Widget>[
Expanded(child: Container()),
Container(
height: 300.0,
color: Colors.orange,
child: ScrollsByTime(
itemExtent: 90.0,
),
),
Expanded(child: Container()),
],
),
),
);
}
}
class ScrollsByTime extends StatefulWidget {
final double itemExtent;
const ScrollsByTime({Key key, #required this.itemExtent}) : super(key: key);
#override
ScrollsByTimeState createState() {
return new ScrollsByTimeState();
}
}
class ScrollsByTimeState extends State<ScrollsByTime> {
final ScrollController _scrollController = new ScrollController();
#override
void initState() {
super.initState();
Timer.periodic(Duration(seconds: 1), (timer) {
_scrollController.animateTo(
(widget.itemExtent * timer.tick) - context.size.height / 2.0 + widget.itemExtent / 2.0,
duration: Duration(milliseconds: 300),
curve: Curves.ease,
);
});
}
#override
Widget build(BuildContext context) {
return ListView.builder(
itemBuilder: (context, index) {
return Center(child: Text("Item $index"));
},
itemExtent: widget.itemExtent,
controller: _scrollController,
);
}
}
I had a similar problem, but with the horizontal listview. You should use ScrollController and NotificationListener. When you receive endScroll event you should calculate offset and use scroll controller animateTo method to center your items.
class SwipeCalendarState extends State<SwipeCalendar> {
List<DateTime> dates = List();
ScrollController _controller;
final itemWidth = 100.0;
#override
void initState() {
_controller = ScrollController();
_controller.addListener(_scrollListener);
for (var i = 1; i < 365; i++) {
var date = DateTime.now().add(Duration(days: i));
dates.add(date);
}
// TODO: implement initState
super.initState();
}
#override
Widget build(BuildContext context) {
// TODO: implement build
return Container(
height: 200,
child: Stack(
children: <Widget>[buildListView()],
),
);
}
void _onStartScroll(ScrollMetrics metrics) {
}
void _onUpdateScroll(ScrollMetrics metrics){
}
void _onEndScroll(ScrollMetrics metrics){
print("scroll before = ${metrics.extentBefore}");
print("scroll after = ${metrics.extentAfter}");
print("scroll inside = ${metrics.extentInside}");
var halfOfTheWidth = itemWidth/2;
var offsetOfItem = metrics.extentBefore%itemWidth;
if (offsetOfItem < halfOfTheWidth) {
final offset = metrics.extentBefore - offsetOfItem;
print("offsetOfItem = ${offsetOfItem} offset = ${offset}");
Future.delayed(Duration(milliseconds: 50), (){
_controller.animateTo(offset, duration: Duration(milliseconds: 100), curve: Curves.linear);
});
} else if (offsetOfItem > halfOfTheWidth){
final offset = metrics.extentBefore + offsetOfItem;
print("offsetOfItem = ${offsetOfItem} offset = ${offset}");
Future.delayed(Duration(milliseconds: 50), (){
_controller.animateTo(offset, duration: Duration(milliseconds: 100), curve: Curves.linear);
});
}
}
Widget buildListView() {
return NotificationListener<ScrollNotification>(
onNotification: (scrollNotification) {
if (scrollNotification is ScrollStartNotification) {
_onStartScroll(scrollNotification.metrics);
} else if (scrollNotification is ScrollUpdateNotification) {
_onUpdateScroll(scrollNotification.metrics);
} else if (scrollNotification is ScrollEndNotification) {
_onEndScroll(scrollNotification.metrics);
}
},
child: ListView.builder(
itemCount: dates.length,
controller: _controller,
scrollDirection: Axis.horizontal,
itemBuilder: (context, i) {
var item = dates[i];
return Container(
height: 100,
width: itemWidth,
child: Center(
child: Text("${item.day}.${item.month}.${item.year}"),
),
);
}),
);
}
}
IMO the link you have posted had some wheel like animation. Flutter provides this type of animation with ListWheelScrollView and rest can be done with the fade in animation and change in font weight with ScrollController.