Android RecyclerView: Là gì, Tìm hiểu với các ví dụ đơn giản
⚡ Tóm tắt thông minh
RecyclerView là một công cụ tiên tiến. Android Widget này hiển thị các tập dữ liệu lớn, có thể cuộn một cách hiệu quả bằng cách tái sử dụng một số lượng khung nhìn hạn chế. Nó dựa vào một bộ điều hợp, một trình quản lý bố cục và một trình giữ khung nhìn để liên kết các tập hợp dữ liệu thay đổi trong thời gian chạy.

RecyclerView trong là gì Android?
RecyclerXem RecyclerView là phiên bản linh hoạt và tiên tiến hơn của GridView và ListView. Nó là một vùng chứa để hiển thị các tập dữ liệu lớn, cho phép cuộn hiệu quả bằng cách duy trì số lượng khung nhìn hạn chế. Bạn có thể sử dụng widget RecyclerView khi có các tập hợp dữ liệu mà các phần tử của chúng thay đổi trong thời gian chạy tùy thuộc vào sự kiện mạng hoặc hành động của người dùng.
Cảnh Quan
Android Nền tảng này sử dụng các lớp View và ViewGroup để vẽ các mục trên màn hình. Các lớp này hoàn toàn khả thi.tracCác lớp này được mở rộng thành nhiều cách triển khai khác nhau để phù hợp với từng trường hợp sử dụng. Ví dụ, TextView có mục đích đơn giản là hiển thị nội dung văn bản trên màn hình. EditText kế thừa từ cùng một lớp View và bổ sung thêm chức năng để cho phép người dùng nhập dữ liệu.
Chúng ta có thể tạo ra các khung nhìn tùy chỉnh riêng để đạt được sự linh hoạt hơn khi phát triển.ping Giao diện người dùng. Lớp View cung cấp các phương thức mà chúng ta có thể ghi đè để vẽ lên màn hình và một phương tiện để truyền các tham số như chiều rộng, chiều cao và các thuộc tính tùy chỉnh riêng mà chúng ta muốn thêm vào View để làm cho nó hoạt động theo ý muốn.
Xem nhóm
Lớp ViewGroup là một loại View, nhưng không giống như lớp View đơn giản chỉ có nhiệm vụ hiển thị, ViewGroup cho phép chúng ta đặt nhiều View vào một View duy nhất, mà chúng ta có thể tham chiếu đến toàn bộ. Trong trường hợp này, View được tạo ở cấp cao nhất mà chúng ta thêm các View đơn giản khác vào được gọi là "View cha", và các View được thêm vào bên trong được gọi là "View con".
Ta có thể hình dung một View như một mảng và một ViewGroup như một mảng các mảng. Vì mảng các mảng bản thân nó cũng là một mảng, ta có thể thấy ViewGroup có thể được xử lý như một 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 cũng cho phép chúng ta định nghĩa cách sắp xếp các phần tử con bên trong View, ví dụ như chúng được bố trí theo chiều dọc hay chiều ngang. Chúng ta có thể đặt ra các quy tắc tương tác khác nhau bên trong View. Ví dụ, các TextView nằm cạnh nhau nên có khoảng cách 12dp, trong khi một ImageView nằm cạnh một TextView khác nên có khoảng cách 5dp.
Điều này sẽ xảy ra nếu chúng ta đang phát triển...ping Chúng tôi tự tạo ViewGroup riêng từ đầu. Để đơn giản hóa các cấu hình này, Android cung cấp một lớp có tên là LayoutParams mà chúng ta có thể sử dụng để nhập các cấu hình này.
Android tài liệu hướng dẫn Cung cấp một số tham số mặc định mà chúng ta sẽ triển khai khi cấu hình ViewGroup của riêng mình. Một số tham số phổ biến liên quan đến chiều rộng, chiều cao và lề. Theo mặc định, các cấu hình này có cấu trúc android:layout_height cho chiều cao và android:layout_width cho chiều rộng. Về vấn đề này, khi bạn tạo ViewGroup của mình, bạn có thể tạo thêm các LayoutParams cụ thể để tùy chỉnh cách bạn muốn ViewGroup hoạt động.
Android Nó đi kèm với các View và ViewGroup mặc định mà chúng ta có thể sử dụng để thực hiện nhiều tác vụ thông thường cần thiết. Một ví dụ mà chúng ta đã đề cập là TextView. Đó là một View đơn giản đi kèm với các thuộc tính có thể cấu hình như chiều cao, chiều rộng và kích thước văn bản. Chúng ta cũng có ImageView để hiển thị hình ảnh và EditText, như chúng ta đã đề cập, cùng với nhiều View khác nữa. Android cũng có các Nhóm Chế độ xem tùy chỉnh mà chúng tôi có thể thêm Chế độ xem của mình vào và nhận được hành vi mong đợi.
Bố cục tuyến tính
LinearLayout cho phép chúng ta thêm các mục View vào bên trong nó. LinearLayout có thuộc tính orientation quy định cách bố trí trên màn hình. Nó cũng có LinearLayout.LayoutParams quy định các quy tắc cho các View bên trong. Ví dụ, thuộc tính android:center_horizontal sẽ căn giữa các View theo trục ngang, trong khi android:center_vertical sẽ căn giữa nội dung của View theo trục dọc.
Dưới đây là một số hình ảnh giúp bạn hiểu rõ hơn về việc căn giữa. Chúng ta giả sử đây là một TextView đơn giản nằm trong không gian 200px x 200px; các thuộc tính căn giữa sẽ làm cho nó hoạt động như sau.
android:center_horizontal
Nội dung được căn giữa theo chiều ngang
android:center_vertical
Nội dung được căn giữa theo chiều dọc
android:trung tâm
Nội dung tập trung
Các thành phần cốt lõi của RecyclerView
Các thành phần cốt lõi của RecyclerView
Sau đây là các thành phần quan trọng của RecyclerView:
RecyclerView.Adapter
Nền tảng số 1 – Mẫu bộ điều hợp
Bộ chuyển đổi (adapter) là thiết bị biến đổi các thuộc tính của một hệ thống hoặc thiết bị này thành các thuộc tính của một thiết bị hoặc hệ thống khác không tương thích. Một số bộ chuyển đổi điều chỉnh các thuộc tính tín hiệu hoặc nguồn điện, trong khi những bộ khác chỉ đơn giản là điều chỉnh hình dạng vật lý của một đầu nối cho phù hợp với đầu nối khác.
Một ví dụ thực tế đơn giản để giải thích về bộ chuyển đổi là khi chúng ta cần kết nối các thiết bị với nhau, nhưng chúng lại có các cổng kết nối không khớp nhau. Điều này có thể xảy ra khi bạn đến một quốc gia khác sử dụng các loại ổ cắm khác nhau. Nếu bạn mang theo bộ sạc điện thoại hoặc máy tính xách tay, việc kết nối nó với các ổ cắm điện là không thể. Tuy nhiên, bạn sẽ không bỏ cuộc mà chỉ cần mua một bộ chuyển đổi để kết nối giữa ổ cắm điện và bộ sạc của bạn, cho phép sạc pin diễn ra.
Đây là trường hợp trong lập trình khi chúng ta muốn kết nối hai cấu trúc dữ liệu với nhau để thực hiện một nhiệm vụ, nhưng các cổng mặc định của chúng không có cách nào để giao tiếp với nhau.
Chúng ta sẽ sử dụng ví dụ đơn giản về một thiết bị và một bộ sạc. Chúng ta sẽ có hai loại bộ sạc, một loại của Mỹ và một loại của Anh.
class AmericanCharger() { var chargingPower = 10 } class BritishCharger(){ var chargingPower = 5 }
Sau đó, chúng ta sẽ tạo ra hai thiết bị.
class AmericanDevice() class BritishDevice()
Ví dụ: sau đó chúng tôi có thể tạo một số phiên bản của thiết bị để chơi cùng.
var myAmericanPhone = new AmericanDevice() var myBritishPhone = new BritishDevice()
Tiếp theo, chúng ta sẽ giới thiệu khái niệm sạc cho cả hai thiết bị bằng cách thêm một phương thức vào các thiết bị có tên là charge(). Phương thức này nhận vào bộ sạc tương ứng và thực hiện sạc dựa trên bộ sạc đó.
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 } }
Trong trường hợp này, dựa trên sự tương đồng của chúng ta, vì lý do nào đó chúng ta sẽ cần sử dụng bộ sạc kiểu Anh khi sử dụng thiết bị của Mỹ, hoặc ngược lại.
Trong thế giới lập trình, điều này thường xảy ra khi kết hợp các thư viện cung cấp cùng chức năng với nhau (trong ngữ cảnh của chúng tôi, chức năng chung của chúng tôi là tính phí). Chúng ta cần phải tìm cách kích hoạt tính năng này.
Nếu ta theo đuổi phép so sánh này, chúng ta sẽ cần đến một cửa hàng điện tử và mua một bộ chuyển đổi cho phép sạc các thiết bị của Mỹ bằng bộ sạc của Anh. Từ góc độ lập trình, chúng ta sẽ là nhà sản xuất bộ chuyển đổi đó.
Chúng ta sẽ tạo một bộ chuyển đổi phù hợp chính xác với mẫu mà chúng ta cần để tạo ra cái kia. Chúng ta sẽ triển khai nó dưới dạng một lớp như sau. Nó không nhất thiết phải là một lớp và có thể là một hàm làm nổi bật những gì mẫu bộ chuyển đổi thường làm. Chúng ta sử dụng một lớp vì nó phù hợp với hầu hết các trường hợp sử dụng. 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 } }
Trong thế giới lập trình, sự khác biệt giữa các loại socket cũng tương tự như sự khác biệt trong các phương pháp sạc pin. Việc các bộ sạc có phương pháp khác nhau sẽ khiến chúng không thể sử dụng được.
var myBritishDevice = new BritishDevice() var americanChargerIFound = new AmericanCharger()
Việc cố gắng gọi phương thức charge() trên myBritishDevice với americanChargerIFound sẽ không hoạt động, vì AmericanDevice chỉ chấp nhận AmericanCharger. Vì vậy, không thể thực hiện điều này:
var myBritishDevice = new BritishDevice() var americanChargerIFound = new AmericanCharger() myBritishDevice.charge(americanChargerIFound)
Trong trường hợp này, bộ chuyển đổi mà chúng ta đã tạo, AmericanToBritishChargerAdapter, giờ đây có thể rất hữu ích. Chúng ta có thể sử dụng phương thức returnNewCharger() để tạo một BritishCharger mới, mà chúng ta có thể sử dụng để sạc. Tất cả những gì chúng ta cần làm là tạo một thể hiện của bộ chuyển đổi và cung cấp cho nó AmericanCharger mà chúng ta có, và nó sẽ tạo ra một BritishCharger mà chúng ta có thể sử dụng.
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
Khi làm việc với ViewGroup, chúng ta có các View được đặt bên trong nó. LayoutManager có nhiệm vụ mô tả cách các View được bố trí bên trong.
Để so sánh, khi làm việc với một LinearLayout ViewGroup, trường hợp sử dụng mà chúng ta cần là khả năng đặt các mục theo chiều dọc hoặc chiều ngang. Điều này dễ dàng được thực hiện bằng cách thêm thuộc tính orientation, cho chúng ta biết LinearLayout sẽ được đặt như thế nào trên màn hình. Chúng ta có thể làm điều này bằng cách sử dụng... android:orientation=VERTICAL|HORIZONTAL thuộc tính.
Chúng ta cũng có một ViewGroup khác gọi là GridLayout. Trường hợp sử dụng của nó là khi chúng ta muốn đặt các View trong cấu trúc lưới hình chữ nhật. Điều này có thể vì những lý do như giúp người dùng ứng dụng dễ dàng tiếp cận dữ liệu hơn. Theo thiết kế, GridLayout cho phép cấu hình để giúp bạn đạt được mục tiêu này bằng cách xác định kích thước của lưới; ví dụ, chúng ta có thể có lưới 4×4 hoặc lưới 3×2.
RecyclerView.ViewHolder
ViewHolder là một abstracLớp này cũng kế thừa từ RecyclerView. ViewHolder cung cấp cho chúng ta các phương thức chung giúp chúng ta tham chiếu đến một View mà chúng ta đã đặt trên RecyclerView, ngay cả sau khi cơ chế tái chế trong RecyclerView đã thay đổi nhiều tham chiếu mà chúng ta không biết.
Danh sách lớn
RecyclerView được sử dụng khi chúng ta muốn hiển thị một tập hợp các View rất lớn cho người dùng, mà không làm cạn kiệt tài nguyên hệ thống. RAM trên thiết bị của chúng tôi cho từng phiên bản của Chế độ xem được tạo.
Nếu xét trường hợp danh bạ, chúng ta sẽ có ý tưởng tổng quát về cách một người liên hệ sẽ hiển thị trong danh sách. Sau đó, chúng ta sẽ tạo một bố cục mẫu, thực chất là một View, với các ô trống để điền các dữ liệu khác nhau từ danh bạ của chúng ta. Đoạn mã giả sau đây giải thích toàn bộ mục đích:
//OneContactView
<OneContact>
<TextView>{{PlaceHolderForName}}</TextView>
<TextView>{{PlaceHolderForAddress}}</TextView>
<ImageView>{{PlaceHolderForProfilePicture}}</ImageView>
<TextView>{{PlaceHolderForPhoneNumber}}</TextView>
</OneContact>
Khi đó, chúng ta sẽ có một danh sách liên lạc như sau:
<ContactList> </ContactList>
Nếu chúng ta mã hóa cứng nội dung, chúng ta sẽ không có cách nào lập trình để thêm nội dung mới vào danh sách mà không cần viết lại ứng dụng. May mắn thay, việc thêm một View vào một ViewGroup được hỗ trợ bởi một addView(view:View) Tuy nhiên, đó không phải là cách mà RecyclerView thêm các view con vào.
Trong trường hợp sử dụng của chúng tôi, chúng tôi sẽ có một danh sách dài các liên hệ. Với mỗi liên hệ trong danh sách, chúng tôi cần tạo một OneContactView và điền dữ liệu vào bên trong View đó sao cho khớp với các trường trong lớp Contact của chúng tôi. Sau đó, khi đã có View, chúng ta cần thêm nó vào RecyclerView để hiển thị danh sách.
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)
Chúng ta có một mảng các liên hệ. Lớp OneContactView chứa các khe cắm để lấy nội dung từ lớp Contact và hiển thị chúng. Trong RecyclerView, chúng ta phải thêm các View vào đó để nó có thể hỗ trợ khả năng tái chế của mình.
RecyclerView thực chất không cho phép chúng ta thêm một View mà chỉ cho phép thêm một ViewHolder. Vì vậy, trong trường hợp này, chúng ta có hai thành phần muốn kết nối nhưng chúng không khớp nhau. Đây là lúc adapter phát huy tác dụng. RecyclerView cung cấp cho chúng ta một adapter, tương tự như các thành phần khác mà chúng ta có thể thêm vào. AmericanToBritishChargerAdapter() Điều đó đã giúp chúng tôi chuyển đổi bộ sạc AmericanCharger, vốn không thể sử dụng được với thiết bị BritishDevice của chúng tôi, thành một thiết bị có thể sử dụng được, tương tự như một bộ chuyển đổi nguồn trong thực tế.
Trong trường hợp này, bộ chuyển đổi sẽ lấy mảng các liên hệ và View của chúng ta, và từ đó tạo ra các ViewHolder mà RecyclerView sẵn sàng chấp nhận.
RecyclerView cung cấp một giao diện mà chúng ta có thể mở rộng để tạo adapter thông qua lớp RecyclerView.Adapter. Bên trong adapter này có một cách để tạo lớp ViewHolder mà RecyclerView muốn làm việc cùng. Vì vậy, chúng ta có tình huống tương tự như trước, nhưng thêm một thứ nữa, đó là adapter.
Chúng ta có một mảng các liên hệ, một View để hiển thị một liên hệ (OneContactView), và một RecyclerView, là một danh sách các View cung cấp dịch vụ tái chế nhưng chỉ chấp nhận ViewHolders. Trong trường hợp này, chúng ta có một lớp RecyclerView.Adapter, có một phương thức để tạo ViewHolders bên trong.
fun createViewHolder(@NonNull parent: ViewGroup, viewType: Int): ViewHolder
RecyclerView.ViewHolder là một abstracLớp này nhận View của chúng ta làm đối số và chuyển đổi nó thành ViewHolder. Nó sử dụng mẫu thiết kế wrapper để mở rộng khả năng của các lớp.
Nền tảng số 2 – Mẫu bao bọc
Chúng ta sẽ sử dụng một ví dụ đơn giản để minh họa cách chúng ta có thể làm cho động vật nói chuyện.
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
Trong ví dụ trên, chúng ta có hai con vật. Nếu chẳng may chúng ta muốn thêm một phương thức để chúng nói chuyện, nhưng tác giả thư viện lại không thân thiện, chúng ta vẫn có thể tìm ra cách. Điều chúng ta cần là một lớp bao bọc cho lớp Animal của mình. Chúng ta sẽ làm điều này bằng cách nhận lớp Animal làm hàm tạo cho lớp của chúng ta.
class SpeechPoweredAnimalByWrapper(var myAnimal:Animal){ fun sound(){ myAnimal.sound() } fun speak(){ println("Hello, my name is ${myAnimal.name}") } }
Giờ đây, chúng ta có thể truyền một thể hiện của loài vật vào SpeechPoweredAnimalByWrapper. Việc gọi phương thức sound() trên thể hiện đó sẽ gọi phương thức sound() của loài vật được truyền vào. Chúng ta cũng có thêm một phương thức speak(), được coi là chức năng mới mà chúng ta thêm vào các loài vật được truyền vào. Chúng ta có thể sử dụng nó như sau:
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"
Sử dụng mẫu này, chúng ta có thể lấy các lớp và thêm chức năng cho chúng. Tất cả những gì chúng ta cần là truyền vào một thể hiện của lớp và các phương thức mới được định nghĩa bởi lớp bao bọc của chúng ta.ping lớp học.
Trong trường hợp trên, chúng ta đã sử dụng một lớp cụ thể. Cũng có thể triển khai điều tương tự trong một lớp trừu tượng.traclớp t. Chúng ta cần thay đổi lớp SpeechPoweredAnimalByWrapper thành abstracVà thế là xong. Chúng ta sẽ đổi tên lớp thành một cái tên ngắn gọn hơn để dễ đọc hơn.
abstract class SpeechPowered(var myAnimal:Animal){ fun sound(){ myAnimal.sound() } fun speak(){ println("Hello, my name is ${myAnimal.name}") } }
Nó vẫn giống như trước, nhưng ý nghĩa sẽ khác. Trong một lớp học thông thường, chúng ta có thể có một thể hiện của lớp theo cách tương tự như chúng ta đã tạo ra cat1 và dog1. AbstracTuy nhiên, các lớp t không được dùng để khởi tạo mà dùng để kế thừa các lớp khác. Vậy làm thế nào chúng ta có thể sử dụng SpeechPowered(var myAnimal:Animal) abs mới?tracLớp T? Chúng ta có thể sử dụng nó bằng cách tạo ra các lớp mới kế thừa từ lớp hiện có và từ đó có được chức năng của nó.
Trong ví dụ này, chúng ta sẽ tạo một lớp SpeechPoweredAnimal kế thừa từ lớp đó.
class SpeechPoweredAnimal(var myAnimal:Animal):SpeechPowered(myAnimal)
var cat1 = Cat("Tubby") var speakingKitty = SpeechPoweredAnimal(cat1) speakingKitty.speak() //"Hello, my name is Tubby"
Đây là cùng một mẫu được sử dụng trong ViewHolder. Lớp RecyclerView.ViewHolder là một lớp tuyệt đốitracLớp này bổ sung chức năng cho View, tương tự như cách chúng ta đã thêm phương thức "speak" cho các loài động vật. Chức năng được thêm vào này giúp nó hoạt động khi làm việc với RecyclerView.
Đây là cách chúng ta tạo một OneContactViewHolder từ 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 có một adapter cho phép chúng ta kết nối mảng danh bạ của mình với ContactsView thông qua RecyclerView.
Thêm chế độ xem
ViewGroup không tự động vẽ lại mà tuân theo một lịch trình cụ thể. Trên thiết bị của bạn, có thể nó sẽ vẽ lại sau mỗi 10ms hoặc 100ms; hoặc, nếu chúng ta chọn một con số vô lý, chẳng hạn như 1 phút, khi chúng ta thêm một View vào ViewGroup, bạn sẽ thấy các thay đổi sau 1 phút khi ViewGroup "làm mới".
RecyclerView.Recycler
Bài tập chuẩn bị #3 – Tìm kho báu
Một trong những ví dụ điển hình nhất về việc chúng ta thường xuyên làm mới trang là trên trình duyệt. Hãy tưởng tượng, ví dụ, trang web chúng ta đang truy cập là trang tĩnh và không cập nhật nội dung động; chúng ta sẽ cần phải liên tục làm mới trang để thấy được những thay đổi.
Ví dụ, hãy tưởng tượng trang web đang xét là Twitter. Chúng ta sẽ có một loạt các tweet tĩnh được liệt kê, và cách duy nhất để xem các tweet mới là bằng cách nhấp vào nút làm mới để tải lại nội dung.
Việc vẽ lại toàn bộ màn hình rõ ràng là tốn kém. Hãy tưởng tượng nếu chúng ta có băng thông hạn chế với nhà cung cấp dịch vụ điện thoại, và danh sách tweet của chúng ta có rất nhiều hình ảnh và video; việc tải lại toàn bộ nội dung trang mỗi khi làm mới sẽ rất tốn kém.
Chúng ta cần một cách để lưu trữ các tweet đã được tải và đảm bảo yêu cầu tiếp theo có khả năng cho biết tweet nào đã có sẵn. Do đó, nó không tải lại mọi thứ mà chỉ lấy các tweet mới, đồng thời kiểm tra xem một số tweet đã được lưu cục bộ có còn tồn tại hay không để có thể xóa chúng cục bộ. Điều chúng ta đang mô tả được gọi là bộ nhớ đệm (caching).
Thông tin chúng tôi gửi đến trang web về nội dung chúng tôi có được gọi là siêu dữ liệu. Vì vậy, trên thực tế, chúng tôi không chỉ nói "chúng tôi muốn tải trang web của bạn", mà chúng tôi nói "chúng tôi muốn tải trang web của bạn, và đây là một số nội dung chúng tôi đã lưu từ lần tải trước; vui lòng chỉ gửi cho chúng tôi những nội dung không có trong đó, để chúng tôi không sử dụng nhiều băng thông."
Cuộc gọi bố cục – Danh sách Tweet phải điên rồ
Một ví dụ về lệnh gọi bố cục là scrollToPosition. Đây là một ví dụ phổ biến trong các ứng dụng trò chuyện. Nếu ai đó trong cuộc trò chuyện trả lời một bong bóng trò chuyện trước đó, một số ứng dụng trò chuyện sẽ bao gồm câu trả lời và một liên kết đến bong bóng trò chuyện đó, khi nhấp vào sẽ đưa bạn đến nơi tin nhắn ban đầu của bạn.
Trong trường hợp chúng ta gọi phương thức này trước khi thêm LayoutManager vào RecyclerView và trước khi có RecyclerView.Adapter, thì scrollToPosition(n:Int) sẽ bị bỏ qua.
Giao tiếp giữa các thành phần RecyclerView
Chuẩn bị #4 – Gọi lại
Trong quá trình hoạt động, RecyclerView có rất nhiều thành phần chuyển động. Nó phải tương tác với LayoutManager, thứ cho chúng ta biết cách sắp xếp các View, theo thứ tự tuyến tính hoặc dạng lưới. Nó cũng phải tương tác với một adapter có nhiệm vụ chuyển đổi các mục của chúng ta (contactList) thành các View (OneContactView) và sau đó thành các ViewHolder (OneContactViewHolder) mà RecyclerView sẵn sàng làm việc cùng bằng các phương thức mà nó cung cấp.
Nguyên liệu thô cho RecyclerView là các View của chúng ta, ví dụ như OneContactView, và một nguồn dữ liệu.
contactList:Array<Contact>
Chúng tôi đã sử dụng một kịch bản đơn giản làm điểm khởi đầu để hiểu rõ hơn về mục tiêu mà RecyclerView hướng tới. Trường hợp cơ bản khi chúng ta có một mảng tĩnh gồm 1000 liên hệ muốn hiển thị cho người dùng rất dễ hiểu. Cơ chế hoạt động của RecyclerView thực sự bắt đầu phát huy tác dụng khi danh sách không còn tĩnh nữa. Với một danh sách động, chúng ta phải suy nghĩ về điều gì sẽ xảy ra với giao diện trên màn hình khi thêm một mục vào danh sách hoặc xóa một mục khỏi danh sách.
RecyclerView.LayoutManager
Ngoài việc quyết định cách bố trí các khung nhìn của chúng ta, theo dạng tuyến tính hoặc dạng lưới, LayoutManager còn thực hiện rất nhiều công việc ngầm bên trong để hỗ trợ.ping Người thu gom và tái chế biết khi nào cần thực hiện việc tái chế.
Nó chịu trách nhiệm giữ gìnping tracLayoutManager chịu trách nhiệm xác định số lượng View hiện đang hiển thị trên màn hình và truyền thông tin này đến cơ chế tái sử dụng. Khi người dùng cuộn xuống, LayoutManager có nhiệm vụ thông báo cho hệ thống tái sử dụng về các View bị mất tiêu điểm ở phía trên để chúng có thể được sử dụng lại, thay vì vẫn ở đó và tiêu tốn bộ nhớ hoặc bị hủy chỉ để tạo ra các View mới.
Điều này có nghĩa là LayoutManager cần phải giữ track là chỉ số vị trí của người dùng khi họ cuộn danh sách. Nó thực hiện điều này bằng cách sử dụng một danh sách các vị trí dựa trên chỉ mục, tức là mục đầu tiên bắt đầu từ 0 và tăng dần để khớp với số lượng mục trong danh sách.
Nếu ta có thể xem 10 mục trong danh sách gồm, chẳng hạn, 100 mục, thì ban đầu LayoutManager biết rằng nó đang tập trung vào các View-0 cho đến View-9. Khi ta cuộn, LayoutManager có thể tính toán các View bị mất tiêu điểm.
Trình quản lý bố cục (LayoutManager) có khả năng giải phóng các khung nhìn này vào cơ chế tái chế để chúng có thể được sử dụng lại (dữ liệu mới có thể được liên kết với chúng; ví dụ: dữ liệu liên hệ của một khung nhìn có thể bị xóa và dữ liệu liên hệ mới từ phân đoạn tiếp theo có thể thay thế các chỗ giữ chỗ).
Đây sẽ là một trường hợp lý tưởng nếu danh sách chúng ta có là tĩnh, nhưng một trong những trường hợp sử dụng phổ biến nhất của RecyclerView là với các danh sách động, nơi dữ liệu có thể đến từ một điểm cuối trực tuyến hoặc thậm chí từ một cảm biến. Không chỉ dữ liệu được thêm vào, mà dữ liệu trong danh sách của chúng ta đôi khi còn bị xóa hoặc cập nhật.
Tính chất động của dữ liệu có thể khiến việc hiểu về LayoutManager trở nên rất khó khăn. Vì lý do này, LayoutManager duy trì danh sách riêng về các mục và vị trí, tách biệt với danh sách mà thành phần tái chế sử dụng. Điều này đảm bảo nó thực hiện công việc bố cục một cách chính xác.
Đồng thời, LayoutManager của RecyclerView không muốn hiển thị sai dữ liệu mà nó có. Để hoạt động chính xác, LayoutManager đồng bộ hóa với RecyclerView.Adapter theo các khoảng thời gian nhất định (60ms), chia sẻ thông tin về các mục trong danh sách của chúng ta, tức là các mục được thêm, cập nhật, xóa hoặc di chuyển từ vị trí này sang vị trí khác. Khi nhận được thông tin này, LayoutManager sẽ sắp xếp lại nội dung trên màn hình để phù hợp với các thay đổi khi cần thiết.
Nhiều thao tác cốt lõi liên quan đến RecyclerView xoay quanh việc giao tiếp giữa RecyclerView.LayoutManager và RecyclerView.Adapter, nơi lưu trữ các danh sách dữ liệu, đôi khi là tĩnh, đôi khi là động.
Ngoài ra, RecyclerView cung cấp cho chúng ta các phương thức mà chúng ta có thể sử dụng để lắng nghe các sự kiện, chẳng hạn như onBindViewHolder khi RecyclerView.Adapter liên kết nội dung từ danh sách của chúng ta (ví dụ: một liên hệ) với một ViewHolder để nó được sử dụng để hiển thị thông tin trên màn hình.
Một phương thức khác là onCreateViewHolder, cho chúng ta biết khi nào RecyclerView.Adapter nhận một View thông thường như OneContactView và chuyển đổi nó thành một mục ViewHolder mà RecyclerView có thể làm việc cùng.
Ngoài các cơ chế cốt lõi cho phép tái sử dụng, RecyclerView còn cung cấp các cách để tùy chỉnh hành vi mà không ảnh hưởng đến quá trình tái sử dụng. Việc tái sử dụng các View khiến cho việc thực hiện những thao tác thông thường mà chúng ta vẫn thường làm với các View tĩnh trở nên khó khăn hơn, chẳng hạn như phản ứng với các sự kiện onClick.
Như chúng ta đã biết, RecyclerView.LayoutManager trình bày Chế độ xem cho người dùng trong một lúc có thể có danh sách các mục khác với RecyclerView.Adapter Danh sách đó được lưu trữ trong cơ sở dữ liệu hoặc được truyền trực tiếp từ một nguồn. Việc đặt trực tiếp các sự kiện OnClick trên Views có thể dẫn đến hành vi không mong muốn, chẳng hạn như xóa nhầm liên hệ.
Gradle
Nếu muốn sử dụng RecyclerView, chúng ta cần thêm nó làm phụ thuộc trong tệp build.gradle. Trong ví dụ sau, chúng ta đã sử dụng `implementation “androidx.recyclerview:recyclerview:1.1.0”`, đây là phiên bản mới nhất theo bài viết này.
Sau khi thêm phần phụ thuộc vào Gradle tập tin, chúng tôi sẽ được nhắc bởi Android Studio để đồng bộ hóa các thay đổi. Đây là cách chúng ta... Gradle Tệp này sẽ hiển thị trạng thái sau khi thêm RecyclerView vào một dự án trống chỉ với các thiết lập mặc định.
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" }
Hiện tại chúng ta chỉ có một tệp bố cục. Chúng ta sẽ bắt đầu với một ví dụ đơn giản, trong đó chúng ta sử dụng RecyclerView để hiển thị danh sách tên các loại trái cây trên màn hình.
Danh sách các mục
Chúng ta sẽ điều hướng đến tệp MainActivity và tạo một mảng chứa tên các loại trái cây, ngay trước phương thức onCreate() được tạo ra trong quá trình thiết lập.
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) } }
Mục tiêu tiếp theo của chúng ta là hiển thị danh sách này trên màn hình bằng cách sử dụng RecyclerView. Để làm điều này, chúng ta sẽ điều hướng đến thư mục bố cục chứa các bố cục của mình và tạo một View chịu trách nhiệm hiển thị một loại trái cây.
Bố cục được sử dụng cho từng mục trong danh sách của chúng tôi
<?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>
Trong TextView ở trên, chúng ta đã thêm một trường id sẽ được sử dụng để xác định một View. Trường này không được tạo mặc định. Chúng ta đã đặt id cho TextView của mình là fruitName để khớp với dữ liệu sẽ được liên kết với nó.
Thêm RecyclerView vào bố cục chính
Trong cùng một hoạt động, có một tệp bố cục main_layout.xml được tạo mặc định cho chúng ta. Nếu chúng ta chọn một dự án trống, nó sẽ tạo ra một tệp như vậy. XML Chứa một ConstraintLayout và bên trong sẽ có một TextView với văn bản “Hello”. Chúng ta sẽ xóa tất cả nội dung và chỉ giữ lại RecyclerView như hình bên dưới:
<?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" />
Chúng tôi cũng đã thêm thuộc tính id cho RecyclerView, và chúng tôi sẽ sử dụng thuộc tính này để tham chiếu đến nó trong mã của mình.
android:id="@+id/fruitRecyclerView"
Sau đó, chúng ta sẽ quay lại tệp MainActivity. Sử dụng các ID mà chúng ta đã tạo, chúng ta sẽ có thể tham chiếu đến các View mà chúng ta vừa tạo. Chúng ta sẽ bắt đầu bằng cách tham chiếu đến RecyclerView bằng phương thức findViewById() được cung cấp bởi AndroidChúng ta sẽ thực hiện điều này trong phương thức onCreate(). Phương thức onCreate() của chúng ta sẽ có dạng như sau.
override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) var myFruitRecyclerView: RecyclerView = findViewById(R.id.fruitRecyclerView) }
Tạo một ViewHolder
Tiếp theo, chúng ta sẽ tạo một RecyclerView.ViewHolder, chịu trách nhiệm lấy View của chúng ta và chuyển đổi nó thành một ViewHolder, mà RecyclerView sử dụng để hiển thị các mục. Chúng ta sẽ thực hiện điều này ngay sau phương thức 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) }
Tạo RecyclerViewAdapter
Tiếp theo, chúng ta sẽ tạo một lớp FruitArrayAdapter kế thừa từ lớp RecyclerView.Adapter. Lớp FruitArrayAdapter mà chúng ta tạo ra sẽ chịu trách nhiệm thực hiện các nhiệm vụ sau: lấy tên các loại trái cây từ mảng trái cây, tạo một ViewHolder sử dụng view one_fruit_view.xml của chúng ta, sau đó liên kết trái cây với ViewHolder và liên kết động nội dung của nó với View mà chúng ta đã tạo.
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 Nó sẽ thêm các đường gạch chân màu đỏ vào FruitArrayAdapter của chúng ta, báo hiệu rằng chúng ta cần triển khai các phương thức mà RecyclerView có thể sử dụng để kết nối mảng của chúng ta với 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) { } }
Chúng ta sẽ bắt đầu với phần dễ nhất trong mã được tạo ra, phương thức getItemCount(). Chúng ta biết cách lấy số lượng phần tử trong mảng bằng cách gọi thuộc tính kích thước mảng.
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) { } }
Tiếp theo, chúng ta sẽ triển khai phương thức onCreateViewHolder. Đây là nơi RecyclerView yêu cầu chúng ta giúp nó tạo ra một FruitViewHolder. Nếu nhớ lại, lớp FruitViewHolder của chúng ta trông như thế này:
class FruitViewHolder(fruitView: View): RecyclerView.ViewHolder(fruitView)
Nó yêu cầu fruitView mà chúng ta đã tạo dưới dạng tệp XML one_fruit_view.xml. Chúng ta có thể tạo tham chiếu đến tệp XML này và chuyển đổi nó thành một View như sau.
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) { } }
Phần còn lại là phương thức ghi đè onBindViewHolder.
fun onBindViewHolder(holder: FruitViewHolder, position: Int)
RecyclerView.Adapter yêu cầu một số nguyên chỉ vị trí, mà chúng ta sử dụng để lấy một mục từ danh sách của mình. Nó cũng cung cấp cho chúng ta một ViewHolder để chúng ta có thể liên kết mục nhận được từ fruitArray với View được chứa bên trong ViewHolder. View được chứa bên trong ViewHolder có thể truy cập được thông qua trường ViewHolder.itemView. Sau khi có được View, chúng ta có thể sử dụng id fruitName mà chúng ta đã tạo trước đó để thiết lập nội dung.
override fun onBindViewHolder(holder: FruitViewHolder, position: Int) { var ourFruitTextView = holder.itemView.findViewById<TextView>(R.id.fruitName) var aFruitName = fruitArray.get(position) ourFruitTextView.setText(aFruitName) }
Như vậy, FruitArrayAdapter của chúng ta đã hoàn thiện và có dạng như sau.
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) } }
Cuối cùng, chúng ta đã sẵn sàng kết nối các thành phần còn lại của RecyclerView, đó là tạo ra một LayoutManager để hướng dẫn RecyclerView cách hiển thị nội dung của danh sách, cho dù hiển thị theo kiểu tuyến tính bằng LinearLayoutManager hay theo dạng lưới bằng GridLayoutManager hoặc StaggeredGridLayoutManager.
Tạo Trình quản lý bố cục
Chúng ta sẽ quay lại hàm onCreate và thêm LayoutManager vào.
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 }
Kết nối bộ điều hợp của chúng tôi với các mục và đặt nó trên 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 }
Chúng ta cũng đã tạo một thể hiện của fruitListAdapter và truyền cho nó mảng tên các loại trái cây. Về cơ bản, chúng ta đã hoàn tất. Toàn bộ tệp MainActivity.kt trông như sau.
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) } } }




