Android RecyclerView: Là gì, Tìm hiểu với các ví dụ đơn giản
RecyclerView trong là gì Android?
RecyclerXem là một tiện ích là phiên bản linh hoạt và nâng cao 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 có thể được cuộn một cách hiệu quả bằng cách duy trì số lượt xem hạn chế. Bạn có thể sử dụng tiện ích RecyclerView khi có bộ sưu tập dữ liệu có các thành phần 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.
Lượt xem
Android platform sử dụng lớp View và ViewGroup để vẽ các mục trên màn hình. Các lớp này là trừu tượng và được mở rộng sang các cách triển khai khác nhau để phù hợp với 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 mở rộng từ cùng một lớp View và bổ sung thêm nhiều chức năng hơn để cho phép người dùng nhập dữ liệu.
Có thể tạo các chế độ xem tùy chỉnh của riêng mình để có thể linh hoạt hơn khi phát triển 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ẽ trê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 của riêng chúng ta mà chúng ta muốn thêm vào Chế độ xem của mình để làm cho nó hoạt động như chúng ta mong muốn.
Xem nhóm
Lớp ViewGroup là một loại Chế độ xem nhưng, không giống như lớp Chế độ xem đơn giản có trách nhiệm hiển thị đơn giản, ViewGroup cung cấp cho chúng ta khả năng đặt nhiều chế độ xem vào một chế độ xem mà chúng ta có thể tham chiếu tổng thể. Trong trường hợp này, Chế độ xem được tạo ở cấp cao nhất mà chúng tôi thêm các chế độ xem đơn giản khác (chúng tôi cũng có thể thêm Nhóm chế độ xem) vào được gọi là "chế độ xem gốc" và các chế độ xem được thêm vào bên trong là "chế độ xem con".
Chúng ta có thể hình ảnh Chế độ xem dưới dạng Mảng và Nhóm View dưới dạng Mảng. Cho rằng Mảng các Mảng chính là một Mảng, chúng ta có thể thấy một ViewGroup có thể được xử lý như một View như thế nào.
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 anology, 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 các thành phần con được sắp xếp bên trong chế độ xem, ví dụ, được sắp xếp theo chiều dọc hoặc chiều ngang. Chúng ta có thể có các quy tắc khác nhau để tương tác bên trong Chế độ xem. Ví dụ, TextView theo sau nhau phải có khoảng cách 12dp trong khi ImageView theo sau TextView phải có khoảng cách 5dp.
Đây sẽ là trường hợp nếu chúng tôi đang phát triển ViewGroup của riêng mình từ đầu. Để thực hiện các cấu hình này dễ dàng hơn, 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 tôi sẽ triển khai khi định cấu hình ViewGroup của riêng mình. Một số tham số phổ biến là những tham số 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í dụ: android:layout_width. Về vấn đề này, khi tạo ViewGroup, bạn có thể tạo thêm LayoutParams cụ thể theo cách bạn muốn ViewGroup của mình hoạt động.
Android đi kèm với Chế độ xem và Nhóm xem mặc định mà chúng ta có thể sử dụng để thực hiện nhiều tác vụ phổ biến mà chúng ta cần. Một ví dụ mà chúng tôi đã đề cập là TextView. Đó là chế độ xem đơn giản đi kèm với các khía cạnh có thể định cấu hình như chiều cao, chiều rộng, kích thước văn bản và những thứ tương tự. Chúng tôi có ImageView để hiển thị Hình ảnh và EditText như chúng tôi đã đề cập, cùng nhiều thứ khác. 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 trong đó. LinearLayout là thuộc tính định hướng cho biết nó sẽ được bố trí như thế nào trên màn hình. Nó cũng có LinearLayout.LayoutParams quy định các quy tắc cho các chế độ xem bên trong, ví dụ: thuộc tính android:center_horizontal sẽ căn giữa các chế độ xem dọc theo trục ngang trong khi đó, `android:center_vertical sẽ căn giữa nội dung của chế độ xem dọc theo trục tung.
Dưới đây là một số hình ảnh để bạn hiểu về định tâm. Chúng tôi sẽ coi đây là một TextView đơn giản bên trong không gian 200px x 200px, các thuộc tính căn giữa sẽ khiến nó hoạt động như sau.
android:center_horizontal
android:center_vertical
android:trung tâm
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ộ điều hợp là thiết bị chuyển đổi các thuộc tính của hệ thống hoặc thiết bị thành các thuộc tính của thiết bị hoặc hệ thống không tương thích. Một số trong số chúng sửa đổi các thuộc tính tín hiệu hoặc công suất, trong khi những thiết 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 này sang đầu nối khác.
Một ví dụ đơn giản chúng ta tìm thấy trong thực tế để giải thích về Adapter là khi chúng ta cần kết nối các thiết bị với nhau nhưng chúng có cổng kết nối không khớp với nhau. Đây có thể là trường hợp khi bạn đến thăm một quốc gia khác nơi họ 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, sẽ không thể kết nối nó với ổ cắm điện. Tuy nhiên, bạn sẽ không bỏ cuộc mà chỉ cần lấy một Bộ chuyển đổi nằm giữa ổ cắm điện và bộ sạc của bạn và cho phép quá trình sạc 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 để hoàn thành 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ề thiết bị và bộ sạc. Chúng ta sẽ có hai phiên bản bộ sạc. Một người Mỹ và một người Anh
class AmericanCharger() { var chargingPower = 10 } class BritishCharger(){ var charginPower = 5 }
Sau đó chúng ta sẽ tạo 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()
Sau đó, chúng tôi 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 thiết bị có tên charge() .
Phương thức này lấy bộ sạc tương ứng làm đầu vào 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 tự của chúng tôi, vì lý do này hay lý do khác, chúng tôi sẽ cần sử dụng BritishCharger khi sử dụng AmericanDevice 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 làm theo cách tương tự, chúng ta sẽ cần đến một cửa hàng điện tử và mua một bộ chuyển đổi để có thể sạc các Thiết bị Mỹ bằng BritishChargers. Từ góc độ lập trình, chúng tôi sẽ là nhà sản xuất bộ chuyển đổi.
Chúng tôi sẽ tạo một Bộ điều hợp cho một bộ điều hợp phù hợp với mẫu chính xác mà chúng tôi cần để tạo bộ điều hợp kia. Chúng ta sẽ triển khai nó như 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 chức năng chung của mẫu bộ điều hợp. Chúng tôi sẽ sử dụng một lớp vì nó phù hợp với hầu hết cách sử dụng trên 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 charingPower:Int = charger.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 về ổ cắm cũng tương tự như sự khác biệt về phương pháp sạc bên trong. Các bộ sạc có phương pháp khác nhau sẽ khiến việc sử dụng bộ sạc không thể thực hiện được.
var myBritishDevice = new BritishDevice() var americanChargerIFound = new AmericanCharger()
Cố gắng gọi phương thức charge() trong myBritishDevice bằng AmericanChargerIFound sẽ không hoạt động vì AmericanDevice chỉ chấp nhận AmericanCharger
Vì vậy không thể làm được đ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 chúng tôi đã tạo
AmericanToBritishChargerAdapter giờ đây có thể trở nên 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 để tính phí. Tất cả những gì chúng ta cần là tạo một phiên bản của bộ điều hợp và cung cấp nó bằng 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(theAmericanCharger) //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 một ViewGroup, chúng ta sẽ có các View được đặt bên trong nó. LayoutManager sẽ có nhiệm vụ mô tả cách các Chế độ xem được bố trí bên trong.
Với mục đích so sánh, khi làm việc với Linearlayout ViewGroup, trường hợp sử dụng mà chúng tôi mong muốn là khả năng đặt các mục theo chiều dọc hoặc chiều ngang. Điều này có thể dễ dàng thực hiện bằng cách thêm thuộc tính định hướng, thuộc tính này cho chúng ta biết cách bố trí tuyến tính sẽ được đặt 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 tôi cũng có một ViewGroup khác gọi là GridLayout, trường hợp sử dụng này là khi chúng tôi muốn đặt Chế độ xem trong cấu trúc Lưới hình chữ nhật. Điều này có thể vì những lý do như làm cho dữ liệu chúng tôi trình bày cho người dùng ứng dụng trở nên dễ sử dụng. Theo thiết kế, GridLayout cho phép các cấu hình giúp bạn đạt được mục tiêu này bằng cách có các cấu hình trong đó chúng ta có thể xác định kích thước của lưới, ví dụ: chúng ta có thể có lưới 4 × 4, lưới 3 x 2.
RecyclerView.ViewHolder
ViewHolder là một lớp trừu tượng mà chúng tôi cũng mở rộng từ RecyclerView. ViewHolder cung cấp cho chúng ta các phương pháp phổ biến để giúp chúng ta tham chiếu Chế độ xem mà chúng ta đã đặt trên RecyclerView ngay cả sau khi máy Tái chế trong RecyclerView đã thay đổi nhiều tham chiếu khác nhau mà chúng ta không biết.
Danh sách lớn
RecyclerView được sử dụng khi chúng tôi muốn hiển thị một tập hợp Chế độ xem thực sự lớn cho người dùng, đồng thời không làm cạn kiệt khả năng của chúng tôi. 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 chúng ta lấy trường hợp của Contact-List, chúng ta sẽ có ý tưởng chung về cách một contact sẽ trông như thế nào 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 ô mà nhiều dữ liệu khác nhau từ danh sách contact của chúng ta sẽ được điền vào. Sau đây là một mã giả 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 hệ có tính chất này
<ContactList> </ContactList>
Nếu đúng như vậy, chúng tôi đã mã hóa nội dung nên chúng tôi sẽ không có cách 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. Thật may mắn cho chúng tôi. Việc thêm Chế độ xem vào Nhóm Chế độ xem được hỗ trợ bởi addView(view:View)
phương pháp.
Ngay cả trong trường hợp đó, đó không phải là cách RecyclerView thêm các chế độ xem trẻ em 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 địa chỉ liên hệ. Đối với mỗi liên hệ trong danh sách, chúng ta cần tạo OnecontactView và điền dữ liệu vào trong Chế độ xem để khớp với các trường trong lớp Liên hệ của chúng ta. Sau đó, khi có chế độ xem, chúng tôi sẽ cần thêm chế độ xem đó 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 tôi có một loạt địa chỉ liên hệ được gọi là OnecontactView. Nó chứa các khe để lấy nội dung từ lớp Liên hệ và hiển thị chúng. Trong RecyclerView, chúng tôi phải thêm Chế độ xem vào đó để nó có thể giúp chúng tôi về khả năng tái chế.
RecyclerView không thực sự cho phép chúng tôi thêm chế độ xem nhưng cho phép chúng tôi thêm ViewHolder. Vì vậy, trong trường hợp này, chúng ta có hai phần chúng ta muốn kết nối nhưng không khớp. Đây là nơi Bộ điều hợp của chúng tôi xuất hiện. RecyclerView cung cấp cho chúng tôi Bộ điều hợp giống như bộ điều hợp của chúng tôi. AmericanToBritishChargerAdapter()
từ trước đó đã cho phép chúng tôi chuyển đổi AmericanCharger vốn không sử dụng được với BritishDevice của chúng tôi thành một thứ có thể sử dụng được, giống như bộ đổi nguồn trong đời thực.
Trong trường hợp này, bộ điều hợp sẽ lấy mảng Danh bạ và Chế độ xem của chúng tôi, rồi từ đó tạo ra ViewHolders 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 Bộ điều hợp thông qua lớp RecyclerView.Adapter. Bên trong Adaptor này là một cách để tạo lớp ViewHolder mà RecyclerView muốn làm việc. Vì vậy, những gì chúng ta có cũng là tình trạng tương tự như trước, nhưng có thêm một thứ, đó là Adaptor.
Chúng tôi có một mảng Danh bạ, một chế độ xem để hiển thị một liên hệ OneLinkView. RecyclerView là danh sách các Chế độ xem cung cấp dịch vụ tái chế nhưng chỉ sẵn sàng đảm nhận ViewHolders
Nhưng trong kịch bản này, bây giờ chúng ta có lớp RecyclerView.Adapter, lớp này 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 lớp trừu tượng lấy View của chúng ta làm đối số và chuyển đổi nó thành ViewHolder.
Nó sử dụng mẫu bao bọc được sử dụng để 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 để chứng minh cách chúng ta có thể khiến Động vật biết nói.
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 tình cờ, chúng tôi muốn thêm một phương pháp để nói chuyện, nhưng tác giả thư viện không vui, chúng tôi vẫn có thể tìm ra cách. Những gì chúng ta cần sẽ là một trình bao bọc cho lớp Động vật của chúng ta. Chúng ta sẽ làm điều này bằng cách lấy Animal làm hàm tạo cho lớp của chúng ta
class SpeechPoweredAnimalByWrapper(var myAnimal:Animal){ fun sound(){ myAnimal.sound() } speak(){ println("Hello, my name is ${myAnimal.name}") } }
Bây giờ chúng ta có thể chuyển một phiên bản động vật sang SpeechPoweredAnimalByWrapper. Gọi phương thức sound() trên đó sẽ gọi phương thức sound() được truyền trong động vật. Chúng tôi cũng có một phương thức speak() bổ sung, được tính là chức năng mới mà chúng tôi thêm vào các động vật được truyền vào. Chúng tôi 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ể nhận các lớp và thêm chức năng. Tất cả những gì chúng ta cần là chuyển một thể hiện của lớp và các phương thức mới được xác định bởi lớp gói của chúng ta.
Trong trường hợp của chúng tôi ở trên, chúng tôi đã sử dụng một lớp cụ thể. Bạn cũng có thể thực hiện điều tương tự trong lớp Trừu tượng. Những gì chúng ta cần làm là thêm thay đổi lớp SpeechPoweredAnimalByWrapper thành trừu tượng và thế là xong. Chúng ta sẽ đổi tên lớp thành tên ngắn hơn để dễ đọc hơn.
abstract class SpeechPowered(var myAnimal:Animal){ fun sound(){ myAnimal.sound() } speak(){ println("Hello, my name is ${myAnimal.name}") } }
Nó vẫn giống như trước đây, nhưng nó có ý nghĩa khác. Trong một lớp bình thường, chúng ta có thể có một thể hiện của một lớp giống như cách chúng ta đã tạo cat1 và dog1. Tuy nhiên, các lớp trừu tượng không có nghĩa là được khởi tạo mà nhằm mục đích mở rộng các lớp khác. Vậy chúng ta sẽ sử dụng lớp trừu tượng SpeechPowered(var myAnimal:Animal) mới như thế nào. Chúng ta có thể sử dụng nó bằng cách tạo các lớp mới sẽ mở rộng nó và từ đó đạt được chức năng của nó.
Trong ví dụ của chúng tôi, chúng tôi sẽ tạo một lớp SpeechPoweredAnimal mở rộng 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à mẫu tương tự được sử dụng trong ViewHolder. Lớp RecyclerView.ViewHolder là một lớp trừu tượng bổ sung chức năng cho Chế độ xem, giống như chúng ta đã thêm phương thức speak cho động vật. Chức năng bổ sung giúp nó hoạt động khi xử lý RecyclerView.
Đây là cách chúng tôi tạo 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 Adaptor cho phép chúng ta kết nối mảng Danh bạ của mình với ContactsView bằng RecyclerView
Thêm chế độ xem
ViewGroup không tự động vẽ lại ViewGroup mà tuân theo một lịch trình cụ thể. Có thể thiết bị của bạn 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ý như 1 phút khi chúng ta thêm View vào ViewGroup, bạn sẽ thấy những thay đổi sau 1 phút khi ViewGroup "làm mới".
RecyclerView.Recycler
Nền tảng số 3. Bộ nhớ đệm
Một trong những ví dụ điển hình nhất về nơi chúng tôi thường xuyên làm mới là trong Trình duyệt. Ví dụ: hãy tưởng tượng trang web chúng ta đang truy cập là trang web tĩnh và không gửi nội dung động, chúng ta cần liên tục làm mới để xem các thay đổi.
Trong ví dụ này, hãy tưởng tượng trang web được đề cập là Twitter. Chúng tôi sẽ có một loạt các tweet tĩnh được liệt kê và cách duy nhất để chúng tôi có thể xem các Tweet mới là nhấp vào nút làm mới để tải lại nội dung.
Sơn lại toàn bộ màn hình rõ ràng là một việc tốn kém. Nếu chúng tôi tưởng tượng nếu đúng như vậy thì chúng tôi có băng thông hạn chế với nhà cung cấp điện thoại của mình. Và danh sách tweet của chúng tôi có rất nhiều hình ảnh và video, sẽ rất tốn kém nếu tải lại tất cả nội dung của trang trong mỗi lần làm mới.
Chúng tôi sẽ 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ủa chúng tôi có khả năng nói các Tweet mà nó đã có. Do đó, nó không tải xuống lại mọi thứ và chỉ nhận các Tweet mới mà nó có, đồng thời kiểm tra xem một số Tweet đã được lưu cục bộ có còn ở đó hay không để có thể xóa nó cục bộ. Những gì chúng tôi đang mô tả được gọi là Bộ nhớ đệm.
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, theo nghĩa 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”, 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 sử dụng nội dung đó để chỉ gửi cho chúng tôi những gì không có trong đó, vì vậy chúng tôi không sử dụng nhiều băng thông vì chúng tôi không có nhiều tài nguyên.”
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 hiện diện trong những thứ như ứng dụng trò chuyện. Nếu ai đó trong chuỗi trò chuyện trả lời bong bóng trò chuyện trước đó thì một số ứng dụng trò chuyện sẽ bao gồm câu trả lời và liên kết tới bong bóng trò chuyện, khi nhấp vào sẽ điều hướng bạn đến vị trí của tin nhắn ban đầu.
Trong trường hợp này, chúng tôi gọi phương thức này trước khi thêm LayoutManager vào RecyclerView và trước khi chúng tôi có RecyclerView.Adapter, cuộnToPosition(n:Int) sẽ bị bỏ qua.
Giao tiếp giữa các thành phần RecyclerView
Nền tảng số 4. Cuộc gọi lại
RecyclerView, khi thực hiện công việc của mình, có rất nhiều bộ phận chuyển động. Nó phải xử lý Trình quản lý bố cục để cho chúng ta biết cách sắp xếp các Chế độ xem, theo tuyến tính hoặc trong Lưới. Nó phải xử lý một Bộ điều hợp thực hiện công việc chuyển đổi Danh sách liên hệ các mục của chúng ta thành Chế độ xem OnecontactView và sau đó thành ViewHolders OnecontactViewHolder mà RecyclerView sẵn sàng hoạt động theo các phương thức mà nó cung cấp cho chúng ta.
Nguyên liệu thô cho RecyclerView là Chế độ xem của chúng tôi, ví dụ: OneLinkView và 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 được những gì RecyclerView đang cố gắng đạt được.
Trường hợp cơ bản về thời điểm chúng tôi có một mảng tĩnh gồm 1000 địa chỉ liên hệ mà chúng tôi muốn hiển thị cho người dùng là điều dễ hiểu.
Bộ máy RecyclerView thực sự bắt đầu hoạt động khi danh sách không còn tĩnh nữa.
Với danh sách động, chúng ta phải nghĩ xem điều gì sẽ xảy ra với Chế độ xem trên màn hình khi chúng ta 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 quan điểm của chúng ta theo Tuyến tính hoặc trong Lưới. Trình quản lý bố cục thực hiện rất nhiều công việc trong việc giúp Người tái chế biết khi nào nên thực hiện Tái chế.
Nó có trách nhiệm theo dõi các Chế độ xem hiện hiển thị trên Màn hình và truyền thông tin này đến cơ chế Tái chế. Khi người dùng cuộn xuống, Trình quản lý bố cục có trách nhiệm thông báo cho hệ thống Tái chế về các Chế độ xem không tập trung ở trên cùng để chúng có thể được sử dụng lại thay vì vẫn ở đó và tiêu tốn bộ nhớ hoặc thay vì phá hủy chúng và phải tạo những cái mới.
Điều này có nghĩa là Trình quản lý bố cục cần theo dõi vị trí của người dùng khi họ cuộn danh sách của chúng tôi. Nó thực hiện điều này bằng cách có một danh sách các vị trí là cơ sở 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 của chúng tôi.
Nếu chúng ta có thể xem 10 mục trong danh sách, chẳng hạn như 100, thì ngay từ đầu, Trình quản lý bố cục sẽ biết rằng nó tập trung vào chế độ xem-0 cho đến Chế độ xem-9 Khi chúng ta cuộn Trình quản lý bố cục có thể tính toán các chế độ xem thoát ra tập trung.
Trình quản lý bố cục có thể giải phóng các chế độ xem này sang 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 Chế độ xem 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ế phần giữ chỗ).
Đây sẽ là một trường hợp tốt nếu Danh sách chúng tôi có là tĩnh, nhưng một trong những trường hợp sử dụng phổ biến nhất khi sử dụng RecyclerView là với danh sách động, nơi dữ liệu có thể đến từ điểm cuối trực tuyến hoặc thậm chí có thể 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 tôi đôi khi còn bị xóa hoặc cập nhật.
Trạng thái động của dữ liệu của chúng tôi có thể gây khó khăn cho việc giải thích về Trình quản lý bố cục. Vì lý do này, Trình quản lý bố cục duy trì một 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ố trí một cách chính xác.
Đồng thời, LayoutManager của RecyclerView không muốn trình bày 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) bằng cách chia sẻ thông tin về các mục Danh sách của chúng ta. tức là, Các mục được thêm vào, cập nhật, xóa, di chuyển từ vị trí này sang vị trí khác). LayoutManager, khi nhận được thông tin này, sẽ sắp xếp lại nội dung trên Màn hình để khớp với các thay đổi khi cần thiết.
Nhiều hoạt động cốt lõi xử lý RecylerView xoay quanh việc giao tiếp giữa RecyclerView.LayoutManager và RecyclerView.Adapter lưu trữ danh sách dữ liệu đôi khi tĩnh hoặc đôi khi động của chúng tôi.
Ngoài ra, RecyclerView còn cung cấp cho chúng ta các phương pháp mà chúng ta có thể sử dụng để theo dõi các sự kiện như onBindViewHolder khi RecyclerView.Adapter liên kết nội dung từ Danh sách của chúng ta, ví dụ: Liên hệ với ViewHolder để giờ đây nó quen với việc hiển thị thông tin trên Màn hình.
Một cái khác là onCreateViewHolder, cho chúng ta biết khi nào RecyclerView. Bộ chuyển đổi nhận một Chế độ xem thông thường như OnecontactView và chuyển đổi nó thành một mục ViewHolder mà RecyclerView có thể hoạt động. Từ ViewHolder của chúng tôi để RecyclerView sử dụng. onViewDetached
Ngoài các cơ chế cốt lõi cho phép Tái chế. RecyclerView cung cấp các cách để tùy chỉnh hành vi mà không ảnh hưởng đến Tái chế.
Việc sử dụng lại Chế độ xem gây khó khăn cho việc thực hiện những việc thông thường mà chúng ta thường làm với chế độ xem tĩnh, chẳng hạn như phản ứng với các sự kiện onClick.
Như chúng tôi biết rằng 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
có danh sách mà chúng tôi đã lưu trữ trong cơ sở dữ liệu hoặc truyền phát từ một nguồn. Việc đặt trực tiếp các sự kiện OnClick trên Chế độ xem có thể dẫn đến hành vi không mong muốn, chẳng hạn như xóa nhầm địa chỉ liên hệ hoặc thay đổi.
Gradle
Nếu muốn sử dụng RecyclerView, chúng ta cần thêm nó làm phần phụ thuộc trong tệp build .gradle của mình.
Trong ví dụ sau, chúng tôi đã sử dụng phiên bản triển khai “androidx.recyclerview:recyclerview:1.1.0” 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 đến Syncđồng hóa hóa những thay đổi,
Đây là cách của chúng tôi Gradle tệp sẽ trông giống như sau khi thêm RecyclerView vào một dự án trống chỉ có các giá trị 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 tôi 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ẽ 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 có tên trái cây bên trong ngay trước phương thức onCreate() được tạo 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 tôi sẽ là hiển thị danh sách này trên màn hình bằng RecyclerView.
Để thực hiện đ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 Chế độ xem có nhiệm vụ 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 tôi đã thêm trường id sẽ được sử dụng để xác định Chế độ xem.
Nó không được tạo theo mặc định. Chúng tôi có TextView id 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ó tệp bố cục main_layout.xml được tạo cho chúng tôi theo mặc định.
Nếu chúng ta chọn một dự án trống. Nó sẽ tạo ra một XML chứa ConstraintLayout và bên trong sẽ là TextView có văn bản “Xin chào”.
Chúng tôi sẽ xóa tất cả nội dung và bố cục chỉ chứa RecyclerView 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 mà chúng tôi sẽ sử dụng để tham chiếu thuộc tính đó trong mã của mình.
android:id="@+id/fruitRecyclerView"
Sau đó chúng ta sẽ điều hướng trở lại tệp MainActivity của mình. Bằng cách sử dụng id mà chúng tôi đã tạo, chúng tôi sẽ có thể tham chiếu Chế độ xem mà chúng tôi vừa tạo.
Chúng ta sẽ bắt đầu bằng cách tham chiếu RecyclerView bằng phương thức findViewById() do Android. Chúng tôi sẽ thực hiện việc này trong phương thức onCreate() của mình.
Phương thức onCreate() của chúng ta sẽ trô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 RecyclerView.ViewHolder chịu trách nhiệm lấy Chế độ xem của chúng ta và chuyển đổi nó thành ViewHolder mà RecyclerView sử dụng để hiển thị các mục của chúng ta.
Chúng ta sẽ thực hiện việc này ngay sau phương thức onCreate() thú vị của mình.
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 mở rộng lớp RecyclerView.Adapter.
FruitArrayAdapter mà chúng ta tạo ra sẽ có nhiệm vụ thực hiện những việc sau.
Nó sẽ lấy tên trái cây từ mảng trái cây. Nó sẽ tạo một ViewHolder bằng cách sử dụng chế độ xem one_fruit_view.xml của chúng tôi. Sau đó, nó sẽ liên kết trái cây với ViewHolder và liên kết động các nội dung với Chế độ xem mà chúng tôi đã tạo one_fruit_view.xml.
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 sẽ thêm các đường ngoằn ngoèo màu đỏ trên FruitArrayAdapter, cho chúng ta biết rằng chúng ta cần triển khai một 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 mà RecyclerView có thể sử dụng.
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 là 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 phương thức array.length.
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) { } }
Sau đó chúng ta sẽ triển khai phương thức ghi đè fun onCreateViewHolder.
Đây là nơi RecyclerView yêu cầu chúng tôi trợ giúp xây dựng FruitHolder cho nó.
Nếu chúng ta nhớ lại thì 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 tôi đã tạo dưới dạng tệp xml one_fruit_view.xml
Chúng ta có thể tạo một tham chiếu đến xml này và chuyển đổi nó thành Chế độ xem 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) { } }
Bit còn lại là phần ghi đè
fun onBindViewHolder(holder: FruitViewHolder, position: Int)
RecyclerView.Adapter yêu cầu với số nguyên vị trí mà chúng tôi sẽ sử dụng để tìm nạp một mục từ danh sách của mình. Nó cũng cung cấp cho chúng ta một ngăn chứa để chúng ta có thể liên kết mục mà chúng ta nhận được từ FruitArray với Chế độ xem đang được giữ bên trong ngăn chứa chế độ xem.
Chế độ xem được giữ bên trong ViewHoder có thể truy cập được thông qua trường ViewHolder.itemView. Khi có được chế độ xem, chúng tôi có thể sử dụng id FruitName mà chúng tôi đã tạo trước đó để đặt 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 tất và trô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 phần còn lại của RecyclerView. Đang tạo Trình quản lý bố cục, trình này sẽ cho RecyclerView biết cách hiển thị nội dung của danh sách. Hiển thị theo cách tuyến tính bằng LinearLayoutManager hay trong 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 bên trong hàm onCreate và thêm 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 }
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 tôi cũng đã tạo một phiên bản FruitListAdapter và cung cấp cho nó một mảng tên trái cây.
Và về cơ bản, tất cả chúng ta đã hoàn tất.
Tệp MainActivity.kt hoàn chỉnh 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) } } }