Flutter Grid View Reorder & Drag Drop onto Another Item and Merge - flutter

I have been trying to add drag/drop support to my app, currently what I have come with is using this library:
reorderable_grid_view
I used this example code:
code link
The reason I used this library is that it's smooth enough of animations when dragging. But what I want to do is to drag one item to another so that I can merge the one to another object when I drop. (It's like in Android/iOS home screen where you can drag apps to folders or drag into another that it creates a folder)
I have searched all the site but couldn't come across with such thing, only drag/drop libraries are available. Can anyone help me on this?
Thanks in advance.

class MyHomePage extends StatelessWidget {
MyHomePage({super.key});
final ValueNotifier<List<ValueNotifier<List<Widget>>>> items = ValueNotifier([
ValueNotifier([Text("A")]),
ValueNotifier([Text("B")]),
ValueNotifier([Text("C")]),
ValueNotifier([Text("D")]),
]);
#override
Widget build(BuildContext context) {
return Scaffold(
body: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Expanded(
child: Padding(
padding: const EdgeInsets.symmetric(vertical: 20.0),
child: ValueListenableBuilder(
valueListenable: items,
builder: (BuildContext context, List<ValueNotifier<List<Widget>>> folders, Widget? child) {
return GridView.builder(
physics: const NeverScrollableScrollPhysics(),
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(crossAxisCount: 3),
itemCount: folders.length,
itemBuilder: (context, index) {
ValueNotifier<List<Widget>> item = folders[index];
return LongPressDraggable(
delay: const Duration(milliseconds: 500),
feedback: SizedBox(width: MediaQuery.of(context).size.width / 4, height: MediaQuery.of(context).size.width / 4, child: FittedBox(child: Icon(Icons.folder))),
data: index,
childWhenDragging: const SizedBox(),
child: DragTarget(
onAccept: (data) {
List<Widget> alreadyHaved = item.value;
alreadyHaved.addAll(folders[data as int].value);
item.value = alreadyHaved;
items.value.removeAt(data);
items.notifyListeners();
},
builder: (context, candidateData, rejectedData) {
return ValueListenableBuilder(
valueListenable: item,
builder: (BuildContext context, List<Widget> boxValues, Widget? child) {
return Stack(children: [
const Positioned.fill(
child: FittedBox(
child: Icon(
Icons.folder,
color: Colors.amber,
))),
Positioned.fill(
child: LayoutBuilder(
builder: (p0, p1) => SizedBox(
height: p1.maxHeight * .7,
width: p1.maxWidth * .7,
child: Center(
child: Wrap(
children: boxValues,
),
))),
)
]);
},
);
},
),
);
});
},
),
),
),
],
),
);
}
}

Related

Updating list in memory doesn't change UI in Flutter

I am currently modifying my app to support large devices. In this app I want to have a list of categories at the left and when I tap the list tile I want to show the list of the items which the category holds on the left. By the approach I am using the list updates in memory but doesnt change the UI. This is my current approach ->
class _OrderCategoryScreenState extends State<OrderCategoryScreen> {
List<FoodItem> filteredLists = [];
#override
Widget build(BuildContext context) {
return Scaffold(
body: SafeArea(
child: Row(
children: [
ValueListenableBuilder<Box<FoodCategory>>(
valueListenable: Boxes.getFoodCategories().listenable(),
builder: (context, box, child) {
final categories = box.values.toList().cast<FoodCategory>();
return categories.length == 0
? Container(
color: Color(0xFFFCF9F9),
height: double.infinity,
width: MediaQuery.of(context).size.height * 0.30,
child: Center(
child: Text("No Categories"),
))
: Container(
height: double.infinity,
width: MediaQuery.of(context).size.height * 0.35,
child: Drawer(
backgroundColor: Color(0xFFFCF9F9),
elevation: 0,
child: ListView(
children: <Widget>[
DrawerHeader(
child: ListView(
children: [
Container(
height: MediaQuery.of(context).size.height,
child: ValueListenableBuilder<
Box<FoodCategory>>(
builder: (context, value, child) {
final foodCategories = box.values
.toList()
.cast<FoodCategory>();
return ListView.builder(
itemCount: foodCategories.length,
itemBuilder: (context, index) {
return ListTile(
tileColor: Colors.green,
onTap: () {
filteredLists = Boxes
.getFoodItems()
.values
.where((element) =>
element.categoryName ==
foodCategories[index]
.name)
.toList();
print(filteredLists.length);
print("object");
},
title: Text(
foodCategories[index].name),
);
},
);
},
valueListenable: Boxes.getFoodCategories()
.listenable(),
),
)
],
),
),
],
),
));
},
),
Container(
width: MediaQuery.of(context).size.width * 0.5,
height: MediaQuery.of(context).size.height * 0.5,
color: Colors.amberAccent,
child: filteredLists.isEmpty
? Center(
child: Text("Choose A Category"),
)
: ListView.builder(itemCount: filteredLists.length,
itemBuilder: (context, index) {
return ListTile(
title: Text(filteredLists[index].itemName),
);
},
),
),
],
)
),
);
}
}
When I hot reload using this approach it works on the UI as well.
Please Help.
Try calling setState to update the UI.
onTap: () {
filteredLists = Boxes
.getFoodItems()
.values
.where((element) =>
element.categoryName ==
foodCategories[index]
.name)
.toList();
print(filteredLists.length);
print("object");
setState((){}); //this
},

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,
),
),
);
}

multi type list View in Flutter

How I can display list View with multi-type for example(item 1: text, item 2: image with Text ...)
using flutter?
Here is the code:
I need to make the ListView show onlyText in item1, imageWithText for item2 and so on, How I can do that?
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(),
body: Column(
children: [
SizedBox(height: 5),
ListView.separated(
shrinkWrap: true,
itemBuilder: (context, index) => onlyText(),
separatorBuilder: (context, index) => SizedBox(height: 10),
itemCount: 100,
),
],
),
);
}
}
Widget imageWithText() => Container(
child: Card(
child: Row(
children: [
Text(
'sara ahmad',
style: TextStyle(fontSize: 16),
),
SizedBox(width: 10),
Image.network(
'https://th.bing.com/th/id/R.e3a5da5209f4c39f1899456c94371a6f?rik=mz9sVBWxRJKGgA&riu=http%3a%2f%2fmedia1.santabanta.com%2ffull1%2fAnimals%2fHorses%2fhorses-62a.jpg&ehk=o%2fS9l8DSJtUbl%2bYcrwLMJy6W4MfUby7bTUHRwJu7a%2bU%3d&risl=&pid=ImgRaw&r=0',
width: 100,
height: 100,
),
],
),
),
);
Widget onlyText() => Container(
child: Card(
child: Row(
children: [
Text(
'sara ahmad',
style: TextStyle(fontSize: 16),
),
SizedBox(width: 10),
Text('Nour'),
],
),
),
);
In the itemBuilder you can check if the item is only text or image with text with a ternary operator ?:, i.e. condition ? expr1 : expr2, like so:
itemBuilder: (context, index) => index == 0 ? onlyText() : imageWithText(),
Or, if you have a list of more than 2 items it could be something like this (assuming the items have a property bool isOnlyText):
itemBuilder: (context, index) => _chats[index].isOnlyText
? onlyText()
: imageWithText(),
Below is the result of the 1st snippet above:
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(),
body: Column(
children: [
SizedBox(height: 5),
Expanded(
child: ListView.separated(
shrinkWrap: true,
itemBuilder: (context, index) =>
index == 0 ? onlyText() : imageWithText(),
separatorBuilder: (context, index) => SizedBox(height: 10),
itemCount: 100,
),
),
],
),
);
}
///crete an empty widget list
List<Widget> item_list=[];
///create a function to add data to list and call this function in the initstate
add_items_to_list()async{
item_list.add(Text(('data')));
item_list.add(Image.asset('name'));
///add as much as you want
}
///use widget as below
Widget items()=>ListView(
children: item_list,
);
if you want to show static list, you can do like this
itemBuilder: (context, index) => index.isEven
? onlyText()
: imageWithText(),
or you have dynamic data then follow below
let's assume you have list of Model class like this
class Model{
String text,
String img,
}
var list = <Model>[]; //your data will be here
and to check if is there only image, you need condition like below,
so in your ListView you can check like this
itemBuilder: (context, index) => list[index].img == null
? onlyText()
: imageWithText(),

Flutter List view builder doesn't shrink when Keyboard appears

I'm creating a chat feature in flutter but noticed this behavior on IOS that doesnt shrink the list so you can see the last sent message. How can I have the listview builder shrink to show the last message when the keyboard appears?
Note: This issue doesn't happen on Android
Scaffold(
resizeToAvoidBottomInset: true,
body: Stack(
children: <Widget>[
StreamBuilder(
stream: _chats,
builder: (context, snapshot) {
if (!snapshot.hasData) return Container();
return snapshot.hasData
? GestureDetector(
onPanDown: (_) {
FocusScope.of(context).requestFocus(FocusNode());
},
child: ListView.builder(
shrinkWrap: true,
controller: _scrollController,
padding: EdgeInsets.only(top: 10, bottom: 100),
itemCount: snapshot.data.docs.length,
itemBuilder: (context, index) {
return MessageWidget(
tripId: widget.docId,
uid: snapshot.data.docs[index].data()["uid"],
messageId: snapshot.data.docs[index].id,
message: snapshot.data.docs[index].data()["message"],
sender: snapshot.data.docs[index].data()["senderName"],
sentByMe: widget.uid ==
snapshot.data.docs[index].data()["uid"],
mediaFileUrl:
snapshot.data.docs[index].data()["mediaFileUrl"],
);
}),
)
: Container();
},
);
]
)
)
I think you can try the 'reverse' property from the ListView.builder.
Tell me if this example didn't fit your needs, can you share us your code ? (I didn't see why you use a Stack and what could be the issue around that).
class _MyHomePageState extends State<MyHomePage> {
#override
Widget build(BuildContext context) {
return Scaffold(
body: Column(
children: <Widget>[
Expanded(
child: Stack(
children: <Widget>[
StreamBuilder<dynamic>(
builder: (context, dynamic snapshot) {
return GestureDetector(
onPanDown: (_) {
FocusScope.of(context).unfocus();
},
child: ListView.builder(
reverse: true,
shrinkWrap: true,
itemCount: 100,
padding: const EdgeInsets.only(top: 10, bottom: 10),
itemBuilder: (context, index) {
return ListTile(title: Text(index.toString()));
},
),
);
},
),
],
),
),
Container(
padding: const EdgeInsets.all(8),
color: Colors.black12,
child: const TextField(),
),
],
),
);
}
}

Flutter page jumps to top after setState({})

I display many images in a Staggered Gridview in a Flutter application.
Everytime I call setState({}), for example after deleting an item, the page jumps to top. How could I remove this behavior?
This is my code:
final _scaffoldKey = new GlobalKey<ScaffoldState>();
.. outside the build function. And then...
return loadingScreen == true
? LoadingScreen()
: Scaffold(
key: _scaffoldKey,
body: CustomScrollView(
slivers: <Widget>[
_AppBar(
theme: theme,
index: index,
albumImagePath: albumImagePath,
albumID: albumID,
albumValue: albumValue,
addPictureToGallery: _addPictureToGallery,
),
SliverToBoxAdapter(
child: Column(
children: <Widget>[
InfoBar(
albumPicturesSum: albumPicturesSum,
getBilderString: _getBilderString,
theme: theme,
getVideoProgress: _getVideoProgress,
progress: progress,
),
albumID == 99999999
? // Demo Projekt
DemoImageGrid(
demoImageList: demoImageList,
getDemoImagesJson: _getDemoImagesJson,
)
: UserImageGrid(
picturesData: picturesData,
albumID: albumID,
showPictureActions: _showPictureActions)
],
),
)
],
),
);
}
The UserImageGrid looks like the following:
class UserImageGrid extends StatelessWidget {
final Pictures picturesData;
final int albumID;
final Function showPictureActions;
final _key = new UniqueKey();
UserImageGrid(
{#required this.picturesData,
#required this.albumID,
#required this.showPictureActions});
#override
Widget build(BuildContext context) {
return FutureBuilder(
key: _key,
future: picturesData.getPicturesFromAlbum(albumID),
builder: (BuildContext context, AsyncSnapshot snapshot) {
// Normale Projekte
if (snapshot.hasData && snapshot.data.length == 0) {
return Center(
child: Column(
children: <Widget>[
Lottie.asset('assets/lottie/drone.json',
width: 250,
options: LottieOptions(enableMergePaths: false)),
],
),
);
}
if (!snapshot.hasData ||
snapshot.connectionState == ConnectionState.waiting) {
return Center(
child: CircularProgressIndicator(),
);
} else {
return Container(
child: StaggeredGridView.countBuilder(
physics: NeverScrollableScrollPhysics(),
shrinkWrap: true,
padding: EdgeInsets.all(0),
crossAxisCount: 6,
itemCount: snapshot.data.length,
itemBuilder: (BuildContext context, int index) =>
GestureDetector(
onLongPress: () {
showPictureActions(snapshot.data[index]);
},
onTap: () async {
await showDialog(
context: context,
builder: (_) {
return Dialog(
child: Stack(
children: [
Container(
margin: const EdgeInsets.symmetric(
vertical: 10.0,
horizontal: 10.0,
),
height: 500.0,
child: ClipRect(
child: PhotoView(
maxScale:
PhotoViewComputedScale.covered * 2.0,
minScale:
PhotoViewComputedScale.contained *
0.8,
initialScale:
PhotoViewComputedScale.covered,
imageProvider: FileImage(
File(snapshot.data[index].path))),
),
),
Positioned(
bottom: 20,
left: 20,
child: Padding(
padding: const EdgeInsets.all(8.0),
child: Text(
DateFormat(tr("date_format")).format(
snapshot.data[index].timestamp
.toDateTime()),
style: TextStyle(color: Colors.white),
),
),
)
],
));
});
},
child: Container(
child: Image.file(
File(snapshot.data[index].thumbPath),
fit: BoxFit.cover,
)),
),
staggeredTileBuilder: (int index) =>
new StaggeredTile.count(2, index.isEven ? 2 : 2),
mainAxisSpacing: 5.0,
crossAxisSpacing: 5.0,
),
);
}
});
}
}
What could be the issue?
I found a solution for this issue. The problem was not the setState({}). It was the return Widget of the FutureBuilder.
I changed
if (!snapshot.hasData || snapshot.connectionState == ConnectionState.waiting) {
return Center(child: CircularProgressIndicator());
}
to:
if (!snapshot.hasData || snapshot.connectionState == ConnectionState.waiting) {
return Container(
height: MediaQuery.of(context).size.height,
);
}
I donĀ“t exactly know why, but with this change the page is not jumping to top anymore on setState({})