ListView keeps data even if you exit the page - flutter

Good morning/evening. Hope you are well.
I have a problem with my ListView.
I receive an Article object in the constructor of my EditArticlePage(StatefulWidget) and one of the properties of my Article is a List of links that I transmit in the constructor of my provider EditArticlePageProvider, then I retrieve these images which I display in a ListView.
When I delete an image from my ListView, it is deleted, so far, no problem ( 2 images - 1 image = 1 image).
But when I leave the EditArticlePage(press back button or back button on appbar), and come back to it, my ListView is kept with the modifications I made before, instead of the initial data of the Article.
P.S: all other data is reset except the ListView containing the images.
Here is my code:
My EditArticlePage class
class EditArticlePage extends StatefulWidget {
final Article article;
EditArticlePage(this.article, {Key key}) : super(key: key);
#override
_EditArticlePageState createState() => _EditArticlePageState();
}
class _EditArticlePageState extends State<EditArticlePage> {
#override
Widget build(BuildContext context) {
return ChangeNotifierProvider<EditArticlePageProvider>(
create: (_) =>
EditArticlePageProvider(originalImages: widget.article.imagesLinks),
child: Consumer<EditArticlePageProvider>(
builder: (context, editProvider, __) {
final screenWidth = MediaQuery.of(context).size.width;
final screenHeight = MediaQuery.of(context).size.height;
return WillPopScope(
onWillPop: () {
return Future<bool>.value(true);
},
child: Scaffold(
body: ListView(
children: [
buildArticleImages(screenWidth, screenHeight, editProvider),
],
),
),
);
},
),
);
}
}
buildArticleImages methodes
Widget buildArticleImages(double screenWidth, double screenHeight,
EditArticlePageProvider editProvider) =>
Container(
padding: EdgeInsets.symmetric(horizontal: 5.0),
width: screenWidth,
height: screenHeight * 0.15,
child: ListView.separated(
scrollDirection: Axis.horizontal,
itemBuilder: (_, index) {
final imageLink = editProvider.originalImages[index];
return Stack(
children: [
SizedBox(
height: screenHeight * 0.15,
width: screenWidth * 0.35,
child: ClipRRect(
borderRadius: BorderRadius.all(
Radius.circular(9.0),
),
child: Image.network(
imageLink,
fit: BoxFit.cover,
)
),
Positioned(
child: DeleteButton(
onPressed: () {
editProvider.addToDeleteList(imageLink, index);
},
),
right: 3.0,
top: 3.0,
)
],
);
},
separatorBuilder: (_, index) => SizedBox(width: 5.0),
itemCount: editProvider.originalImages.length,
),
);
My provider
class EditArticlePageProvider extends ChangeNotifier {
final List originalImages;
EditArticlePageProvider({#required this.originalImages});
List<String> imagesToDelete = [];
List<String> get imagesToDelete => _imagesToDelete;
void addToDeleteList(String imageLink, int index) {
if (imageLink != null) {
imagesToDelete.add(imageLink);
originalImages.removeAt(index);
notifyListeners();
}
}
}
May you guy help me please ? And tell me if i'm doing wrong something THANKS!

Because the originalImages and the widget.article.imagesLinks are actually references to the same list object, when originalImages.removeAt(index); in addToDeleteList method, it will remove the image at the index of this list object and the two references are affected.
Then, if you want to keep the images in widget.article.imagesLinks, you can copy the list in EditArticlePageProvider:
EditArticlePageProvider({#required List originalImages}) : this.originalImages = List.of(originalImages);

Related

Flutter PageView and ListWheelScrollView asset image error

i use images in ListWheelScrollView in PageView. i have 3 pages and 60 same images in all pages in listwheelscrollview. when i try to change to second page (red page), I see that the images are not loading. i tried to precacheImage for all images but it didnt work.
i think the problem is with ListWheelScrollView's lazy loading. how can i load all widgets in ListWheelScrollView when ListWheelScrollView created?
i try to create sample code, i hope it is enough. there are 3 images in code but if you try 10-15 images, you will see error.
import 'package:flutter/material.dart';
class IntroPage extends StatelessWidget {
final _images = ["image1", "image3", "image2"];
late List<FixedExtentScrollController> _scrollControllers;
IntroPage({Key? key}) : super(key: key) {
_scrollControllers = List.generate(_images.length, (index) => FixedExtentScrollController(initialItem: index));
}
#override
Widget build(BuildContext context) {
// _precacheImages(context);
return Scaffold(
body: SafeArea(
child: PageView.builder(
clipBehavior: Clip.none,
itemCount: 3,
itemBuilder: (_, index) => _buildImagesWheel(index),
),
),
);
}
Widget _buildImagesWheel(final int index) {
return RotatedBox(
quarterTurns: -1,
child: ListWheelScrollView(
itemExtent: 250.0,
controller: _scrollControllers[index],
physics: const NeverScrollableScrollPhysics(),
children: [for (final image in _images) _buildImage(image)],
),
);
}
Widget _buildImage(final String image) {
return RotatedBox(
quarterTurns: 1,
child: SizedBox(
width: 250.0,
height: 250.0,
child: Image.asset(
image,
fit: BoxFit.contain,
color: Colors.black,
),
),
);
}
/// methods
void _precacheImages(final BuildContext context) {
for (final image in _images) {
precacheImage(AssetImage(image), context);
}
}
}

flutter Infinite Scrolling for ListView.builder

I've to use graphql query and I've got data page by page.
so I need to Infinite Scrolling in my list view builder but I don't know how to add num in page.
can anyone help me, please?
this is my query:
query homeview(\$moreId: ID!, \$page: Int! ){
homeview(HM_ID: \$moreId, page: \$page){
HM_ID
HM_Type_ID
HM_Type_Name
}
}
""";
and this is my variable to pass int number in page:
dynamic pageNum = 0;
here is the controller :
ScrollController _scrollController = new ScrollController( initialScrollOffset: 10,
and this is my list view builder:
class MoreEpd extends StatefulWidget {
final String moreId;
const MoreEpd({Key? key, required this.moreId}) : super(key: key);
#override
_MoreEpdState createState() => _MoreEpdState();
}
class _MoreEpdState extends State<MoreEpd> {
double pageWidth = 0;
double pageHeigh = 0;
dynamic pageNum = 0;
final String leftArrow = 'assets/icons/left-arrow.svg';
String getSearchResult = """
query homeview(\$moreId: ID!, \$page: Int! ){
homeview(HM_ID: \$moreId, page: \$page){
HM_ID
Priority
Details{
Ep_ID
Image
title
Pod_title
}
}
}
""";
#override
Widget build(BuildContext context) {
pageWidth = MediaQuery.of(context).size.width;
pageHeigh = MediaQuery.of(context).size.height;
return Container(
child: Query(
options: QueryOptions(
document: gql(getSearchResult),
variables: {'moreId': widget.moreId, 'page': pageNum},
),
builder: (
QueryResult result, {
Refetch? refetch,
FetchMore? fetchMore,
}) {
return handleResult(result);
},
),
);
}
Widget handleResult(QueryResult result) {
var data = result.data!['homeview']['Details'] ?? [];
return Container(
child: ListView.builder(
padding: EdgeInsets.only(top: 15),
shrinkWrap: true,
itemCount: data.length ,
itemBuilder: (context, index) {
return InkWell(
onTap: () {},
child: Padding(
padding: EdgeInsets.only(
top: pageWidth * 0.0,
right: pageWidth * 0.08,
left: pageWidth * 0.08,
bottom: pageWidth * 0.0),
child: Container(
child: Stack(
children: [
Column(
children: [
Padding(
padding:
EdgeInsets.only(bottom: pageWidth * 0.060),
child: Row(
children: [
Padding(
padding:
EdgeInsets.only(left: pageWidth * 0.01),
child: Container(
// alignment: Alignment.centerRight,
width: pageWidth * 0.128,
height: pageWidth * 0.128,
decoration: BoxDecoration(
image: DecorationImage(
fit: BoxFit.cover,
image: CachedNetworkImageProvider(
data[index]['Image'],
)),
borderRadius: BorderRadius.all(
Radius.circular(15)),
// color: Colors.redAccent,
border: Border.all(
color: MyColors.lightGrey,
width: 1,
)),
),
),
Expanded(
child: Row(
children: [
Column(
crossAxisAlignment:
CrossAxisAlignment.start,
children: [
Container(
width: pageWidth * 0.5,
alignment: Alignment.centerRight,
child: Text(
data[index]['title'],
textAlign: TextAlign.right,
overflow: TextOverflow.ellipsis,
maxLines: 1,
// softWrap: true,
style: TextStyle(
// fontWeight: FontWeight.bold,
fontSize: 14,
),
),
),
],
),
],
),
)
],
),
),
],
),
],
),
),
),
);
}));
}}
can anyone help me please how can I use infinite scrolling to load other pages in my query?
The easiest way is to use a ListView.builder without specifying the itemCount parameter.
Here is the simplest example:
import 'package:flutter/material.dart';
void main() => runApp(new MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return new MaterialApp(
home: new MyHomePage(),
);
}
}
class MyHomePage extends StatefulWidget {
#override
_MyHomePageState createState() => new _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
#override
Widget build(BuildContext context) {
return new Scaffold(
appBar: new AppBar(
title: new Text("Infinite List"),
),
body: ListView.builder(
itemBuilder: (context, index) {
return Text("$index");
},
),
);
}
}
Later, you can enhance this by fetching real data. You could show a 'CircularProgressIndicator' in the last item of the list while waiting for the new data.
body: ListView.builder(
itemBuilder: (context, index) {
if (index < data.length) {
// Show your info
return Text("$index");
} else {
getMoreData();
return Center(child: CircularProgressIndicator());
}
},
itemCount: data.length + 1,
),
You can see that we trick the list by adding an index, and calling for more data when displaying that final index.
getMoreData() would include a call to setState() to force a rebuild and to take into account the new data.
Here I've created a flat_list widget which has a similar specification as in React Native. Hope the below works.
FlatList(
+ loading: loading.value,
+ onEndReached: () async {
+ loading.value = true;
+ await Future.delayed(const Duration(seconds: 2));
+ if (context.mounted) {
+ items.value += getMoreData();
+ loading.value = false;
+ }
+ },
data: items.value,
buildItem: (item, index) {
var person = items.value[index];
return ListItemView(person: person);
},
),

Move an item from one list to another with animation in Flutter

I have two vertical lists, one on the left side and the other one on the right, let's call them "Selected List" and "Unselected List".
I want the items in Unselected List to Animate from left side to the right side of the screen and add to Selected List.
the other items should fill the empty space in Unselected List and items in Selected List should free up the space for new item.
Here's the Ui
My Code:
class AddToFave extends StatefulWidget {
const AddToFave({Key? key}) : super(key: key);
#override
_AddToFaveState createState() => _AddToFaveState();
}
class _AddToFaveState extends State<AddToFave> {
List<String> unselected = [ '1','2','3','4','5','6','7','8','9','10'];
List<String> selected = [];
#override
Widget build(BuildContext context) {
return Scaffold(
body: SafeArea(
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Container(
width: MediaQuery.of(context).size.width / 5,
height: MediaQuery.of(context).size.height,
child: ListView.builder(
itemCount: selected.length,
itemBuilder: (context, index) {
return InkWell(
onTap: () {
unselected.add(selected[index]);
selected.removeAt(index);
setState(() {});
},
child: Container(
width: MediaQuery.of(context).size.width / 5,
height: MediaQuery.of(context).size.width / 5,
decoration: BoxDecoration(
color: Colors.black,
borderRadius: BorderRadius.circular(
MediaQuery.of(context).size.width / 5)),
child: Center(
child: Text(
selected[index],
style: TextStyle(color: Colors.white),
)),
),
);
}),
),
Container(
width: MediaQuery.of(context).size.width / 5,
height: MediaQuery.of(context).size.height,
child: ListView.builder(
itemCount: unselected.length,
itemBuilder: (context, index) {
return InkWell(
onTap: () {
selected.add(unselected[index]);
unselected.removeAt(index);
setState(() {});
},
child: Container(
width: MediaQuery.of(context).size.width / 5,
height: MediaQuery.of(context).size.width / 5,
decoration: BoxDecoration(
color: Colors.black,
borderRadius: BorderRadius.circular(
MediaQuery.of(context).size.width / 5)),
child: Center(
child: Text(
unselected[index],
style: TextStyle(color: Colors.white),
)),
),
);
}),
),
],
),
),
);
}
}
Thank you in advance.
This task can be broken into 2 parts.
First, use an AnimatedList instead of a regular ListView, so that when an item is removed, you can control its "exit animation" and shrink its size, thus making other items slowly move upwards to fill in its spot.
Secondly, while the item is being removed from the first list, make an OverlayEntry and animate its position, to create an illusion of the item flying. Once the flying is finished, we can remove the overlay and insert the item in the actual destination list.
Full source code for you to use, as a starting point:
import 'package:flutter/material.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
home: TwoAnimatedListDemo(),
);
}
}
class TwoAnimatedListDemo extends StatefulWidget {
const TwoAnimatedListDemo({Key? key}) : super(key: key);
#override
_TwoAnimatedListDemoState createState() => _TwoAnimatedListDemoState();
}
class _TwoAnimatedListDemoState extends State<TwoAnimatedListDemo> {
final List<String> _unselected = ['A', 'B', 'C', 'D', 'E', 'F', 'G'];
final List<String> _selected = [];
final _unselectedListKey = GlobalKey<AnimatedListState>();
final _selectedListKey = GlobalKey<AnimatedListState>();
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Two Animated List Demo'),
),
body: Row(
children: [
SizedBox(
width: 56,
child: AnimatedList(
key: _unselectedListKey,
initialItemCount: _unselected.length,
itemBuilder: (context, index, animation) {
return InkWell(
onTap: () => _moveItem(
fromIndex: index,
fromList: _unselected,
fromKey: _unselectedListKey,
toList: _selected,
toKey: _selectedListKey,
),
child: Item(text: _unselected[index]),
);
},
),
),
Spacer(),
SizedBox(
width: 56,
child: AnimatedList(
key: _selectedListKey,
initialItemCount: _selected.length,
itemBuilder: (context, index, animation) {
return InkWell(
onTap: () => _moveItem(
fromIndex: index,
fromList: _selected,
fromKey: _selectedListKey,
toList: _unselected,
toKey: _unselectedListKey,
),
child: Item(text: _selected[index]),
);
},
),
),
],
),
);
}
int _flyingCount = 0;
_moveItem({
required int fromIndex,
required List fromList,
required GlobalKey<AnimatedListState> fromKey,
required List toList,
required GlobalKey<AnimatedListState> toKey,
Duration duration = const Duration(milliseconds: 300),
}) {
final globalKey = GlobalKey();
final item = fromList.removeAt(fromIndex);
fromKey.currentState!.removeItem(
fromIndex,
(context, animation) {
return SizeTransition(
sizeFactor: animation,
child: Opacity(
key: globalKey,
opacity: 0.0,
child: Item(text: item),
),
);
},
duration: duration,
);
_flyingCount++;
WidgetsBinding.instance!.addPostFrameCallback((timeStamp) async {
// Find the starting position of the moving item, which is exactly the
// gap its leaving behind, in the original list.
final box1 = globalKey.currentContext!.findRenderObject() as RenderBox;
final pos1 = box1.localToGlobal(Offset.zero);
// Find the destination position of the moving item, which is at the
// end of the destination list.
final box2 = toKey.currentContext!.findRenderObject() as RenderBox;
final box2height = box1.size.height * (toList.length + _flyingCount - 1);
final pos2 = box2.localToGlobal(Offset(0, box2height));
// Insert an overlay to "fly over" the item between two lists.
final entry = OverlayEntry(builder: (BuildContext context) {
return TweenAnimationBuilder(
tween: Tween<Offset>(begin: pos1, end: pos2),
duration: duration,
builder: (_, Offset value, child) {
return Positioned(
left: value.dx,
top: value.dy,
child: Item(text: item),
);
},
);
});
Overlay.of(context)!.insert(entry);
await Future.delayed(duration);
entry.remove();
toList.add(item);
toKey.currentState!.insertItem(toList.length - 1);
_flyingCount--;
});
}
}
class Item extends StatelessWidget {
final String text;
const Item({Key? key, required this.text}) : super(key: key);
#override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.all(4.0),
child: CircleAvatar(
child: Text(text),
radius: 24,
),
);
}
}

How to create a custom gridview with appropriate aspect ratio?

I am trying to create a custom image picker that looks something like this:
As you can see the images are capped at a set height however maintain their aspect ratio (i.e. vertical images are vertical and horizontal images are horizontal). I have created the entire custom image picker. However, I'm struggling with the aspect ratio part. I'd prefer not to use a library, however, if it's easier then please provide the adjusted code.
Could you please provide a solution with code?
FYI I'm using photo_manager to retrieve the images/videos.
This is what it all looks like right now:
Here is my code:
class MediaGrid extends StatefulWidget {
#override
_MediaGridState createState() => _MediaGridState();
}
class _MediaGridState extends State<MediaGrid> {
List<Widget> _mediaList = [];
int currentPage = 0;
int? lastPage;
#override
void initState() {
super.initState();
_fetchNewMedia();
}
_handleScrollEvent(ScrollNotification scroll) {
if (scroll.metrics.pixels / scroll.metrics.maxScrollExtent > 0.33) {
if (currentPage != lastPage) {
_fetchNewMedia();
}
}
}
_fetchNewMedia() async {
lastPage = currentPage;
var result = await PhotoManager.requestPermission();
if (result) {
// success
//load the album list
List<AssetPathEntity> albums =
await PhotoManager.getAssetPathList(onlyAll: true);
print(albums);
List<AssetEntity> media =
await albums[0].getAssetListPaged(currentPage, 60);
print(media);
List<Widget> temp = [];
for (var asset in media) {
temp.add(
FutureBuilder<dynamic>(
future: asset.thumbDataWithSize(300, 300),
builder: (BuildContext context, snapshot) {
if (snapshot.connectionState == ConnectionState.done)
return Stack(
children: <Widget>[
Expanded(
child: Image.memory(snapshot.data, fit: BoxFit.cover),
),
if (asset.type == AssetType.video)
Align(
alignment: Alignment.bottomRight,
child: Padding(
padding: EdgeInsets.only(right: 5, bottom: 5),
child: Icon(
Icons.videocam_rounded,
color: Colors.white,
),
),
),
],
);
return Container();
},
),
);
}
setState(() {
_mediaList.addAll(temp);
currentPage++;
});
} else {
// fail
/// if result is fail, you can call `PhotoManager.openSetting();` to open android/ios applicaton's setting to get permission
}
}
#override
Widget build(BuildContext context) {
return NotificationListener<ScrollNotification>(
onNotification: (ScrollNotification scroll) {
return _handleScrollEvent(scroll);
},
child: GridView.builder(
physics: NeverScrollableScrollPhysics(),
itemCount: _mediaList.length,
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 2,
mainAxisSpacing: 5.0,
crossAxisSpacing: 5.0,
),
itemBuilder: (BuildContext context, int index) {
return _mediaList[index];
}),
);
}
}
This idea will only work if you wish to give each image a single specified height. And using fit: BoxFit.cover to fill up the remaining space.
Now you must find a way to get the width of each image, in my code its of Network Images
From here use the width as the flex value.
class HomePage extends StatelessWidget {
const HomePage({Key? key}) : super(key: key);
Future<Size> _calculateImageDimension(String url) {
Completer<Size> completer = Completer();
Image image = Image.network(url);
image.image.resolve(ImageConfiguration()).addListener(
ImageStreamListener(
(ImageInfo image, bool synchronousCall) {
var myImage = image.image;
Size size = Size(myImage.width.toDouble(), myImage.height.toDouble());
completer.complete(size);
},
),
);
return completer.future;
}
#override
Widget build(BuildContext context) {
//for odd no. of images you might have to add more conditions to your widget
final _netWorkimages = [
'https://images.pexels.com/photos/7179053/pexels-photo-7179053.jpeg?cs=srgb&dl=pexels-olya-prutskova-7179053.jpg&fm=jpg',
'https://images.pexels.com/photos/7527509/pexels-photo-7527509.jpeg?cs=srgb&dl=pexels-john-lee-7527509.jpg&fm=jpg',
'https://images.pexels.com/photos/8018591/pexels-photo-8018591.jpeg?cs=srgb&dl=pexels-inna-stellinna-8018591.jpg&fm=jpg',
'https://images.pexels.com/photos/3244513/pexels-photo-3244513.jpeg?cs=srgb&dl=pexels-andy-vu-3244513.jpg&fm=jpg',
'https://images.pexels.com/photos/694587/pexels-photo-694587.jpeg?cs=srgb&dl=pexels-samuel-silitonga-694587.jpg&fm=jpg',
'https://images.pexels.com/photos/5121986/pexels-photo-5121986.jpeg?cs=srgb&dl=pexels-marcelo-chagas-5121986.jpg&fm=jpg',
'https://images.pexels.com/photos/4519234/pexels-photo-4519234.jpeg?cs=srgb&dl=pexels-dinielle-de-veyra-4519234.jpg&fm=jpg',
'https://images.pexels.com/photos/2286385/pexels-photo-2286385.jpeg?auto=compress&cs=tinysrgb&dpr=2&h=750&w=1260',
'https://images.pexels.com/photos/35629/bing-cherries-ripe-red-fruit.jpg?cs=srgb&dl=pexels-pixabay-35629.jpg&fm=jpg',
'https://images.pexels.com/photos/4033324/pexels-photo-4033324.jpeg?cs=srgb&dl=pexels-karolina-grabowska-4033324.jpg&fm=jpg'
];
List<Future<Size>> _niSizes = [];
_netWorkimages.forEach((url) {
_niSizes.add(_calculateImageDimension(url));
});
return FutureBuilder<List<Size>>(
future: Future.wait(_niSizes),
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.waiting)
return Center(
child: CircularProgressIndicator(),
);
else
return ListView.builder(
itemCount: _netWorkimages.length - 1,
itemBuilder: (context, i) {
return i.isEven
? Container(
height: 120,
child: Row(
children: [
Flexible(
flex: snapshot.data![i].width.toInt(),
child: Padding(
padding: const EdgeInsets.all(4.0),
child: Image(
image: NetworkImage(_netWorkimages[i]),
fit: BoxFit.cover,
height: double.infinity,
width: double.infinity,
),
)),
Flexible(
flex: snapshot.data![i + 1].width.toInt(),
child: Padding(
padding: const EdgeInsets.all(4.0),
child: Image(
image: NetworkImage(_netWorkimages[i + 1]),
fit: BoxFit.cover,
height: double.infinity,
width: double.infinity,
),
)),
],
),
)
: SizedBox.shrink();
});
},
);
}
}
I hope this is thing you are looking for, replace container with image.
import 'package:flutter/material.dart';
import 'package:flutter_staggered_grid_view/flutter_staggered_grid_view.dart';
import 'dart:math' as math;
class TestScreen extends StatefulWidget {
TestScreen({Key? key}) : super(key: key);
#override
_TestScreenState createState() => _TestScreenState();
}
class _TestScreenState extends State<TestScreen> {
final _items = List.generate(
100,
(index) => ClipRRect(
borderRadius: BorderRadius.circular(
8,
),
child: Container(
height: 124,
color: Color(
(math.Random().nextDouble() * 0xFFFFFF).toInt(),
).withOpacity(1.0),
),
));
#override
Widget build(BuildContext context) {
return Container(
child: StaggeredGridView.countBuilder(
mainAxisSpacing: 2,
crossAxisSpacing: 2,
crossAxisCount: 6,
itemCount: 100,
itemBuilder: (context, index) {
return _items[index];
},
staggeredTileBuilder: (index) {
if (index % 6 == 0 || index % 6 == 3) {
return StaggeredTile.count(2, 1);
} else if (index % 6 == 1 || index % 6 == 2) {
return StaggeredTile.count(4, 1);
} else
return StaggeredTile.count(3, 1);
},
),
);
}
}
You can use https://pub.dev/packages/flutter_staggered_grid_view
This plugin has options to modify the aspect ratio

Draw outside listview bounds in Flutter

I want to transform my item that it is bigger than the listview itself. (intention for focused navigation)
My List:
Container(
height: 100,
child: ListView.builder(
itemBuilder: (context, index) => HomeItem(title: '$index'),
scrollDirection: Axis.horizontal,
),
),
My Item:
class HomeItem extends StatelessWidget {
final String title;
final bool expand;
const HomeItem({
#required this.title,
this.expand = false,
});
#override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.symmetric(horizontal: ThemeDimens.padding8),
child: Transform.scale(
scale: expand ? 1.5 : 1,
child: AnimatedContainer(
width: 50,
height: 100,
color: expand ? ThemeColors.accent : ThemeColors.primary,
duration: ThemeDurations.shortAnimationDuration(),
child: Center(
child: Text(title),
),
),
),
);
}
}
Current behaviour
Expected behaviour
If you try to use OverflowBox or Transform, content of an item will still clip and won't be drawn outside of its bounding box. But it's possible to use Overlay to draw an element on top of list and position it on a specific list item, though it's a bit complicated.
class _MyHomePageState extends State<MyHomePage> with WidgetsBindingObserver {
final elements = List.generate(12, (i) => i);
int selectedIndex;
OverlayEntry overlayEntry;
List<LayerLink> layerLinks;
#override
void initState() {
super.initState();
// Creating a layer link for each list cell
layerLinks = List.generate(elements.length, (i) => LayerLink());
}
void createOverlayEntry(int i, BuildContext context) {
// Removing an overlay entry, if there was one
overlayEntry?.remove();
final renderBox = context.findRenderObject() as RenderBox;
final size = renderBox.size;
final offset = renderBox.localToGlobal(Offset.zero);
// Creating a new overlay entry linked to specific list element
overlayEntry = OverlayEntry(
builder: (context) => Positioned(
left: 0,
top: 0,
child: CompositedTransformFollower(
link: layerLinks[i],
showWhenUnlinked: false,
offset: Offset(-20, 0),
child: Material(
color: Colors.yellow,
child: InkWell(
onTap: () {
setState(() {
selectedIndex = null;
});
overlayEntry?.remove();
overlayEntry = null;
},
child: Container(
alignment: Alignment.center,
width: 70,
height: elementHeight,
child: Text('$i')
),
)
),
)
)
);
// Inserting an entry
Overlay.of(context).insert(overlayEntry);
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Container(
height: elementHeight,
child: ListView.separated(
scrollDirection: Axis.horizontal,
itemCount: elements.length,
itemBuilder: (c, i) {
return CompositedTransformTarget(
link: layerLinks[i],
child: Material(
color: Colors.red,
child: InkWell(
onTap: () {
setState(() {
selectedIndex = i;
});
createOverlayEntry(i, context);
},
child: Container(
alignment: Alignment.center,
width: 30,
child: Text('${elements[i]}'),
),
),
),
);
},
separatorBuilder: (c, i) {
return Container(width: 10, height: 10);
},
),
),
);
}
}