Flutter Drag&Drop

Osman Yılmaz
5 min readDec 18, 2023

Bu yazımda size aşağıdaki örnek üzerinden Flutterda drag&drop deyimini anlatacağım.

Haydi başlayalım…

Merhaba, Drag&Drop yani sürükle bırak diye bilinen, web ve mobilde bir çok uygulamada karşımıza çıkan bir kullanıcı deneyiminim Flutter’da nasıl yapıldığını kısaca kısa örneklerle açıklayıp yukarıdaki örnek uygulamayı sizlere anlatmaya çalışacağım.

Sürükle ve bırak (Drag and Drop), bir nesneyi parmağınızla veya fare ile tutulup , onu farklı bir konuma sürükleyip bıraktığınız ana kadar gelişen süreçtir. Süreç olarak tanımlamamın nedeni, bu işlem sırasında bir çok durum ortaya çıkabilir. Örneğin, kullanıcı sürükleme işleminden vazgeçebilir, hedef konum hariç farklı bir noktaya bırakma işlemi yapılabilir vs. Bu süreci daha iyi anlamak için kod üzerinden tüm durumları anlatmaya çalışacağım.

Peki Flutterda bu işlem nasıl olur?

Flutterda bir çok yardımcı widget olduğu gibi bu işlemi yapmak için Draggable widgeti kullanabiliriz. Bu widget sayesinde tüm süreci kolaylıkla yönetebilirsiniz.

Draggable widgetinin tüm detaylarına ve ek özelliklerine aşağıdaki linkten ulaşabilirsiniz.

Draggable class — widgets library — Dart API

Draggable widgetinin kullanımı

1- ilk olarak Draggable widgetini kullanarak sürükleme adımlarını öğrenelim

Yukarıdaki örnekte görüldüğü üzere bir nesneyi sürükle bırak yöntemiyle hareket ettiriliyor. Aşağıdaki kod bloğununda Draggable widgeti’nin standart özellikleri kullanılmıştır.


import 'package:flutter/material.dart';

class SampleDragging extends StatefulWidget {
const SampleDragging({Key? key}) : super(key: key);

@override
_SampleDraggingState createState() => _SampleDraggingState();
}

class _SampleDraggingState extends State<SampleDragging> {
@override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: Draggable<String>(
data: 'ball',
child: SizedBox(
height: 120.0,
width: 120.0,
child: Center(
child: Image.asset('assets/images/ball.png'),
),
),
feedback: SizedBox(
height: 120.0,
width: 120.0,
child: Center(
child: Image.asset('assets/images/ball.png'),
),
),
/*childWhenDragging: const SizedBox(),

childWhenDragging: SizedBox(
height: 120.0,
width: 120.0,
child: Center(
child: Image.asset('assets/images/ball_gray.png'),
),
),*/
),
));
}
}

Kodu incedikten sonra önemli kısımları adım adım inceleyelim;

  • Draggable: Scaffold'in body’sine Center widgetinin child’i olarak Draggable widgetini ekliyoruz.
  • data: Draggable widgetinin içerisinde yer alan, hareket etmesini istediğimiz widgeti temsil eder. Bu widgetler DragTarget tarafından kullanılır.

Örnekte ben bir Sizebox içerisine bir Image ekledim.

  • feedback: Bu kısım data widgetinin, yani sürüklemeye başladığınız ana widget’in referansını temsil eder. Hareket ettirmeye başladığınız anda boyutunu, rengini veya diğer özelliklerini değiştirerek kullanıcılara daha iyi bir deneyim sunabilirsiniz. Sürüklenen nesnenin mevcut konumundan ayrıldığını daha iyi gösterebilmek için referans objenin alfa değerini değiştirebilirsiniz veya farklı bir görselle değiştirebilirsiniz.

👀 2 durumda bunu inceleyelim;

childWhenDragging: SizedBox(
height: 120.0,
width: 120.0,
child: Center(
child: Image.asset('assets/images/ball_gray.png'),
),

childWhenDragging widgetimizi hareket ettirdiğimizde nereden hareket ettiğini kullanıcıya göstermek için kullanabiliriz.

childWhenDragging: const SizedBox()

Arkada kalan Objemizi gizlemek için Direk bir boş SizeBox ekleyebilirsiniz.

childWhenDragging Direk boş bir size box eklerseniz göründü bu şekilde olacaktır.

axis: Axis.vertical , Axis.horizontal : Draggable widget’in axis özelliğini değiştirerek hareket yönünü yatay veya dikey yapabilirsiniz.

Sürükle- Bırak sürecinde Draggable widgeti bize bir durumu hazır olarak sunar. Bunlar;

onDragStarted,onDragEnd,onDraggableCanceled,onDragCompleted

  • onDragStarted: Sürükleme işlemi başladığı anı dinleyebiliriz.
  • onDragEnd: Sürükleme işlemi bittiği anda ekranın neresine bırakıldı, başarılı mı başarısız mı gibi durumları dinleyebiliriz
  • onDraggableCanceled: Sürükleme işlemi başarısız olduğunda veya kullanıcı parmağını veya fareyi kaldırdığı anı dinlememizi sağlar
  • onDragCompleted: Sürükleme işleminin ilgili noktaya başarılı bir şekilde bırakıldığını dinlemizi sağlar

Flutterda sürüklenen bir widget’in hedef noktaya ulaştırmayı yönetmemizi sağlayan DragTarget isiminde widget kullanılmaktadır.

özetle;

DragTarget widgeti Draggable widgetlerini kabul eden widgettir.

Aşağıdaki örnekte görüldüğü üzere

Kırmızı, Mavi ve Sarı balıklar Draggable widgetlerimiz. aşağıda gördüğümüz akvaryum iseDragTarget widgetimizi


import 'package:flutter/material.dart';
class SampleDragAndDrop extends StatefulWidget {
const SampleDragAndDrop({Key? key}) : super(key: key);
@override
_SampleDragAndDropState createState() => _SampleDragAndDropState();
}
GlobalKey<ScaffoldState> scaffoldKey = GlobalKey();
class _SampleDragAndDropState extends State<SampleDragAndDrop> {
bool _isDropped = false;
String yellow = 'yellow';
String blue = 'blue';
String red = 'red';
var addedItems = <String>[];
@override
Widget build(BuildContext context) {
return SafeArea(
child: Scaffold(
appBar: AppBar(
actions: [
IconButton(
icon: const Icon(
Icons.reset_tv,
color: Colors.white,
),
onPressed: () {
clearAll();
},
)
],
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Visibility(
visible: !addedItems.contains(blue),
child: Draggable<String>(
// Data is the value this Draggable stores.
data: 'blue',
child: SizedBox(
height: 120.0,
width: 120.0,
child: Center(
child: Image.asset('assets/images/blue.png'),
),
),
feedback: SizedBox(
height: 120.0,
width: 120.0,
child: Center(
child: Image.asset('assets/images/blue.png'),
),
),
childWhenDragging: const SizedBox(
width: 120,
height: 120,
),
),
),
Visibility(
visible: !addedItems.contains(red),
child: Draggable<String>(
// Data is the value this Draggable stores.
data: 'red',
child: SizedBox(
height: 120.0,
width: 120.0,
child: Center(
child: Image.asset('assets/images/red.png'),
),
),
feedback: SizedBox(
height: 120.0,
width: 120.0,
child: Center(
child: Image.asset('assets/images/red.png'),
),
),
childWhenDragging: const SizedBox(
width: 120,
height: 120,
),
),
),
Visibility(
visible: !addedItems.contains(yellow),
child: Draggable<String>(
// Data is the value this Draggable stores.
data: 'yellow',
child: SizedBox(
height: 120.0,
width: 120.0,
child: Center(
child: Image.asset('assets/images/yellow.png'),
),
),
feedback: SizedBox(
height: 120.0,
width: 120.0,
child: Center(
child: Image.asset('assets/images/yellow.png'),
),
),
childWhenDragging: const SizedBox(
width: 120,
height: 120,
),
),
),
],
),
SizedBox(
height: MediaQuery.of(context).size.height * 0.15,
),
DragTarget<String>(
builder: (
BuildContext context,
List<dynamic> accepted,
List<dynamic> rejected,
) {
return SizedBox(
height: 320,
width: 320,
child: Center(
child: getBow(addedItems),
),
);
},
onWillAccept: (data) {
if (data == 'blue' || data == 'yellow' || data == 'red') {
return true;
} else {
return false;
}
},
onAccept: (data) {
setState(() {
if (!addedItems.contains(data)) {
addedItems.add(data);
}
_isDropped = true;
});
},
onLeave: (data) {},
),
],
),
)),
);
}

void clearAll() {
setState(() {
addedItems.clear();
});
}

Image getBow(List<String> selectedData) {if (selectedData.contains(yellow) &&
selectedData.contains(blue) &&
selectedData.contains(red)) {
return Image.asset('assets/images/full_bowl.png');
} else if (selectedData.contains(yellow) && selectedData.contains(blue)) {
return Image.asset('assets/images/without_red.png');
} else if (selectedData.contains(yellow) && selectedData.contains(red)) {
return Image.asset('assets/images/without_blue.png');
} else if (selectedData.contains(red) && selectedData.contains(blue)) {
return Image.asset('assets/images/without_yellow.png');
} else if (selectedData.contains(red)) {
return Image.asset('assets/images/red_bowl.png');
} else if (selectedData.contains(yellow)) {
return Image.asset('assets/images/yellow_bowl.png');
} else if (selectedData.contains(blue)) {
return Image.asset('assets/images/blue_bowl.png');
} else {
return Image.asset('assets/images/empty_bowl.png');
}
}
}
  • onWillAccept is called whenever the item is dropped over the DragTarget. We can use this method to retrieve the data carried by the Draggable widget and decide whether to accept the item or not. In the code above, we accept the tomato image if it carries that string as red
  • onAccept is a callback that we should receive once the item is accepted by the DragTarget. We are showing the success message and updating the _isDropped variable. _isDropped is used to change the image of the bowl to show the tomato inside the bowl image

Kısaca Flutterda sürükle bırakma işlemini 2 basit örnekle anlatmaya çalıştım. Siz de bu örneklerden yararlanacak puzzle gibi, sepete bir şeyler eklemek gibi güzel oyunlar bile yapabilirsiniz.

Projenin tamamına git hesabımdan erişebilirsiniz.

https://github.com/imosmanyilmaz/FlutterDraggable

--

--