Android RecyclerView: Τι είναι, Μάθετε με απλά παραδείγματα

⚡ Έξυπνη Σύνοψη

Το RecyclerView είναι ένα προηγμένο Android Γραφικό στοιχείο που εμφανίζει αποτελεσματικά μεγάλα, κυλιόμενα σύνολα δεδομένων, ανακυκλώνοντας έναν περιορισμένο αριθμό προβολών. Βασίζεται σε έναν προσαρμογέα, έναν διαχειριστή διάταξης και έναν κάτοχο προβολής για τη σύνδεση συλλογών δεδομένων που αλλάζουν κατά τον χρόνο εκτέλεσης.

  • 📱 Αυτό που είναι: Το RecyclerView είναι ένας ευέλικτος διάδοχος των ListView και GridView για την εμφάνιση μεγάλων συλλογών δεδομένων.
  • 🧱 Προβολές και Ομάδες Προβολών: Android σχεδιάζει τη διεπαφή χρησιμοποιώντας τις κλάσεις View και ViewGroup, με το LayoutParams να ελέγχει το μέγεθος και τη θέση.
  • 🔄 Adapter: Το μοτίβο προσαρμογέα μετατρέπει έναν πίνακα δεδομένων σε ViewHolders που μπορεί να αποδεχτεί το RecyclerView.
  • 📐 LayoutManager: Ο διαχειριστής διάταξης ταξινομεί τα στοιχεία γραμμικά ή σε πλέγμα και σηματοδοτεί πότε μπορούν να ανακυκλωθούν οι προβολές.
  • ♻️ Ανακύκλωση: Οι προβολές εκτός οθόνης επαναχρησιμοποιούνται αντί να αναδημιουργούνται, γεγονός που εξοικονομεί μνήμη και διατηρεί την κύλιση ομαλή.
  • Βήματα κατασκευής: Προσθέστε το Gradle εξάρτηση, δημιουργήστε μια διάταξη στοιχείου, ένα ViewHolder, έναν προσαρμογέα και ένα LayoutManager.

Android RecyclerView

Τι είναι το RecyclerView Android?

The RecyclerView είναι μια πιο ευέλικτη και προηγμένη έκδοση των GridView και ListView. Είναι ένα κοντέινερ για την εμφάνιση μεγάλων συνόλων δεδομένων που μπορούν να μετακινηθούν αποτελεσματικά διατηρώντας έναν περιορισμένο αριθμό προβολών. Μπορείτε να χρησιμοποιήσετε το γραφικό στοιχείο RecyclerView όταν έχετε συλλογές δεδομένων των οποίων τα στοιχεία αλλάζουν κατά τον χρόνο εκτέλεσης ανάλογα με ένα συμβάν δικτύου ή μια ενέργεια χρήστη.

Προβολές

The Android Η πλατφόρμα χρησιμοποιεί τις κλάσεις View και ViewGroup για να σχεδιάσει στοιχεία στην οθόνη. Αυτές οι κλάσεις είναι abs.tract και επεκτείνονται σε διαφορετικές υλοποιήσεις για να ταιριάζουν σε μια περίπτωση χρήσης. Το TextView, για παράδειγμα, έχει τον απλό σκοπό της εμφάνισης περιεχομένου κειμένου στην οθόνη. Το EditText επεκτείνεται από την ίδια κλάση View και προσθέτει περισσότερες λειτουργίες για να επιτρέψει στον χρήστη να εισάγει δεδομένα.

Είναι δυνατό να δημιουργήσουμε τις δικές μας προσαρμοσμένες προβολές για να επιτύχουμε μεγαλύτερη ευελιξία κατά την ανάπτυξη.ping διεπαφές χρήστη. Η κλάση View παρέχει μεθόδους που μπορούμε να παρακάμψουμε για να σχεδιάσουμε στην οθόνη και έναν τρόπο για να περάσουμε παραμέτρους όπως πλάτος, ύψος και τα δικά μας προσαρμοσμένα χαρακτηριστικά που θα θέλαμε να προσθέσουμε στην προβολή μας για να την κάνουμε να συμπεριφέρεται όπως επιθυμούμε.

ViewGroups

Η κλάση ViewGroup είναι ένα είδος View, αλλά, σε αντίθεση με την απλή κλάση View της οποίας η ευθύνη είναι απλώς η εμφάνιση, η ViewGroup μας δίνει τη δυνατότητα να τοποθετήσουμε πολλαπλές προβολές σε μία προβολή, την οποία μπορούμε να αναφέρουμε ως σύνολο. Σε αυτήν την περίπτωση, η προβολή που δημιουργείται στο ανώτατο επίπεδο και στην οποία προσθέτουμε άλλες απλές προβολές ονομάζεται «γονική» και οι προβολές που προστίθενται μέσα είναι «παιδιά».

Μπορούμε να φανταστούμε μια View ως έναν Πίνακα και μια ViewGroup ως έναν Πίνακα Πινάκων. Δεδομένου ότι ένας Πίνακας Πινάκων είναι ο ίδιος ένας Πίνακας, μπορούμε να δούμε πώς μια ViewGroup μπορεί να αντιμετωπιστεί ως μια View.

var arr1 = [1,2,3] //imagine a simple View as an Array
//we can imagine this as a NumberTextView which doesn't really exist
//but we could imagine there's one that makes it easy to use numbers
var arr2 = ["a","b","c"] // We can imagine this as another simple view

var nestedArr = [arr1,arr2] //in our analogy, we can now group views
//together and the structure that would hold that would be what we call the ViewGroup

Η Ομάδα Προβολών (ViewGroup) μας επιτρέπει επίσης να ορίσουμε τον τρόπο οργάνωσης των παιδιών (children) μέσα στην προβολή, για παράδειγμα, αν είναι τοποθετημένα κάθετα ή οριζόντια. Μπορούμε να έχουμε διαφορετικούς κανόνες για την αλληλεπίδραση μέσα στην προβολή. Για παράδειγμα, οι Προβολές Κειμένου (TextViews) που ακολουθούν η μία την άλλη θα πρέπει να έχουν απόσταση 12dp, ενώ μια Προβολή Εικόνας (ImageView) που ακολουθείται από μια Προβολή Κειμένου (TextView) θα πρέπει να έχει απόσταση 5dp.

Αυτό θα συνέβαινε αν ήμασταν αναπτυγμένοιping το δικό μας ViewGroup από την αρχή. Για να γίνουν αυτές οι διαμορφώσεις ευκολότερες, Android παρέχει μια κλάση που ονομάζεται LayoutParams, την οποία μπορούμε να χρησιμοποιήσουμε για να εισάγουμε αυτές τις διαμορφώσεις.

The Android τεκμηρίωση παρέχει ορισμένες προεπιλεγμένες παραμέτρους που θα εφαρμόζαμε κατά τη διαμόρφωση της δικής μας ViewGroup. Ορισμένες κοινές παράμετροι αφορούν το πλάτος, το ύψος και το περιθώριο. Από προεπιλογή, αυτές οι διαμορφώσεις έχουν τη δομή android:layout_height για το ύψος και android:layout_width για το πλάτος. Από αυτή την άποψη, όταν δημιουργείτε την ViewGroup σας, μπορείτε να δημιουργήσετε περαιτέρω LayoutParams ειδικά για τον τρόπο που θέλετε να συμπεριφέρεται η ViewGroup σας.

Android συνοδεύεται από προεπιλεγμένες Προβολές και Ομάδες Προβολών που μπορούμε να χρησιμοποιήσουμε για να κάνουμε πολλές από τις συνηθισμένες εργασίες που χρειαζόμαστε. Ένα παράδειγμα που έχουμε αναφέρει είναι μια Προβολή Κειμένου. Αυτή είναι μια απλή προβολή που συνοδεύεται από διαμορφώσιμες πτυχές όπως ύψος, πλάτος και μέγεθος κειμένου. Έχουμε επίσης μια Προβολή Εικόνας για την εμφάνιση εικόνων και EditText, όπως αναφέραμε, μεταξύ πολλών άλλων. Android έχει επίσης προσαρμοσμένες ομάδες προβολής στις οποίες μπορούμε να προσθέσουμε τις προβολές μας και να έχουμε την αναμενόμενη συμπεριφορά.

Γραμμική διάταξη

Το LinearLayout μας επιτρέπει να προσθέσουμε στοιχεία Προβολής σε αυτό. Το LinearLayout έχει ένα χαρακτηριστικό orientation που υπαγορεύει τον τρόπο με τον οποίο θα διαταχθεί στην οθόνη. Έχει επίσης LinearLayout.LayoutParams που υπαγορεύουν τους κανόνες για τις προβολές στο εσωτερικό. Για παράδειγμα, το χαρακτηριστικό android:center_horizontal θα κεντράρει τις προβολές κατά μήκος του οριζόντιου άξονα, ενώ το android:center_vertical θα κεντράρει το περιεχόμενο της προβολής κατά μήκος του κάθετου άξονα.

Ακολουθούν ορισμένες εικόνες που θα σας βοηθήσουν να κατανοήσετε το κεντράρισμα. Θεωρούμε ότι πρόκειται για μια απλή προβολή κειμένου (TextView) μέσα σε έναν χώρο 200px επί 200px. Τα χαρακτηριστικά κεντραρίσματος θα το έκαναν να συμπεριφέρεται ως εξής.

android:center_horizontal

Οριζόντια κεντραρισμένο περιεχόμενο

Οριζόντια κεντραρισμένο περιεχόμενο

android:center_vertical

Κάθετα κεντραρισμένο περιεχόμενο

Κάθετα κεντραρισμένο περιεχόμενο

android:center

Περιεχόμενο στο κέντρο

Επικεντρωμένο περιεχόμενο

Βασικά στοιχεία του RecyclerView

Βασικά στοιχεία του RecyclerView

Βασικά στοιχεία του RecyclerView

Ακολουθούν τα σημαντικά στοιχεία του RecyclerView:

RecyclerView.Adapter

Βάση #1 – Μοτίβο προσαρμογέα

Ένας προσαρμογέας είναι μια συσκευή που μετατρέπει τα χαρακτηριστικά ενός συστήματος ή συσκευής σε εκείνα μιας κατά τα άλλα ασύμβατης συσκευής ή συστήματος. Ορισμένοι προσαρμογείς τροποποιούν τα χαρακτηριστικά σήματος ή ισχύος, ενώ άλλοι απλώς προσαρμόζουν τη φυσική μορφή ενός συνδέσμου σε έναν άλλο.

Ένα απλό παράδειγμα από την πραγματική ζωή που εξηγεί έναν προσαρμογέα είναι όταν χρειάζεται να συνδέσουμε συσκευές μεταξύ τους, αλλά έχουν θύρες σύνδεσης που δεν ταιριάζουν μεταξύ τους. Αυτό θα μπορούσε να συμβεί όταν επισκέπτεστε μια διαφορετική χώρα που χρησιμοποιεί διαφορετικά είδη πριζών. Εάν έχετε μαζί σας τον φορτιστή του τηλεφώνου ή του φορητού υπολογιστή σας, θα ήταν αδύνατο να τον συνδέσετε στις πρίζες. Ωστόσο, δεν θα τα παρατήσετε, αλλά απλώς θα αγοράσετε έναν προσαρμογέα που μπαίνει ανάμεσα στην πρίζα και τον φορτιστή σας και επιτρέπει τη φόρτιση.

Αυτό συμβαίνει στον προγραμματισμό όταν θέλουμε να συνδέσουμε δύο δομές δεδομένων για να εκπληρώσουμε μια εργασία, αλλά οι προεπιλεγμένες θύρες τους δεν έχουν τρόπο επικοινωνίας μεταξύ τους.

Θα χρησιμοποιήσουμε το απλό παράδειγμα μιας συσκευής και ενός φορτιστή. Θα έχουμε δύο παρουσίες φορτιστών, έναν αμερικανικό και έναν βρετανικό.

class AmericanCharger() {
    var chargingPower = 10
}
class BritishCharger(){
    var chargingPower = 5
}

Στη συνέχεια, θα δημιουργήσουμε δύο συσκευές.

class AmericanDevice()
class BritishDevice()

Για παράδειγμα, μπορούμε στη συνέχεια να δημιουργήσουμε ορισμένες περιπτώσεις των συσκευών για αναπαραγωγή.

var myAmericanPhone = new AmericanDevice()
var myBritishPhone = new BritishDevice()

Στη συνέχεια, θα εισαγάγουμε την έννοια της φόρτισης και για τις δύο συσκευές προσθέτοντας μια μέθοδο στις συσκευές που ονομάζεται charge(). Η μέθοδος λαμβάνει τον αντίστοιχο φορτιστή και φορτίζει με βάση αυτόν.

sealed trait Device
class AmericanDevice : Device{
    fun charge(charger:AmericanCharger){
        //Do some American charging
    }
}
class BritishDevice: Device{
    fun charge(charger:BritishCharger){
        //Do some British charging
    }
}

Σε αυτήν την περίπτωση, με βάση την αναλογία μας, για τον έναν ή τον άλλο λόγο θα χρειαζόμασταν να χρησιμοποιήσουμε ένα BritishCharger όταν χρησιμοποιούμε μια AmericanDevice ή αντίστροφα.

Στον κόσμο του προγραμματισμού, αυτό συμβαίνει συνήθως όταν αναμιγνύουμε βιβλιοθήκες μεταξύ τους που προσφέρουν την ίδια λειτουργικότητα (στο περιβάλλον μας, η κοινή μας λειτουργικότητα φορτίζει). Θα πρέπει να βρούμε έναν τρόπο να το επιτρέψουμε.

Αν ακολουθήσουμε την αναλογία, θα χρειαστεί να πάμε σε ένα κατάστημα ηλεκτρονικών ειδών και να αγοράσουμε έναν προσαρμογέα που μας επιτρέπει να φορτίζουμε αμερικανικές συσκευές με βάση τους British Chargers. Από την άποψη του προγραμματισμού, θα είμαστε ο κατασκευαστής του προσαρμογέα.

Θα δημιουργήσουμε έναν προσαρμογέα που ταιριάζει ακριβώς με το μοτίβο που θα χρειαζόμασταν για τη δημιουργία του άλλου. Θα τον υλοποιήσουμε ως κλάση ως εξής. Δεν χρειάζεται απαραίτητα να είναι κλάση και θα μπορούσε να είναι μια συνάρτηση που επισημαίνει τι κάνει γενικά το μοτίβο του προσαρμογέα. Χρησιμοποιούμε μια κλάση καθώς ταιριάζει με τις περισσότερες χρήσεις στο Android.

class AmericanToBritishChargerAdapter(theAmericanCharger:AmericanCharger){
    fun returnNewCharger(): BritishCharger{
            //convert the American charger to a BritishCharger
            //we would change the American charging functionality
            //to British charging functionality to make sure the
            //adapter doesn't destroy the device. The adapter could
            //, for example, control the power output by dividing by 2
            //our adapter could encompass this functionality in here

           var chargingPower:Int = theAmericanCharger.chargingPower / 2
           var newBritishCharger = new BritishCharger()
           newBritishCharger.chargingPower = theAmericanCharger.chargingPower/2

        return newBritishCharger
    }
}

Στον κόσμο του προγραμματισμού, η διαφορά στις πρίζες είναι ανάλογη με τη διαφορά στις μεθόδους που χρησιμοποιούνται για τη φόρτιση. Οι φορτιστές που έχουν διαφορετικές μεθόδους θα καθιστούσαν αδύνατη τη χρήση των φορτιστών.

var myBritishDevice = new BritishDevice()
var americanChargerIFound = new AmericanCharger()

Η προσπάθεια κλήσης της μεθόδου charge() στο myBritishDevice με americanChargerIFound δεν θα λειτουργήσει, καθώς το AmericanDevice δέχεται μόνο ένα AmericanCharger. Επομένως, είναι αδύνατο να γίνει αυτό:

var myBritishDevice = new BritishDevice()
var americanChargerIFound = new AmericanCharger()
myBritishDevice.charge(americanChargerIFound)

Σε αυτό το σενάριο, ο προσαρμογέας που δημιουργήσαμε, ο AmericanToBritishChargerAdapter, μπορεί τώρα να φανεί χρήσιμος. Μπορούμε να χρησιμοποιήσουμε τη μέθοδο returnNewCharger() για να δημιουργήσουμε έναν νέο BritishCharger, τον οποίο μπορούμε να χρησιμοποιήσουμε για φόρτιση. Το μόνο που χρειάζεται να κάνουμε είναι να δημιουργήσουμε μια παρουσία του προσαρμογέα μας και να την τροφοδοτήσουμε με τον AmericanCharger που έχουμε, και αυτό θα δημιουργήσει έναν BritishCharger που μπορούμε να χρησιμοποιήσουμε.

var myBritishDevice = new BritishDevice()
var americanChargerIFound = new AmericanCharger()
//We create the adapter and feed it the americanCharger
var myAdapter =  AmericanToBritishChargerAdapter(americanChargerIFound)
//calling returnNewCharger from myAdapter would return a BritishCharger
var britishChargerFromAdapter = myAdapter.returnNewCharger()
//and once we have the britishCharger we can now use it
myBritishDevice.charge(britishChargerFromAdapter)

RecyclerView.LayoutManager

Όταν έχουμε να κάνουμε με ένα ViewGroup, έχουμε τοποθετημένες Προβολές μέσα σε αυτό. Ο LayoutManager έχει την εργασία να περιγράψει τον τρόπο με τον οποίο είναι τοποθετημένες οι Προβολές μέσα σε αυτό.

Για λόγους σύγκρισης, όταν εργαζόμαστε με μια ομάδα προβολής LinearLayout, η περίπτωση χρήσης που θέλουμε είναι η δυνατότητα τοποθέτησης των στοιχείων είτε κάθετα είτε οριζόντια. Αυτό υλοποιείται εύκολα προσθέτοντας ένα χαρακτηριστικό orientation, το οποίο μας λέει πώς θα τοποθετηθεί η LinearLayout στην οθόνη. Μπορούμε να το κάνουμε αυτό χρησιμοποιώντας το android:orientation=VERTICAL|HORIZONTAL αποδίδουν.

Έχουμε επίσης μια άλλη ομάδα προβολής που ονομάζεται GridLayout. Η χρήση της είναι όταν θέλουμε να τοποθετήσουμε προβολές σε μια ορθογώνια δομή πλέγματος. Αυτό θα μπορούσε να συμβαίνει για λόγους όπως η εύκολη κατανάλωση των δεδομένων που παρουσιάζουμε στον χρήστη της εφαρμογής. Σχεδιαστικά, το GridLayout επιτρέπει διαμορφώσεις που σας βοηθούν να επιτύχετε αυτόν τον στόχο ορίζοντας τις διαστάσεις του πλέγματος. Για παράδειγμα, μπορούμε να έχουμε ένα πλέγμα 4×4 ή ένα πλέγμα 3×2.

RecyclerView.ViewHolder

Ο ViewHolder είναι κοιλιακοίtracκλάση t την οποία επεκτείνουμε επίσης από το RecyclerView. Το ViewHolder μας παρέχει κοινές μεθόδους που μας βοηθούν να αναφέρουμε μια προβολή που έχουμε τοποθετήσει στο RecyclerView, ακόμα και αφού ο μηχανισμός ανακύκλωσης στο RecyclerView έχει αλλάξει διάφορες αναφορές για τις οποίες δεν γνωρίζουμε.

Μεγάλοι Λίστες

Τα RecyclerViews χρησιμοποιούνται όταν θέλουμε να παρουσιάσουμε ένα πολύ μεγάλο σύνολο Views στον χρήστη, χωρίς να εξαντλούμε το RAM στη συσκευή μας για κάθε εμφάνιση της προβολής που δημιουργήθηκε.

Αν πάρουμε την περίπτωση μιας λίστας επαφών, θα έχουμε μια γενική ιδέα για το πώς θα φαίνεται μια επαφή στη λίστα. Αυτό που θα κάνουμε στη συνέχεια είναι να δημιουργήσουμε μια διάταξη προτύπου, η οποία στην πραγματικότητα είναι μια Προβολή, με υποδοχές όπου θα συμπληρωθούν διάφορα δεδομένα από τη λίστα επαφών μας. Ακολουθεί ψευδοκώδικας που εξηγεί ολόκληρο τον σκοπό:

//OneContactView
<OneContact>
<TextView>{{PlaceHolderForName}}</TextView>
<TextView>{{PlaceHolderForAddress}}</TextView>
<ImageView>{{PlaceHolderForProfilePicture}}</ImageView>
<TextView>{{PlaceHolderForPhoneNumber}}</TextView>
</OneContact>

Στη συνέχεια, θα έχουμε μια Λίστα Επαφών αυτού του είδους:

<ContactList>
</ContactList>

Αν κάναμε hardcode στα περιεχόμενα, δεν θα είχαμε έναν προγραμματιστικό τρόπο προσθήκης νέου περιεχομένου στη λίστα χωρίς να ξαναγράψουμε την εφαρμογή. Ευτυχώς για εμάς, η προσθήκη μιας View σε μια ViewGroup υποστηρίζεται από ένα addView(view:View) μέθοδος. Ακόμα κι έτσι, δεν είναι ο τρόπος με τον οποίο το RecyclerView προσθέτει σε αυτό θυγατρικές προβολές.

Στην περίπτωσή μας, θα είχαμε μια μεγάλη λίστα επαφών. Για κάθε επαφή στη λίστα, θα χρειαζόταν να δημιουργήσουμε ένα OneContactView και να συμπληρώσουμε τα δεδομένα μέσα στην προβολή ώστε να ταιριάζουν με τα πεδία στην κλάση Contact. Στη συνέχεια, μόλις έχουμε την προβολή, θα πρέπει να την προσθέσουμε στο RecyclerView για να εμφανιστεί η λίστα.

data  class Contact(var name:String, var address:String, var pic:String, var phoneNumber:Int)

var contact1 = Contact("Guru","Guru97", "SomePic1.jpg", 991)
var contact2 = Contact("Guru","Guru98", "SomePic2.jpg", 992)
var contact3 = Contact("Guru","Guru99", "SomePic3.jpg", 993)

var myContacts:ArrayList<Contact> = arrayListOf<Contact>(contact1,contact2,contact3)

Έχουμε μια σειρά από επαφές. Η OneContactView περιέχει υποδοχές για να λαμβάνει περιεχόμενα από την κλάση Contact και να τα εμφανίζει. Στην RecyclerView, πρέπει να προσθέσουμε Views σε αυτήν, ώστε να μας βοηθήσει με την ικανότητα ανακύκλωσης.

Το RecyclerView δεν μας επιτρέπει πραγματικά να προσθέσουμε μια προβολή, αλλά μας επιτρέπει να προσθέσουμε ένα ViewHolder. Έτσι, σε αυτό το σενάριο, έχουμε δύο κομμάτια που θέλουμε να συνδέσουμε αλλά δεν ταιριάζουν. Εδώ μπαίνει στο παιχνίδι ο προσαρμογέας μας. Το RecyclerView μας παρέχει έναν προσαρμογέα, όπως ακριβώς και ο δικός μας. AmericanToBritishChargerAdapter() από νωρίτερα αυτό μας επέτρεψε να μετατρέψουμε τον AmericanCharger μας, ο οποίος ήταν άχρηστος με τη BritishDevice μας, σε κάτι χρήσιμο, παρόμοιο με έναν προσαρμογέα ρεύματος στην πραγματική ζωή.

Σε αυτό το σενάριο, ο προσαρμογέας θα λάβει τον πίνακα επαφών μας και την προβολή μας και από εκεί θα δημιουργήσει ViewHolders που το RecyclerView είναι διατεθειμένο να αποδεχτεί.

Το RecyclerView παρέχει μια διεπαφή που μπορούμε να επεκτείνουμε για να δημιουργήσουμε τον προσαρμογέα μας μέσω της κλάσης RecyclerView.Adapter. Μέσα σε αυτόν τον προσαρμογέα υπάρχει ένας τρόπος δημιουργίας της κλάσης ViewHolder με την οποία θέλει να λειτουργήσει το RecyclerView. Έτσι, έχουμε την ίδια κατάσταση όπως πριν, αλλά με ένα επιπλέον πράγμα, τον προσαρμογέα.

Έχουμε μια σειρά από επαφές, μια προβολή για την εμφάνιση μίας επαφής (OneContactView) και ένα RecyclerView, το οποίο είναι μια λίστα με Προβολές που παρέχει υπηρεσίες ανακύκλωσης αλλά είναι πρόθυμη να αναλάβει μόνο ViewHolders. Σε αυτό το σενάριο, έχουμε τώρα μια κλάση RecyclerView.Adapter, η οποία έχει μια μέθοδο για τη δημιουργία ViewHolders μέσα.

fun createViewHolder(@NonNull parent: ViewGroup, viewType: Int): ViewHolder

Το RecyclerView.ViewHolder είναι ένα κοιλιακόtracΗ κλάση t δέχεται την View μας ως όρισμα και τη μετατρέπει σε ViewHolder. Χρησιμοποιεί το μοτίβο περιτύλιξης που χρησιμοποιείται για την επέκταση των δυνατοτήτων των κλάσεων.

Groundwork #2 – Μοτίβο περιτυλίγματος

Θα χρησιμοποιήσουμε ένα απλό παράδειγμα για να δείξουμε πώς μπορούμε να κάνουμε τα ζώα να μιλήσουν.

sealed trait Animal{
    fun sound():String
}

data class Cat(name:String):Animal{
    fun sound(){
        "Meow"
        }
}
data class Dog(name:String):Animal{
    fun sound(){
        "Woof"
        }
}

var cat1 = Cat("Tubby")
var dog1 = Dog("Scooby")
cat1.sound() //meow
dog1.sound() //woof

Στο παραπάνω παράδειγμα, έχουμε δύο ζώα. Αν, τυχαία, θέλαμε να προσθέσουμε μια μέθοδο για να τα κάνουμε να μιλήσουν, αλλά ο συγγραφέας της βιβλιοθήκης δεν ήταν διασκεδαστικός, θα μπορούσαμε να βρούμε έναν τρόπο. Αυτό που θα χρειαζόμασταν είναι ένα περιτύλιγμα για την κλάση Animal. Θα το κάναμε αυτό λαμβάνοντας το Animal ως κατασκευαστή για την κλάση μας.

class SpeechPoweredAnimalByWrapper(var myAnimal:Animal){

    fun sound(){
        myAnimal.sound()
    }

    fun speak(){
        println("Hello, my name is ${myAnimal.name}")
    }
}

Τώρα μπορούμε να περάσουμε μια παρουσία ζώου στο SpeechPoweredAnimalByWrapper. Η κλήση της μεθόδου sound() σε αυτήν θα καλούσε τη μέθοδο animal sound() που πέρασε. Έχουμε επίσης μια επιπλέον μέθοδο speak(), η οποία μετράει ως νέα λειτουργικότητα που προσθέτουμε στα ζώα που πέρασαν. Μπορούμε να τη χρησιμοποιήσουμε ως εξής:

var cat1 =  Cat("Garfield")
cat1.sound()//"meow"
cat1.speak()// doesn't work as it isn't implemented
var talkingCat = new SpeechPoweredAnimalByWrapper(cat1)
talkingCat.sound() //"meow" the sound method calls the one defined for cat1
talkingCat.speak() //"Hello, my name is Garfield"

Χρησιμοποιώντας αυτό το μοτίβο, μπορούμε να λάβουμε κλάσεις και να προσθέσουμε λειτουργικότητα. Το μόνο που χρειαζόμαστε είναι να περάσουμε μια παρουσία κλάσης και νέες μεθόδους που ορίζονται από το wrap μας.ping τάξη.

Στην παραπάνω περίπτωση, χρησιμοποιήσαμε μια συγκεκριμένη κλάση. Είναι επίσης δυνατό να εφαρμοστεί η ίδια σε μια ABStracκλάση t. Θα χρειαστεί να αλλάξουμε την κλάση SpeechPoweredAnimalByWrapper σε abs.tract, και τελειώσαμε. Θα αλλάξουμε το όνομα της κλάσης σε κάτι πιο σύντομο για να το κάνουμε πιο ευανάγνωστο.

abstract class SpeechPowered(var myAnimal:Animal){

    fun sound(){
        myAnimal.sound()
    }

    fun speak(){
        println("Hello, my name is ${myAnimal.name}")
    }
}

Είναι το ίδιο όπως πριν, αλλά θα σήμαινε κάτι άλλο. Σε μια κανονική κλάση, μπορούμε να έχουμε μια παρουσία μιας κλάσης με τον ίδιο τρόπο που δημιουργήσαμε τις cat1 και dog1. AbstracΟι κλάσεις t, ωστόσο, δεν προορίζονται για δημιουργία στιγμιότυπων, αλλά για την επέκταση άλλων κλάσεων. Πώς λοιπόν θα χρησιμοποιούσαμε το νέο SpeechPowered(var myAnimal:Animal) abs;tracκλάση t; Μπορούμε να τη χρησιμοποιήσουμε δημιουργώντας νέες κλάσεις που θα την επεκτείνουν και, με τη σειρά τους, θα αποκτήσουν τη λειτουργικότητά της.

Στο παράδειγμά μας, θα δημιουργήσουμε μια κλάση SpeechPoweredAnimal που επεκτείνει την κλάση.

class SpeechPoweredAnimal(var myAnimal:Animal):SpeechPowered(myAnimal)
var cat1 =  Cat("Tubby")
var speakingKitty = SpeechPoweredAnimal(cat1)
speakingKitty.speak() //"Hello, my name is Tubby"

Αυτό είναι το ίδιο μοτίβο που χρησιμοποιείται στο ViewHolder. Η κλάση RecyclerView.ViewHolder είναι ένα abs.tracκλάση t που προσθέτει λειτουργικότητα στην προβολή, όπως ακριβώς προσθέσαμε τη μέθοδο speak στα ζώα. Η πρόσθετη λειτουργικότητα είναι αυτή που την κάνει να λειτουργεί όταν χειρίζεστε το RecyclerView.

Έτσι θα δημιουργούσαμε ένα OneContactViewHolder από το OneContactView:

//The View argument we pass is converted to a ViewHolder which uses the View to give it more abilities and in turn work with the RecyclerView
class OneContactViewHolder(ourContactView: View) : RecyclerView.ViewHolder(ourContactView)

Το RecyclerView διαθέτει έναν προσαρμογέα που μας επιτρέπει να συνδέσουμε τον πίνακα επαφών μας στο ContactsView μέσω του RecyclerView.

Προσθήκη προβολής

Το ViewGroup δεν επανασχεδιάζει αυτόματα, αλλά ακολουθεί ένα συγκεκριμένο πρόγραμμα. Μπορεί η συσκευή σας να επανασχεδιάζει κάθε 10ms ή 100ms. Ή, αν επιλέξουμε έναν παράλογο αριθμό, ας πούμε 1 λεπτό, όταν προσθέτουμε μια προβολή σε μια ViewGroup, θα δείτε τις αλλαγές 1 λεπτό αργότερα, όταν το ViewGroup "ανανεωθεί".

RecyclerView.Recycler

Βασική εργασία #3 – Αποθήκευση στην προσωρινή μνήμη

Ένα από τα καλύτερα παραδείγματα για το πού κάνουμε τακτικά ανανεώσεις είναι στο πρόγραμμα περιήγησης. Ας φανταστούμε, για παράδειγμα, ότι ο ιστότοπος που επισκεπτόμαστε είναι στατικός και δεν στέλνει περιεχόμενο δυναμικά. Θα πρέπει να συνεχίσουμε να τον ανανεώνουμε για να δούμε τις αλλαγές.

Για αυτό το παράδειγμα, ας φανταστούμε ότι ο εν λόγω ιστότοπος είναι το Twitter. Θα είχαμε μια σειρά από στατικά tweets και ο μόνος τρόπος για να δούμε νέα tweets θα ήταν κάνοντας κλικ στο κουμπί ανανέωσης για να ανακτήσουμε ξανά το περιεχόμενο.

Η επαναβαφή ολόκληρης της οθόνης είναι προφανώς μια δαπανηρή διαδικασία. Φανταστείτε να είχαμε περιορισμένο εύρος ζώνης με τον πάροχο τηλεφωνίας μας και η λίστα tweet μας να είχε πολλές εικόνες και βίντεο. Θα ήταν δαπανηρό να ξανακατεβάζουμε όλο το περιεχόμενο της σελίδας σε κάθε ανανέωση.

Θα χρειαζόμασταν έναν τρόπο να αποθηκεύσουμε τα ήδη φορτωμένα tweets και να διασφαλίσουμε ότι το επόμενο αίτημά μας θα έχει τη δυνατότητα να πει ποια tweets έχει ήδη. Επομένως, δεν ξανακατεβάζει τα πάντα και λαμβάνει μόνο τα νέα tweets, και επίσης ελέγχει αν κάποιο tweet που είχε αποθηκευτεί τοπικά δεν υπάρχει πλέον, ώστε να μπορεί να το διαγράψει τοπικά. Αυτό που περιγράφουμε ονομάζεται προσωρινή αποθήκευση.

Οι πληροφορίες που στέλνουμε στον ιστότοπο σχετικά με το περιεχόμενο που έχουμε ονομάζονται μεταδεδομένα. Έτσι, στην πραγματικότητα, δεν λέμε απλώς «θέλουμε να φορτώσουμε τον ιστότοπό σας», λέμε «θέλουμε να φορτώσουμε τον ιστότοπό σας και ορίστε μέρος του περιεχομένου που έχουμε ήδη αποθηκεύσει από την τελευταία φορά που το φορτώσαμε. Χρησιμοποιήστε το για να μας στείλετε μόνο ό,τι δεν υπάρχει εκεί, ώστε να μην χρησιμοποιούμε πολύ εύρος ζώνης».

Κλήσεις διάταξης – Η λίστα Tweet πρέπει να είναι τρελή

Ένα παράδειγμα κλήσης διάταξης είναι το scrollToPosition. Αυτό είναι ένα συνηθισμένο παράδειγμα που υπάρχει σε εφαρμογές συνομιλίας. Εάν κάποιος σε ένα νήμα συνομιλίας απαντήσει σε μια φούσκα συνομιλίας από προηγούμενη φορά, ορισμένες εφαρμογές συνομιλίας περιλαμβάνουν την απάντηση και έναν σύνδεσμο προς τη φούσκα συνομιλίας, η οποία, κάνοντας κλικ, σας μεταφέρει εκεί που ήταν το αρχικό σας μήνυμα.

Στην περίπτωση που καλούμε αυτήν τη μέθοδο πριν προσθέσουμε ένα LayoutManager στο RecyclerView μας και πριν έχουμε ένα RecyclerView.Adapter, η συνάρτηση scrollToPosition(n:Int) απλώς αγνοείται.

Επικοινωνία μεταξύ RecyclerView Components

Προκαταρκτική εργασία #4 – Επανακλήσεις

Το RecyclerView, κατά την εκτέλεση της εργασίας του, έχει πολλά κινούμενα μέρη. Πρέπει να ασχοληθεί με το LayoutManager, το οποίο μας λέει πώς να οργανώσουμε τις Προβολές, είτε γραμμικά είτε σε πλέγμα. Πρέπει να ασχοληθεί με έναν προσαρμογέα που κάνει τη δουλειά της μετατροπής των στοιχείων μας (contactList) σε Προβολές (OneContactView) και στη συνέχεια σε ViewHolders (OneContactViewHolder) με τα οποία το RecyclerView είναι διατεθειμένο να συνεργαστεί χρησιμοποιώντας τις μεθόδους που παρέχει.

Η πρώτη ύλη για το RecyclerView είναι οι Προβολές μας, π.χ. OneContactView, και μια πηγή δεδομένων.

contactList:Array<Contact>

Χρησιμοποιήσαμε ένα απλό σενάριο ως σημείο εκκίνησης για να καταλάβουμε τι προσπαθεί να επιτύχει το RecyclerView. Μια βασική περίπτωση όπου έχουμε έναν στατικό πίνακα 1000 επαφών που θέλουμε να δείξουμε στον χρήστη είναι εύκολα κατανοητή. Ο μηχανισμός του RecyclerView αρχίζει πραγματικά να παίρνει ζωή όταν η λίστα δεν είναι πλέον στατική. Με μια δυναμική λίστα, πρέπει να σκεφτούμε τι συμβαίνει στην Προβολή στην οθόνη όταν προσθέτουμε ένα στοιχείο στη λίστα ή αφαιρούμε ένα στοιχείο από τη λίστα.

RecyclerView.LayoutManager

Εκτός από το να αποφασίζει πώς θα διατεθούν οι προβολές μας, είτε γραμμικά είτε σε πλέγμα, το LayoutManager κάνει πολλή δουλειά στο εσωτερικό του hel.ping Ο Ανακυκλωτής γνωρίζει πότε να κάνει την ανακύκλωση.

Είναι υπεύθυνο για τηνping track από τις Προβολές που είναι ορατές αυτήν τη στιγμή στην οθόνη και κοινοποίηση αυτών των πληροφοριών στον μηχανισμό ανακύκλωσης. Καθώς ένας χρήστης κάνει κύλιση προς τα κάτω, το LayoutManager είναι υπεύθυνο για την ενημέρωση του συστήματος ανακύκλωσης σχετικά με τις Προβολές που χάνουν την εστίαση στο επάνω μέρος, ώστε να μπορούν να επαναχρησιμοποιηθούν, αντί να παραμένουν εκεί και να καταναλώνουν μνήμη ή να καταστρέφονται μόνο και μόνο για να δημιουργηθούν νέες.

Αυτό σημαίνει ότι ο LayoutManager πρέπει να διατηρεί track για το πού βρίσκεται ο χρήστης καθώς κάνει κύλιση στη λίστα μας. Αυτό επιτυγχάνεται έχοντας μια λίστα θέσεων που βασίζονται σε ευρετήριο, δηλαδή το πρώτο στοιχείο ξεκινά από το 0 και αυξάνεται ώστε να ταιριάζει με τον αριθμό των στοιχείων στη λίστα μας.

Αν μπορούμε να δούμε 10 στοιχεία στη λίστα μας, ας πούμε, 100, στην αρχή ο LayoutManager γνωρίζει ότι έχει εστιάσει από την Προβολή-0 έως την Προβολή-9. Καθώς κάνουμε κύλιση, ο LayoutManager είναι σε θέση να υπολογίσει τις προβολές που χάνουν την εστίαση.

Το LayoutManager είναι σε θέση να αποδεσμεύσει αυτές τις προβολές στον μηχανισμό ανακύκλωσης, ώστε να μπορούν να επαναχρησιμοποιηθούν (νέα δεδομένα μπορούν να συνδεθούν με αυτές, π.χ. τα δεδομένα επικοινωνίας μιας προβολής μπορούν να αφαιρεθούν και τα νέα δεδομένα επικοινωνίας από το επόμενο τμήμα μπορούν να αντικαταστήσουν τα placeholder).

Αυτό θα ήταν μια καλή περίπτωση αν η λίστα που έχουμε είναι στατική, αλλά μία από τις πιο συνηθισμένες περιπτώσεις χρήσης ενός RecyclerView είναι με δυναμικές λίστες όπου τα δεδομένα μπορούν να προέρχονται από ένα διαδικτυακό τελικό σημείο ή ακόμα και ίσως από έναν αισθητήρα. Όχι μόνο προστίθενται δεδομένα, αλλά δεδομένα στη λίστα μας μερικές φορές αφαιρούνται ή ενημερώνονται.

Η δυναμική κατάσταση των δεδομένων μας μπορεί να δυσχεράνει πολύ την ανάλυση του LayoutManager. Για αυτόν τον λόγο, το LayoutManager διατηρεί τη δική του λίστα σχετικά με τα στοιχεία και τις θέσεις, ξεχωριστή από τη λίστα που χρησιμοποιεί το στοιχείο ανακύκλωσης. Αυτό διασφαλίζει ότι εκτελεί σωστά τη λειτουργία της διάταξης.

Ταυτόχρονα, το LayoutManager του RecyclerView δεν θέλει να παρουσιάσει λανθασμένα τα δεδομένα που έχει. Για να λειτουργήσει σωστά, το LayoutManager συγχρονίζεται με το RecyclerView.Adapter σε συγκεκριμένα χρονικά διαστήματα (60ms), κοινοποιώντας πληροφορίες σχετικά με τα στοιχεία της λίστας μας, δηλαδή στοιχεία που προστέθηκαν, ενημερώθηκαν, αφαιρέθηκαν ή μετακινήθηκαν από τη μία θέση στην άλλη. Το LayoutManager, μόλις λάβει αυτές τις πληροφορίες, αναδιοργανώνει τα περιεχόμενα στην οθόνη ώστε να ταιριάζουν με τις αλλαγές όταν είναι απαραίτητο.

Πολλές από τις βασικές λειτουργίες που ασχολούνται με το RecyclerView περιστρέφονται γύρω από την επικοινωνία μεταξύ του RecyclerView.LayoutManager και του RecyclerView.Adapter, το οποίο αποθηκεύει τις λίστες μας με μερικές φορές στατικά ή μερικές φορές δυναμικά δεδομένα.

Επιπλέον, το RecyclerView μας παρουσιάζει μεθόδους που μπορούμε να χρησιμοποιήσουμε για να ακούσουμε συμβάντα, όπως το onBindViewHolder όταν το RecyclerView.Adapter συνδέει περιεχόμενο από τη λίστα μας (π.χ. μια επαφή) με ένα ViewHolder, έτσι ώστε να χρησιμοποιείται για την εμφάνιση των πληροφοριών στην οθόνη.

Ένα άλλο είναι το onCreateViewHolder, το οποίο μας λέει πότε το RecyclerView.Adapter λαμβάνει μια κανονική προβολή όπως το OneContactView και τη μετατρέπει σε ένα στοιχείο ViewHolder με το οποίο μπορεί να λειτουργήσει το RecyclerView.

Εκτός από τους βασικούς μηχανισμούς που επιτρέπουν την ανακύκλωση, το RecyclerView παρέχει τρόπους για την προσαρμογή της συμπεριφοράς χωρίς να επηρεάζεται η ανακύκλωση. Η επαναχρησιμοποίηση των προβολών δυσκολεύει την εκτέλεση συνηθισμένων πράξεων που έχουμε συνηθίσει να κάνουμε με τις στατικές προβολές, όπως η αντίδραση σε συμβάντα onClick.

Όπως γνωρίζουμε, το RecyclerView.LayoutManager που παρουσιάζει τις Προβολές στον χρήστη μπορεί για μια στιγμή να έχει διαφορετική λίστα στοιχείων από το RecyclerView.Adapter που έχει τη λίστα αποθηκευμένη σε μια βάση δεδομένων ή μεταδίδεται μέσω ροής από μια πηγή. Η τοποθέτηση συμβάντων OnClick απευθείας στις Προβολές μπορεί να οδηγήσει σε απροσδόκητη συμπεριφορά, όπως η διαγραφή λανθασμένης επαφής.

Gradle

Αν θέλουμε να χρησιμοποιήσουμε το RecyclerView, πρέπει να το προσθέσουμε ως εξάρτηση στο αρχείο build.gradle. Στο ακόλουθο παράδειγμα, χρησιμοποιήσαμε την υλοποίηση "androidx.recyclerview:recyclerview:1.1.0", η οποία είναι η πιο πρόσφατη έκδοση σύμφωνα με αυτό το άρθρο.

Αφού προσθέσουμε την εξάρτηση στο δικό μας Gradle αρχείο, θα μας ζητηθεί Android Studio για να συγχρονίσουμε τις αλλαγές. Έτσι είναι ο τρόπος με τον οποίο Gradle Το αρχείο θα φροντίσει μετά την προσθήκη ενός RecyclerView σε ένα κενό έργο με μόνο τις προεπιλογές.

apply plugin: 'com.android.application'

apply plugin: 'kotlin-android'

apply plugin: 'kotlin-android-extensions'

android {
    compileSdkVersion 29
    buildToolsVersion "29.0.2"
    defaultConfig {
        applicationId "com.guru99.learnrecycler"
        minSdkVersion 17
        targetSdkVersion 29
        versionCode 1
        versionName "1.0"
        testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
    }
    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'),
             'proguard-rules.pro'
        }
    }
}

dependencies {
    implementation fileTree(dir: 'libs', include: ['*.jar'])
    implementation"org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
    implementation 'androidx.appcompat:appcompat:1.1.0'
    implementation 'androidx.core:core-ktx:1.1.0'
    implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
    testImplementation 'junit:junit:4.12'
    androidTestImplementation 'androidx.test.ext:junit:1.1.1'
    androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'

    implementation "androidx.recyclerview:recyclerview:1.1.0"
}

Προς το παρόν, έχουμε μόνο ένα αρχείο διάταξης. Θα ξεκινήσουμε με ένα απλό παράδειγμα όπου χρησιμοποιούμε ένα RecyclerView για να εμφανίσουμε μια λίστα με ονόματα φρούτων στην οθόνη.

Λίστα αντικειμένων

Θα μεταβούμε στο αρχείο MainActivity και θα δημιουργήσουμε έναν πίνακα με ονόματα φρούτων μέσα, ακριβώς πριν από τη μέθοδο onCreate() που δημιουργήθηκε κατά την εγκατάσταση.

package com.guru99.learnrecycler

import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle

class MainActivity : AppCompatActivity() {

    var fruitNames:Array<String> = arrayOf<String>("Banana", "Mango", "Passion fruit", "Orange", "Grape")

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
    }
}

Ο επόμενος στόχος μας θα είναι να παρουσιάσουμε αυτήν τη λίστα στην οθόνη χρησιμοποιώντας ένα RecyclerView. Για να το κάνουμε αυτό, θα μεταβούμε στον κατάλογο διάταξης που περιέχει τις διατάξεις μας και θα δημιουργήσουμε μια προβολή που θα είναι υπεύθυνη για την εμφάνιση ενός φρούτου.

Διάταξη που θα χρησιμοποιηθεί για κάθε στοιχείο στη λίστα μας

<?xml version="1.0" encoding="utf-8"?>
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:id="@+id/fruitName"
    />
</TextView>

Στην παραπάνω προβολή κειμένου (TextView), έχουμε προσθέσει ένα πεδίο id που θα χρησιμοποιηθεί για την αναγνώριση μιας προβολής (View). Δεν δημιουργείται από προεπιλογή. Έχουμε δώσει στην προβολή κειμένου (TextView) το id fruitName για να ταιριάζει με τα δεδομένα, τα οποία θα είναι συνδεδεμένα με αυτό.

Προσθήκη του RecyclerView στην κύρια διάταξη

Στην ίδια δραστηριότητα, υπάρχει ένα αρχείο διάταξης main_layout.xml που δημιουργήθηκε για εμάς από προεπιλογή. Αν επιλέξουμε ένα κενό έργο, θα έχει δημιουργηθεί ένα XML που περιέχει ένα ConstraintLayout και μέσα θα υπάρχει ένα TextView με κείμενο "Hello". Θα διαγράψουμε όλα τα περιεχόμενα και η διάταξη θα περιέχει μόνο το RecyclerView όπως παρακάτω:

<?xml version="1.0" encoding="utf-8"?>
<androidx.recyclerview.widget.RecyclerView xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
     android:id="@+id/fruitRecyclerView"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity" />

Έχουμε επίσης προσθέσει ένα χαρακτηριστικό id για το RecyclerView, το οποίο θα χρησιμοποιήσουμε για να το αναφέρουμε στον κώδικά μας.

android:id="@+id/fruitRecyclerView"

Στη συνέχεια, θα επιστρέψουμε στο αρχείο MainActivity. Χρησιμοποιώντας τα αναγνωριστικά που έχουμε δημιουργήσει, θα μπορούμε να αναφέρουμε τα Views που μόλις δημιουργήσαμε. Θα ξεκινήσουμε αναφέροντας το RecyclerView χρησιμοποιώντας τη μέθοδο findViewById() που παρέχεται από AndroidΘα το κάνουμε αυτό με τη μέθοδο onCreate(). Η μέθοδος onCreate() θα έχει την εξής μορφή.

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setContentView(R.layout.activity_main)

    var myFruitRecyclerView: RecyclerView = findViewById(R.id.fruitRecyclerView)
}

Δημιουργήστε ένα ViewHolder

Στη συνέχεια, θα δημιουργήσουμε ένα RecyclerView.ViewHolder, το οποίο είναι υπεύθυνο για τη μετατροπή του View μας σε ViewHolder, το οποίο χρησιμοποιεί το RecyclerView για την εμφάνιση των στοιχείων μας. Θα το κάνουμε αυτό αμέσως μετά τη μέθοδο onCreate().

package com.guru99.learnrecycler

import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.view.View
import androidx.recyclerview.widget.RecyclerView

class MainActivity : AppCompatActivity() {

    var fruitNames:Array<String> = arrayOf<String>("Banana", "Mango", "Passion fruit", "Orange", "Grape")

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        var myFruitRecyclerView: RecyclerView = findViewById(R.id.fruitRecyclerView)
    }

    class FruitViewHolder(fruitView: View): RecyclerView.ViewHolder(fruitView)

}

Δημιουργήστε έναν προσαρμογέα RecyclerView

Στη συνέχεια, θα δημιουργήσουμε μια κλάση FruitArrayAdapter που επεκτείνει την κλάση RecyclerView.Adapter. Το FruitArrayAdapter που δημιουργούμε θα είναι υπεύθυνο για τα εξής: θα παίρνει ονόματα φρούτων από τον πίνακα φρούτων, θα δημιουργεί ένα ViewHolder χρησιμοποιώντας την προβολή μας one_fruit_view.xml, στη συνέχεια θα συνδέει το φρούτο με ένα ViewHolder και θα συνδέει δυναμικά τα περιεχόμενα στην προβολή που δημιουργήσαμε.

package com.guru99.learnrecycler

import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.view.View
import androidx.recyclerview.widget.RecyclerView

class MainActivity : AppCompatActivity() {

    var fruitNames:Array<String> = arrayOf<String>("Banana", "Mango", "Passion fruit", "Orange", "Grape")

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        var myFruitRecyclerView: RecyclerView = findViewById(R.id.fruitRecyclerView)
    }

    class FruitViewHolder(fruitView: View): RecyclerView.ViewHolder(fruitView)

    class FruitArrayAdapter(var fruitArray: Array<String>) : RecyclerView.Adapter<FruitViewHolder>()
}

Android Studio θα προσθέσει κόκκινες γραμμές στο FruitArrayAdapter μας, λέγοντάς μας ότι πρέπει να εφαρμόσουμε μεθόδους που μπορεί να χρησιμοποιήσει το RecyclerView για να συνδέσει τον πίνακα μας με ένα ViewHolder.

class FruitListAdapter(var fruitArray: Array<String>) : RecyclerView.Adapter<FruitViewHolder>() {
     override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): FruitViewHolder {

     }

     override fun getItemCount(): Int {

     }

     override fun onBindViewHolder(holder: FruitViewHolder, position: Int) {

     }
 }

Θα ξεκινήσουμε με το ευκολότερο κομμάτι από τον παραγόμενο κώδικα, τη μέθοδο getItemCount(). Γνωρίζουμε πώς να βρούμε τον αριθμό των στοιχείων στον πίνακά μας καλώντας την ιδιότητα μεγέθους πίνακα.

class FruitListAdapter(var fruitArray: Array<String>) : RecyclerView.Adapter<FruitViewHolder>() {
        override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): FruitViewHolder {

        }

        override fun getItemCount(): Int {
         return fruitArray.size
        }

        override fun onBindViewHolder(holder: FruitViewHolder, position: Int) {

        }
    }

Στη συνέχεια, θα υλοποιήσουμε τη μέθοδο onCreateViewHolder. Εδώ είναι που το RecyclerView μας ζητά να το βοηθήσουμε να κατασκευάσει ένα FruitViewHolder. Αν θυμόμαστε καλά, η κλάση FruitViewHolder μας έμοιαζε με αυτή:

class FruitViewHolder(fruitView: View): RecyclerView.ViewHolder(fruitView)

Απαιτεί το fruitView μας, το οποίο δημιουργήσαμε ως αρχείο XML one_fruit_view.xml. Μπορούμε να δημιουργήσουμε μια αναφορά σε αυτό το XML και να το μετατρέψουμε σε View ως εξής.

class FruitListAdapter(var fruitArray: Array<String>) : RecyclerView.Adapter<FruitViewHolder>() {

        override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): FruitViewHolder {

            var fruitView = LayoutInflater.from(parent.context).inflate(R.layout.one_fruit_view, parent, false)

            var fruitViewHolder = FruitViewHolder(fruitView)

            return fruitViewHolder

        }

        override fun getItemCount(): Int {
            return fruitArray.size
        }

        override fun onBindViewHolder(holder: FruitViewHolder, position: Int) {

        }
    }

Το υπόλοιπο bit είναι η παράκαμψη onBindViewHolder.

fun onBindViewHolder(holder: FruitViewHolder, position: Int)

Το RecyclerView.Adapter ζητάει έναν ακέραιο αριθμό θέσης, τον οποίο χρησιμοποιούμε για να ανακτήσουμε ένα στοιχείο από τη λίστα μας. Μας παρέχει επίσης έναν holder, ώστε να μπορούμε να συνδέσουμε το στοιχείο που λαμβάνουμε από το fruitArray με την προβολή που βρίσκεται μέσα στον holder της προβολής. Η προβολή που βρίσκεται μέσα σε ένα ViewHolder είναι προσβάσιμη μέσω του πεδίου ViewHolder.itemView. Μόλις λάβουμε την προβολή, μπορούμε να χρησιμοποιήσουμε το id fruitName που δημιουργήσαμε νωρίτερα για να ορίσουμε το περιεχόμενο.

override fun onBindViewHolder(holder: FruitViewHolder, position: Int) {

          var ourFruitTextView = holder.itemView.findViewById<TextView>(R.id.fruitName)

          var aFruitName = fruitArray.get(position)

          ourFruitTextView.setText(aFruitName)
      }

Με αυτό, ο FruitArrayAdapter μας είναι ολοκληρωμένος και έχει ως εξής.

class FruitListAdapter(var fruitArray: Array<String>) : RecyclerView.Adapter<FruitViewHolder>() {

        override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): FruitViewHolder {

            var fruitView = LayoutInflater.from(parent.context).inflate(R.layout.one_fruit_view, parent, false)

            var fruitViewHolder = FruitViewHolder(fruitView)

            return fruitViewHolder
        }

        override fun getItemCount(): Int {
            return fruitArray.size
        }

        override fun onBindViewHolder(holder: FruitViewHolder, position: Int) {

            var ourFruitTextView = holder.itemView.findViewById<TextView>(R.id.fruitName)

            var aFruitName = fruitArray.get(position)

            ourFruitTextView.setText(aFruitName)
        }
    }

Τέλος, είμαστε έτοιμοι να συνδέσουμε τα υπόλοιπα κομμάτια του RecyclerView μας, τα οποία δημιουργούν ένα LayoutManager που θα λέει στο RecyclerView πώς να εμφανίσει τα περιεχόμενα της λίστας, είτε με γραμμικό τρόπο χρησιμοποιώντας το LinearLayoutManager είτε σε πλέγμα χρησιμοποιώντας το GridLayoutManager ή το StaggeredGridLayoutManager.

Δημιουργία Layout Manager

Θα επιστρέψουμε στη συνάρτηση onCreate και θα προσθέσουμε το LayoutManager.

override fun onCreate(savedInstanceState: Bundle?) {
       super.onCreate(savedInstanceState)
       setContentView(R.layout.activity_main)

       var myFruitRecyclerView: RecyclerView = findViewById(R.id.fruitRecyclerView)

       var fruitLinearLayout = LinearLayoutManager(this)

       myFruitRecyclerView.layoutManager =fruitLinearLayout

   }

Συνδέστε τον προσαρμογέα μας σε αντικείμενα και ρυθμίστε τον στο RecyclerView

override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        var myFruitRecyclerView: RecyclerView = findViewById(R.id.fruitRecyclerView)

        var fruitLinearLayout = LinearLayoutManager(this)

        myFruitRecyclerView.layoutManager =fruitLinearLayout

        var fruitListAdapter = FruitListAdapter(fruitNames)

        myFruitRecyclerView.adapter =fruitListAdapter
    }

Δημιουργήσαμε επίσης μια παρουσία fruitListAdapter και της βάλαμε τον πίνακα με τα ονόματα των φρούτων. Και ουσιαστικά, τελειώσαμε. Το πλήρες αρχείο MainActivity.kt έχει ως εξής.

package com.guru99.learnrecycler

import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.TextView
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView

class MainActivity : AppCompatActivity() {

    var fruitNames:Array<String> = arrayOf<String>("Banana", "Mango", "Passion fruit", "Orange", "Grape")

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        var myFruitRecyclerView: RecyclerView = findViewById(R.id.fruitRecyclerView)

        var fruitLinearLayout = LinearLayoutManager(this)

        myFruitRecyclerView.layoutManager =fruitLinearLayout

        var fruitListAdapter = FruitListAdapter(fruitNames)

        myFruitRecyclerView.adapter =fruitListAdapter
    }

    class FruitViewHolder(fruitView: View): RecyclerView.ViewHolder(fruitView)

    class FruitListAdapter(var fruitArray: Array<String>) : RecyclerView.Adapter<FruitViewHolder>() {

        override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): FruitViewHolder {

            var fruitView = LayoutInflater.from(parent.context).inflate(R.layout.one_fruit_view, parent, false)

            var fruitViewHolder = FruitViewHolder(fruitView)

            return fruitViewHolder
        }

        override fun getItemCount(): Int {
            return fruitArray.size
        }

        override fun onBindViewHolder(holder: FruitViewHolder, position: Int) {

            var ourFruitTextView = holder.itemView.findViewById<TextView>(R.id.fruitName)

            var aFruitName = fruitArray.get(position)

            ourFruitTextView.setText(aFruitName)
        }
    }
}

Κατεβάστε το Έργο

Συχνές Ερωτήσεις

Το RecyclerView είναι ο πιο ευέλικτος και αποτελεσματικός διάδοχος του ListView. Επιβάλλει το μοτίβο ViewHolder, υποστηρίζει γραμμικές, πλέγματες και κλιμακωτές διατάξεις μέσω των διαχειριστών διάταξης και προσθέτει ενσωματωμένες κινούμενες εικόνες στοιχείων, ενώ το ListView δεν προσφέρει καμία από αυτές από προεπιλογή.

Το GridView εμφανίζει μόνο στοιχεία σε ένα σταθερό πλέγμα. Το RecyclerView μπορεί να αναπαράγει ένα πλέγμα χρησιμοποιώντας το GridLayoutManager, αλλά υποστηρίζει επίσης γραμμικές και κλιμακωτές διατάξεις, ανακύκλωση προβολών και κινούμενα σχέδια, καθιστώντας το πολύ πιο ευέλικτο από το GridView.

Αυτό το παράδειγμα RecyclerView είναι γραμμένο σε Kotlin, τη γλώσσα που συνιστάται επίσημα από Google για Android ανάπτυξη. Τα ίδια API RecyclerView, adapter και layout manager λειτουργούν επίσης σε Java.

Συνδέστε ένα πρόγραμμα ακρόασης κλικ μέσα στο ViewHolder ή στο onBindViewHolder και, στη συνέχεια, εκθέστε το μέσω μιας ανάκλησης διεπαφής στη δραστηριότητα ή το τμήμα. Η χρήση της θέσης ViewHolder αποφεύγει τη σύνδεση κλικ σε λάθος ανακυκλωμένο στοιχείο.

Το DiffUtil είναι μια κλάση βοηθητικού προγράμματος που υπολογίζει τη διαφορά μεταξύ δύο λιστών και αποστέλλει το ελάχιστο σύνολο ενημερώσεων στον προσαρμογέα. Κάνει τις δυναμικές αλλαγές λιστών αποτελεσματικές και ενεργοποιεί αυτόματα τις προσθήκες, τις αφαιρέσεις και τις μετακινήσεις.

Το Jetpack Compose χρησιμοποιεί τα LazyColumn και LazyRow αντί για το RecyclerView για νέες οθόνες. Ωστόσο, το RecyclerView εξακολουθεί να χρησιμοποιείται ευρέως σε υπάρχοντα έργα που βασίζονται σε View και μπορεί να ενσωματωθεί στο Compose μέσω API διαλειτουργικότητας.

Ναι. Οι βοηθοί τεχνητής νοημοσύνης, όπως το GitHub Copilot και τα εργαλεία που βασίζονται στο GPT, μπορούν να δημιουργήσουν ένα στερεότυπο προσαρμογέα RecyclerView, ViewHolder και onBindViewHolder από μια σύντομη προτροπή. Οι προγραμματιστές θα πρέπει να ελέγχουν τον παραγόμενο κώδικα για ορθότητα και απόδοση.

Εκμάθηση μηχανών Android Οι εφαρμογές χρησιμοποιούν το RecyclerView για την απόδοση δυναμικών εξόδων, όπως προτάσεις, κατατάξεις αναζήτησης ή ταξινομήσεις εικόνων. Ο προσαρμογέας συνδέει τα αποτελέσματα του μοντέλου με στοιχεία λίστας, έτσι ώστε τα μεγάλα, συχνά μεταβαλλόμενα σύνολα δεδομένων να μπορούν να κυλούν ομαλά.

Συνοψίστε αυτήν την ανάρτηση με: