Last element is hidden behind BottomAppBar with ReorderableListView - flutter

I'm using a ReorderableListView builder in combination with a Bottom App Bar with a FAB.
I set extendBody because I want the FAB notch to be transparent when scrolling up/down the list. However with a ReorderableListView I cannot scroll down to the last list item because the list item is hidden behind the Bottom App Bar.
Here's a demonstration:
This is not the case with a regular ListView as you can see in the code example below and on this picture right here. This would be my intended behaviour because the last list item is fully visible:
Important: It's not a solution to just set extendBody to false because there would be no transparent notch when scrolling. The scrolling behaviour itself is correct for ReorderableListView and ListView and looks like this when scrolling:
Is there a way to access the last list element with a Reorderable List View similar than with a List View?
Here's a code example with both versions:
import 'package:flutter/material.dart';
void main() {
runApp(SO());
}
class SO extends StatelessWidget {
List<String> myList = ["a", "b", "c", "d", "e", "f", "g", "h"];
#override
Widget build(BuildContext context) {
return MaterialApp(
home: Builder(
builder: (context) => Scaffold(
extendBody: true,
appBar: AppBar(),
body:
// CANNOT scroll up to the last element
ReorderableListView.builder(
itemCount: myList.length,
itemBuilder: (BuildContext context, int index) {
return Container(
key: ValueKey(myList[index]),
height: 150,
color: Colors.green,
child: Center(child: Text('Entry ${myList[index]}')),
);
},
onReorder: (oldIndex, newIndex){
if (newIndex > oldIndex) newIndex --;
final item = myList.removeAt(oldIndex);
myList.insert(newIndex, item);
},
),
/*
// CAN scroll up to the last element
ListView.builder(itemCount: myList.length,
itemBuilder: (BuildContext context, int index) {
return Container(
key: ValueKey(myList[index]),
height: 150,
color: Colors.green,
child: Center(child: Text('Entry ${myList[index]}')),
);
},
),
*/
floatingActionButtonLocation: FloatingActionButtonLocation.centerDocked,
floatingActionButton: FloatingActionButton(
onPressed: () {},
),
bottomNavigationBar: BottomAppBar(
shape: const CircularNotchedRectangle(),
notchMargin: 18,
color: Colors.blue,
child: Container(
height: 60,
),
),
)
)
);
}
}

You can add padding like this:
ReorderableListView.builder(
padding: EdgeInsets.only(bottom: kBottomNavigationBarHeight + 16),//<--- add this
itemCount: myList.length,
itemBuilder: (BuildContext context, int index) {
return Container(
key: ValueKey(myList[index]),
height: 150,
color: Colors.green,
child: Center(child: Text('Entry ${myList[index]}')),
);
},
onReorder: (oldIndex, newIndex) {
if (newIndex > oldIndex) newIndex--;
final item = myList.removeAt(oldIndex);
myList.insert(newIndex, item);
},
),

Add a bottom edge inset. An inset would add padding inside the control without losing your transparent notch behaviour.
This can be done with something like -
ReorderableListView.builder(
padding(EdgeInsets.only(bottom: 60.0)),
...
)
This is based on the documentation here though I think it is a weird parameter naming as padding suggests offet but documentation says this as inset. Hence I hope this works for you.

Add dynamic padding to the ReorderableListview as below
ReorderableListView.builder(
padding: EdgeInsets.only(bottom: kBottomNavigationBarHeight + 15.0), // add this
itemCount: myList.length,
itemBuilder: (BuildContext context, int index) {
return Container(
key: ValueKey(myList[index]),
height: 150,
color: Colors.green,
child: Center(child: Text('Entry ${myList[index]}')),
);
},
onReorder: (oldIndex, newIndex){
if (newIndex > oldIndex) newIndex --;
final item = myList.removeAt(oldIndex);
myList.insert(newIndex, item);
},
),
kBottomNavigationBarHeight
get the height of bottom navigation bar and add as padding..

Related

Flutter Retrigger Staggered Animation on ListView

I am using flutter_staggered_animations in my app for my listView. It is working quite nice when starting the app.
Problem:
I want the animation to be triggered if I change the child-widget of the listView or even just the itemCount. So what I need is a rebuild of the staggeredList.
But how can I do that? I tried simply changing the child or itemCount with setState. But that is triggering an animation...
Couldn't find anything on this. Let me know if you need more info!
I use pretty much the exact code from the example:
#override
Widget build(BuildContext context) {
return Scaffold(
body: AnimationLimiter(
child: ListView.builder(
itemCount: 100,
itemBuilder: (BuildContext context, int index) {
return AnimationConfiguration.staggeredList(
position: index,
duration: const Duration(milliseconds: 375),
child: SlideAnimation(
verticalOffset: 50.0,
child: FadeInAnimation(
child: YourListChild(),
),
),
);
},
),
),
);
}
You can provide a new key on AnimationLimiter, and it will recreate the AnimationLimiter,
AnimationLimiter(
key: ValueKey("$itemCount"),
child: ListView.builder(
class STA extends StatefulWidget {
const STA({super.key});
#override
State<STA> createState() => _STAState();
}
class _STAState extends State<STA> {
int itemCount = 5;
#override
Widget build(BuildContext context) {
return Scaffold(
floatingActionButton: FloatingActionButton(
onPressed: () {
itemCount++;
setState(() {});
},
),
body: AnimationLimiter(
key: ValueKey("$itemCount"),
child: ListView.builder(
itemCount: itemCount,
itemBuilder: (BuildContext context, int index) {
return AnimationConfiguration.staggeredList(
position: index,
duration: const Duration(milliseconds: 375),
child: SlideAnimation(
verticalOffset: 50.0,
child: FadeInAnimation(
child: Padding(
padding: const EdgeInsets.all(8.0),
child: Container(
height: 50,
color: index.isEven ? Colors.amber : Colors.purple,
),
),
),
),
);
},
),
),
);
}
}
Because the widget AnimationLimiter of this package is prevent you reanimate when setState
Problem is if you remove that widget, ListView will be reanimate when you scroll back to old position, that will be ugly
If you still want animation like you want, I recommend you to write your custom AnimatedList, I have do one which AnimatedList like this :
AnimatedList(
key: _listKey,
itemBuilder: (_, index, animation) {
return SlideTransition(
position: Tween<Offset>(
begin: const Offset(-0.5, 0.0),
end: const Offset(0.0, 0.0),
).animate(animation),
child: YourItemWidget(),
);
},
)
then you can use insert function to add single item animate to list :
_listKey.currentState?.insertItem

Flutter Card Swipe Animation with forward and reverse methode without a loop

can i implement my own card swip animation ?
what i want is something similar to this package here. but the slides should shown from the top like so: (and without a loop)
my code:
final controller = SwiperController();
List<Widget> _pages = [
Container(color: Colors.blue),
Container(color: Colors.black),
];
#override
Widget build(BuildContext context) {
return Scaffold(
body: new Swiper(
loop: false,
controller: SwiperController(),
itemBuilder: (BuildContext context, int index) {
return Padding(
padding: const EdgeInsets.symmetric(horizontal: 40.0),
child: _pages[index],
);
},
itemCount: _pages.length,
itemWidth: 400.0,
itemHeight: 700.0,
layout: SwiperLayout.TINDER,
),
);
}
}
can you suggest me a way to reach that and thanks.
My editor warns me about this:
The library 'package:flutter_swiper/flutter_swiper.dart' is legacy, and shouldn't be imported into a null safe library.
So I guess you should search for a up-to-date, null-safe package which does the same.
And for the vertical layout that you want to achieve... Can't you just rotate the whole swiper widget by 90 degrees?
Like so:
#override
Widget build(BuildContext context) {
return Scaffold(
body: RotatedBox(
quarterTurns: 1,
child: Swiper(
loop: false,
controller: SwiperController(),
itemBuilder: (BuildContext context, int index) {
return Padding(
padding: const EdgeInsets.symmetric(horizontal: 40.0),
child: _pages[index],
);
},
itemCount: _pages.length,
itemWidth: 400.0,
itemHeight: 700.0,
layout: SwiperLayout.TINDER,
),
),
);
}

Obx - Rebuilding a list on variable change

I am trying to make a stories progress bar.
I have a PageView that shows a new video per page, and a progress bar on top that should show the progress of each PageView when I scroll to it.
The problem is the stories bar only updates the very first progress bar. I need them all to keep listening to the updates.
Video progress code:
RxDouble getVideoProgress(int storyBarIndex) {
if (storyBarIndex < pageIndex.value) {
return 1.0.obs;
} else if (storyBarIndex == pageIndex.value) {
final properVideoController = Get.put(
ProperVideoController(video: pageVideo!.value),
tag: ValueKey(pageVideo!.value.uid + '_' + user.authUid + 'tt')
.toString());
return properVideoController.videoProgress;
} else {
return 0.0.obs;
}
}
ListView code:
Widget build(BuildContext context) {
return GetBuilder<ProfileViewController>(
init: ProfileViewController(),
builder: (controller) => Container(
width: Get.width,
child: LayoutBuilder(
builder: ((context, constraints) => Column(
children: [
Container(
height: 4,
child: ListView.builder(
itemCount: itemCount,
scrollDirection: Axis.horizontal,
itemBuilder: (context, index) {
return _ProgressBarStep(
width: constraints.maxWidth / 6.2,
index: index,
);
}),
),
],
))),
),
);
}
Single Story bar widget
Widget build(BuildContext context) {
return GetBuilder<ProfileViewController>(
init: ProfileViewController(),
builder: (controller) {
return Padding(
padding: const EdgeInsets.only(right: 0.0),
child: Obx(
() {
return LinearPercentIndicator(
barRadius: Radius.circular(20),
animation: true,
width: width,
lineHeight: 9.0,
percent: controller.getVideoProgress(index).value,
// ignore: deprecated_member_use
linearStrokeCap: LinearStrokeCap.roundAll,
backgroundColor: TabooColors.darkBlueGrey,
progressColor: TabooColors.red,
);
},
));
});
}
You may remove the GetBuilder widget and just use the Obx it would work. Need to add Get.put(ProfileViewController()); above the Widget build() method. I hope this might help you. Please go through the docs: https://pub.dev/packages/get
When we use GetBuilder then we also need to call the update() method in GetxController class to update the UI

Flutter - how to scroll a ListView automatically to make a partially visible Flat Button completely visible

My App is a simple AppBar and a horizontal scrollable ListView of ten FlatButtons.
Screenshot
As you can see there is a partially visible FlatButton.
I want to scroll the ListView programatically when user press the partially visible FlatButton, to make this one completely visible.
How can i do that?
I attach my code here:
class _DashboardScreenState extends State<DashboardScreen> {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('FlutterApp'),
),
body: Container(
height: 70.0,
child: ListView.builder(
scrollDirection: Axis.horizontal,
itemCount: 10,
itemBuilder: (context, index) => _scrollableList(context, index),
),
),
);
}
}
Widget _scrollableList (BuildContext context, int index){
return Container(
child: FlatButton(
color: Colors.white,
textColor: Colors.red,
onPressed: (){},
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(10.0),
side: BorderSide(color: Colors.red)
),
child: Text('data $index'),
)
);
}
you can use ScrollablePositionedList from `flutter_widgets plugin to achieve this.
Create you ListView as below:
ItemScrollController _scrollController = ItemScrollController();
ScrollablePositionedList.builder(
itemScrollController: _scrollController,
scrollDirection: Axis.horizontal,
itemCount: 10,
itemBuilder: (context, index) => _scrollableList(context, index),
)
on tap of button scroll to that button as below:
onPressed: (){
_scrollController.scrollTo(index: index, duration: Duration(seconds: 1));
}
let me know if you have any doubts.

How to display progress indicator in flutter when a certain condition is true?

I have widget which return CircularProgressIndicator()
it shows on the circular mark upper left of screen.
However I want to put this as overlay and put at the center of screen.
I am checking widget list but I cant find what Widget should I use as overlay.
On which layer should I put this on??
For now my code is like this ,when loading it shows CircularProgressIndicator instead of ListView
However I want to put CircularProgressIndicator() on ListView
Widget build(BuildContext context) {
if(loading) {
return CircularProgressIndicator();
}
return ListView.builder(
controller: _controller,
itemCount: articles.length,
itemBuilder: (context, index) {
return ListTile(
title: Text(articles[index]),
);
},
);
}
Thank you very much for answers.
I solve with stack Widget like this below.
At first I try to use overlay, but I bumped into some errors.
So, I use simply stack.
#override
Widget build(BuildContext context) {
Widget tempWidget = new CircularProgressIndicator();
if(loading) {
tempWidget = new CircularProgressIndicator();
}
else {
tempWidget = new Center();//EmptyWidget
}
return Stack(
children: <Widget>[
ListView.builder(
controller: _controller,
itemCount: articles.length,
itemBuilder: (context, index) {
return ListTile(
title: Text(articles[index].title),
onTap: () => onTapped(context,articles[index].url),
);
},
),
Center(
child: tempWidget
),
]
);
}
Stack(
children: <Widget>[
ListView.builder(
itemBuilder: (BuildContext context, int index) {
return Container(
height: 100,
color: Colors.red,
);
},
itemCount: 10,
),
Center(
child: CircularProgressIndicator(),
),
],
)
Stack(
children: <Widget>[
ListView.builder(
itemBuilder: (BuildContext context, int index) {
return Container(
height: 100,
color: Colors.red,
);
},
itemCount: 10,
),
isLoading? Container(child: Center(
child: CircularProgressIndicator(),
)): Container(), //if isLoading flag is true it'll display the progress indicator
],
)
or you can use futureBuilder or streamBuilder when loading data from somewhere and you want to change the ui depending on the state
To overlay or position items on top of each other you would usually use a Stack widget or Overlay as described here. For your usecase I would recommend checking out the modal progress hud package.