PanResponder
PanResponder API ile React Native'de touch(dokunma) eventleri, PanResponder'a ait lifecycle methodlar ile düzenleyip, drag and drop işlemlerini gerçekleştirebiliriz.
Bir elemente drag-drop özelliği eklemek istediğimizde, yukarıdaki kod parçacığı sabit bir yaklaşım direkt bunu kullanabiliriz. Hatta kullanığınız editörde bir snippet kısayolu ayarlamanız size pratiklik sağlayacaktır. ( Bu arada react-native'in gesture sisteminde sadece bu methodlar yok. Ben buraya en çok kullanılanlarını ve aşağıdaki örnekte kullanacaklarımızı yazdım)
Şimdi bu API'ı bir örnekle açıklamak istiyorum. Bizim bir görevimiz olsun. 3x3 kare board ve altında 3 kırmızı, 3 mavi, 3 sarı topumuz olsun. Aşağıdaki topları sürükleyip, board'a bırakalım.
Sürükleyip bıraktığımız kutu boş ise , topun rengini alsın.
Sürükleyip bıraktığımız kutu dolu ise, top tekrar yerine gitsin
Bütün board kareleri dolduğunda yan yana olan tüm renkler aynı ise Kazandınız, değil ise Kaybettiniz diye bir Alert çıkarsın.
Kodun tamamı için https://github.com/ysfzrn/react-native-panresponder-demo
Görevimizi anladıysak şimdi yapılacakları adım adım sıralayalım.
Board çizelim. Board'ın koordinatlarını ve özelliklerini state'te tutalım.
Bir PanResponder Element Yaratım.
Hover effect ekleyelim
Sürüklediğimiz componenti drop ettikten sonraki kontrolleri ekleyelim.
1.BOARD
Bizden istenen 3x3 bir board. Bu board'daki her bir karede sayfada kendine ait koordinatlarını, içinin boş olup olmadığını, üstünde bir componentin sürüklenip sürüklenmediğini bilecek. Bunun için bir başlangıç objesi set edelim.
Uygulamamız açılır açılmaz board'ı çizmek için componentDidMount methodunda 3x3 lük board bilgisini tutacak bir array oluşturalım. Bu array'ın her bir elemanı az önceki yarattığımız, initObj objesinden oluşsun. Oluşturduğumuz geçici array'ı state'e set edelim. Bunu aşağıda digHole isimli fonksiyonda topladım. Bunu componentDidMount methodunda çağıralım.
Güzel artık elimde görselleştirebileceğim bir data'm var. Ama ondan önce bizim componentlerimiz arası width, height, konum ilişkisi oldukça fazla olacak. Bu yüzden bir tane componentlerin bu değerlerini bir araya toplamak adına bir kendimize ait customStyle objesini bir javascript dosyasında tutalım ve kullanacağımız yerlerde import edelim. Bunun için src klasörümüzde bir tane util klasörü yaratalım içine de SharedStyle.js isimli dosya ekleyelim. (initObj objesindeki color değerini de bu style'a göre değiştirelim)
Tekrar app.js' e dönelim. Bir adet header componenti ve Board'umuz için bir boardContainer yaratalım.boardContainer style'a dikkat edin. SharedStyle'da verdiğimiz width ve borderWidth değerlerin 3 katının toplamını verdik ki boardContainer'ımız tam anlamıyla karelerimiz için kapsayıcı olsun.
Önce bir tane Hole.js isimli bir component yapıp holes state'inde tuttuğumuz datayı görselleştirmeye başlayalım.
boardContainer tam bir kapsayıcı olmuşa benzemiyor. Bunun için boardContainer'a, flexWrap i eklememiz gerekiyor.
Sıra geldi karelerimizin sayfadaki pozisyonlarını bulmaya. Bunun için react-native'de birkaç yöntem var. Ama en pratiği her element yaratıldığında tetiklenen onLayout methodu.
Bir şeyler ters gidiyor. onLayout methodunu çağırdık koordinatları aldık ama bu koordinatlar hole componentin kendi üstündeki View'e göre boardContainer'a göre değerleri. Bize sayfaya göre olanı lazım. Bunun için bizim boardContainer'ın koordinatlarına da ihtiyacımız var. app.js'e dönüp boardContainer'ın koordinatlarını alıp, hole componentin her birine bunları gönderelim. onLayout merhodu component mount edildikten sonra çağrıldığı için bir render koşulu ekleyelim. mainx ve mainy belli değil ise ekranda bir loader gösterelim.
Şimdi hole.js componentimizde koordinat bulma işlemine mainx ve mainy props'larını da katalım. Aşağıdaki koordinatlar gayet doğru gözüküyor.
Yukarıda karelere ait array'i yaratırken bir başlangıç objesi set etmiştik ve onda pozisyon değereri {x:0, y:0} şeklindeydi. Şimdi o state'deki array'i değiştirelim.Bunun için hole.js içinde handleLayout methodun da üstte kullanılmak üzere bir function props fırlatalım.
2. PanResponder Element Yaratalım
Toplamda bizim 9 tane topumuz olacak. Bunların kendine özgü renkleri olacak. Ve sürüklenip bırakılan top bir daha sürüklenmeyecek. Bu verilere dayanarak kendimize yine state'te tutacağımız bir array yaratalım.
Bu array'i görselleştireceğimiz bir ball.js isimli bir component yaratalım sonra da ona drag&drop özellikleri ekleyelim.
Başlangıçta componentimiz touch eventlere tepki versin ve touch eventlerle hareket edebilmesi için onStartShouldSetPanResponder
ve onMoveShouldSetPanResponder
fonksiyonlarının true döndürmesini sağladık. Bu fonksiyonlar tetiklendikten sonra iki tane daha fonksiyon tetiklenecektir. Bunlardan birincisi başlangıç değerlerini set edebileceğimiz, onPanResponderGrant
; parmağımız componenti sürüklemeye başlayınca ekranda dokunduğumuz yerlerin coordinatlarını almamızı sağlayan onPanResponderMove
.
Önemli: Biz PanResponer API'ı kullanırken elementin değerlerini filan almıyoruz. Biz ekranda dokunduğumuz yerlerin koordinat değerlerini alıp, elementin style'ını değiştiriyoruz.
Şimdi başlangıç değerlerimizi set edelim. Burada Animated API kullanacağız. bunun için ilk önce constructor'da bir Animated state yaratalım.
Şimdi onResponderGrant'da bu state'imize değer verelim.
Component'i tutup elimizle sürükleyince tetiklenecek olan onPanResponderMove
için bir fonksiyon atayalım.
this.state.pan değiştikçe değişecek olan style yapıp, View'den oluşan ball componentini Animated.View'e çevirelim.
getTranslateTranform, tranform:[{translateX: value }, {translateY:value} ] işini yapan Animated value ile kullanılabilen hazır bir fonksiyon
Şimdi örnek tek bir Ball componentini app.js' e import edip neler oluyor bir bakalım.
Yine bir şeyler ters gidiyor. İlk defa sürüklerken bir sorun olmuyor fakat ikinci kez topa dokunduğumuzda topun pozisyonu uzaklara kaçıveriyor. Bunu çözmek için 2 tane global değişken yaratacağız (this._animatedValueX, this.animatedValueY) ve bu iki değişkeni dinleyen listenerlarımız olacak. Topu son bıraktığımız yerin değerini kendilerinde tutup, ikinci kez dokunduğumuzda tekrar tetiklenen, onPanResponderGrant methodunda bunlarda tuttuğumuz değerleri this.state.pan state'ine atacağız. O da dolayısıyla elementin style'ını update edecek.
Listenerları componentWillUnmount'da remove etmeyi unutmayın
Şimdi tüm toplarımızı map edelim
3-Hover Effect Ekleme
PanResponder sisteminde onPanResponderMove
ile ekranın neresinde sürüklenme işlemi oluyor anlayabiliyorduk. Hover effect yaparken de bunu kullanacağız. onPanResponderMove
aktif iken gerekli değerleri alıp, üst component'e function props fırlatıp, effect oluşma işlemini de üst component'e halledeceğiz.
nativeEvent ve gestureState kavramları için; Panresponder sisteminde herbir method bu iki değişkeni bize otomatik verir. Bu iki değişken (evt.nativeEvent ve gestureState) aslında bir obje ve biz burada dokunduğumuz yerin nativeEvent içerisinde sayfaya göre konumunu pageX ve pageY değişkenlerinden alıyoruz. Daha fazla ayrıntı için resmi dökümanda şuraya bakabilirsiniz
Şimdi bir kaç util fonksiyonu yazalım. Bunlardan biri ben bir karenin üstünde miyim değil miyim onu söyleyecek olan isDropZone fonksiyonu diğeri üstündeyken holes state'ini değiştirecek olan hoverDropZone fonksiyonu. İşin logic tarafına girmiyorum. Çünkü gayet koddan okunabilir diye düşünüyorum.
Burada ayrıntı belki de algoritmayı kurarken yaptığım bir hata, sayfadaki header boyutuna da ihtiyacım var. İşin açıkçası karelerin koordinatlarını hesaplarken onu da içine katıp bu parametreye burada ihtiyaç duymayabilirdik. Ama sorun değil.
Şimdi app.js'de handlePositionChanging methodunda hoverDropZone'u çağıralım. Ordan dönen holes array'i ile state'i güncelleyelim.
Şimdi this.state.holes state'i sorumlu olduğu kareye kendi üzerinde neler döndüğünü anlatabilir. Kareye ona göre style ekleyelim.
4-Drop - Release - Bırakma İşlemi
Tekrar PanResponder componentimize dönüp yeni bir method ekliyoruz, onPanResponderRelease:this.handleRelease
methodu. Bu method ne yapacak ? Topun bırakıldığı koordinatları ve hangi topun bırakıldığını üst componente haber verecek.
Üst component'te onDrop props'unu karşılayalım
Burada bir util fonksiyon daha yazalım, selectDropZone diye. Buda bizim balls ve holes array'imiz değiştirsin. Ve bizde o değiştirdiği array'ler ile state'lerimizi güncelleyelim.
Yukarıdaki algoritma çok basit. for döngüsü ile ilk önce hangi karenin üstünde olduğuna karar veriyor. Sonra o karenin renk ve ilgili değerlerini değiştiriyor sonra da, hangi topun seçildiğine karar verip, o topun bir daha seçilmemesi adına selected özelliğini false'a çekiyor.
Şimdi bu util fonksiyonlarımızı handleDrop methodunda çağıralım.
hole ve ball componentlerimizin style'larının yeni state'e göre şekil alması lazım. Onları ekleyelim. İlk önce hole componentine bakalım. Hovering ise hoverColor, filled ise toptan aldığı renk, hiçbiri değilse beyaz gözükecek. ( Ne dersiniz bu yöntemi biraz değiştirerek basit bir kalemle çizim uygulaması yapılabilir mi ? )
Şimdi top bırakıldığında boardContainer'da değilse yada dolu bir karenin üzerindeyse yerine geriye dönmesini isteyelim. İşin burası çok basit sadece release methoduna Animated fonksiyonu ekleyeceğiz.
Animated.spring'de diyoruz ki; top bırakıldığında this.state.pan başlangıç değerini {x:0 , y:0} değerine çek. Bunu da kendine özel animasyonunla yap.
Bu kadar, kodun tamamı için https://github.com/ysfzrn/react-native-panresponder-demo
Last updated