Android RecyclerView: 간단한 예를 통해 알아보기
⚡ 스마트 요약
RecyclerView는 고급 버전입니다. Android 이 위젯은 제한된 수의 뷰를 재활용하여 대규모의 스크롤 가능한 데이터 세트를 효율적으로 표시합니다. 런타임에 변경되는 데이터 컬렉션을 바인딩하기 위해 어댑터, 레이아웃 관리자 및 뷰 홀더를 사용합니다.

RecyclerView란 무엇인가요? Android?
The 리사이클러뷰 RecyclerView는 GridView 및 ListView보다 더 유연하고 고급스러운 버전입니다. 제한된 수의 뷰를 유지하여 효율적인 스크롤링이 가능한 대규모 데이터 세트를 표시하는 컨테이너입니다. 네트워크 이벤트나 사용자 동작에 따라 런타임에 요소가 변경되는 데이터 컬렉션이 있는 경우 RecyclerView 위젯을 사용할 수 있습니다.
조회수
The Android 이 플랫폼은 View 및 ViewGroup 클래스를 사용하여 화면에 항목을 그립니다. 이 클래스들은 절대적인 것입니다.tracTextView는 다양한 사용 사례에 맞춰 여러 가지 구현으로 확장될 수 있습니다. 예를 들어 TextView는 화면에 텍스트 콘텐츠를 표시하는 간단한 목적을 가지고 있습니다. EditText는 동일한 View 클래스를 상속받아 사용자가 데이터를 입력할 수 있도록 더 많은 기능을 추가합니다.
개발 시 더 큰 유연성을 확보하기 위해 사용자 지정 뷰를 직접 만들 수도 있습니다.ping 사용자 인터페이스. View 클래스는 화면에 그림을 그리는 데 사용할 수 있는 메서드를 제공하며, 너비, 높이, 그리고 원하는 대로 동작하도록 View에 추가할 수 있는 사용자 지정 속성과 같은 매개변수를 전달하는 방법을 제공합니다.
보기그룹
ViewGroup 클래스는 View의 한 종류이지만, 단순히 화면을 표시하는 역할만 하는 일반 View 클래스와는 달리, 여러 뷰를 하나의 뷰 그룹으로 묶어 참조할 수 있도록 해줍니다. 이 경우, 최상위에 생성되어 다른 뷰들을 추가하는 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을 사용하면 뷰 내부에서 자식 요소들이 어떻게 배치될지, 예를 들어 세로 또는 가로로 배치될지 등을 정의할 수 있습니다. 또한 뷰 내부에서의 상호 작용에 대해 서로 다른 규칙을 적용할 수 있습니다. 예를 들어, 연속되는 TextView 사이에는 12dp의 간격을 두고, ImageView 뒤에 오는 TextView 사이에는 5dp의 간격을 두는 것과 같은 규칙을 만들 수 있습니다.
우리가 개발 중이라면 그럴 것입니다.ping 저희는 ViewGroup을 처음부터 직접 만들었습니다. 이러한 설정을 더 쉽게 하기 위해, Android 이러한 구성을 입력하는 데 사용할 수 있는 LayoutParams라는 클래스를 제공합니다.
The Android 선적 서류 비치 이 문서에서는 ViewGroup을 구성할 때 사용할 몇 가지 기본 매개변수를 제공합니다. 일반적인 매개변수로는 너비, 높이, 여백 등이 있습니다. 기본적으로 높이는 `android:layout_height`, 너비는 `android:layout_width` 구조를 갖습니다. ViewGroup을 생성할 때 원하는 동작 방식에 맞춰 레이아웃 매개변수를 추가로 지정할 수 있습니다.
Android 기본적으로 제공되는 뷰와 뷰그룹을 사용하여 필요한 일반적인 작업을 수행할 수 있습니다. 예를 들어, 앞서 언급한 TextView는 높이, 너비, 텍스트 크기 등을 설정할 수 있는 간단한 뷰입니다. 또한 이미지를 표시하는 ImageView와 EditText 등 다양한 뷰도 제공합니다. Android 또한 뷰를 추가하고 예상되는 동작을 얻을 수 있는 사용자 정의 ViewGroup도 있습니다.
선형 레이아웃
LinearLayout은 뷰 항목을 추가할 수 있는 레이아웃을 제공합니다. LinearLayout에는 화면에 어떻게 배치될지를 결정하는 orientation 속성이 있습니다. 또한, 내부 뷰에 대한 레이아웃 규칙을 지정하는 LinearLayout.LayoutParams 속성도 있습니다. 예를 들어, android:center_horizontal 속성은 뷰를 가로축 중앙에 배치하고, android:center_vertical 속성은 뷰의 내용을 세로축 중앙에 배치합니다.
다음은 가운데 정렬을 이해하는 데 도움이 되는 이미지입니다. 200px x 200px 공간 안에 있는 간단한 TextView를 예로 들면, 가운데 정렬 속성을 적용했을 때 다음과 같이 동작합니다.
안드로이드:center_horizontal
가로 중심 콘텐츠
안드로이드:center_vertical
세로로 가운데 정렬된 콘텐츠
안드로이드:센터
중앙 정렬된 콘텐츠
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 } }
이 경우, 앞서 언급한 비유에 따르면, 어떤 이유로든 미국 기기를 사용할 때 영국 충전기를 사용해야 하거나, 그 반대의 경우도 발생할 수 있습니다.
프로그래밍 세계에서는 일반적으로 동일한 기능을 제공하는 라이브러리를 함께 혼합할 때 발생합니다(우리의 맥락에서 공유 기능은 충전입니다). 우리는 이것을 가능하게 하는 방법을 찾아야 합니다.
이 비유를 따라가자면, 우리는 전자제품 매장에 가서 영국 충전기로 미국 기기를 충전할 수 있게 해주는 어댑터를 사야 할 것입니다. 프로그래밍 관점에서 보면, 우리는 그 어댑터를 만드는 사람이 되는 셈입니다.
우리는 다른 어댑터를 만들 때 필요한 패턴과 정확히 일치하는 어댑터를 만들 것입니다. 다음과 같이 클래스 형태로 구현할 것입니다. 반드시 클래스일 필요는 없으며, 어댑터 패턴의 일반적인 기능을 보여주는 함수일 수도 있습니다. 하지만 대부분의 사용 사례와 일치하기 때문에 클래스를 사용했습니다. 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()
AmericanDevice는 AmericanCharger만 허용하므로, myBritishDevice에서 americanChargerIFound를 사용하여 charge() 메서드를 호출하려고 하면 작동하지 않습니다. 따라서 다음과 같이 하는 것은 불가능합니다.
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
뷰그룹을 다룰 때, 그 안에 뷰들이 배치됩니다. 레이아웃 관리자는 뷰들이 뷰그룹 안에서 어떻게 배치될지 정의하는 역할을 합니다.
비교하자면, LinearLayout ViewGroup을 사용할 때 우리가 원하는 사용 사례는 항목을 세로 또는 가로로 배치할 수 있는 기능입니다. 이는 LinearLayout이 화면에 어떻게 배치될지를 알려주는 orientation 속성을 추가하여 쉽게 구현할 수 있습니다. 이를 위해 다음과 같은 방법을 사용할 수 있습니다. android:orientation=VERTICAL|HORIZONTAL 속성을 사용하지 않는 것입니다.
또한 GridLayout이라는 또 다른 ViewGroup이 있습니다. GridLayout은 뷰를 직사각형 그리드 구조로 배치하려는 경우에 사용됩니다. 예를 들어 앱 사용자에게 제공하는 데이터를 쉽게 이해할 수 있도록 하기 위한 목적일 수 있습니다. GridLayout은 그리드의 크기를 정의하여 이러한 목표를 달성할 수 있도록 다양한 설정을 지원합니다. 예를 들어 4x4 그리드 또는 3x2 그리드를 구성할 수 있습니다.
RecyclerView.ViewHolder
뷰홀더는 ABS입니다.trac우리가 RecyclerView를 상속받는 클래스인 ViewHolder는 RecyclerView에 배치한 뷰를 참조할 수 있도록 공통 메서드를 제공합니다. 이를 통해 RecyclerView의 재활용 메커니즘이 알 수 없는 여러 참조를 변경한 후에도 뷰를 계속 참조할 수 있습니다.
큰 목록
RecyclerView는 메모리 부족 없이 사용자에게 매우 많은 수의 뷰를 표시하고 싶을 때 사용됩니다. 램 생성된 뷰의 모든 인스턴스에 대해 우리 장치에서.
연락처 목록을 예로 들면, 목록에서 각 연락처가 어떻게 표시될지 대략적인 아이디어를 가지고 있을 것입니다. 그런 다음 연락처 목록의 다양한 데이터가 채워질 슬롯이 있는 템플릿 레이아웃(실제로는 뷰)을 생성합니다. 다음은 전체적인 목적을 설명하는 의사 코드입니다.
//OneContactView
<OneContact>
<TextView>{{PlaceHolderForName}}</TextView>
<TextView>{{PlaceHolderForAddress}}</TextView>
<ImageView>{{PlaceHolderForProfilePicture}}</ImageView>
<TextView>{{PlaceHolderForPhoneNumber}}</TextView>
</OneContact>
그러면 다음과 같은 연락처 목록을 갖게 됩니다.
<ContactList> </ContactList>
만약 내용을 하드코딩했다면, 앱을 다시 작성하지 않고는 목록에 새 콘텐츠를 추가할 수 있는 프로그래밍 방식이 없었을 것입니다. 다행히도 ViewGroup에 View를 추가하는 기능은 지원됩니다. 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를 사용하려면 뷰를 추가해야 재활용 기능을 활용할 수 있습니다.
RecyclerView는 실제로 뷰를 추가하는 것을 허용하지 않고 ViewHolder를 추가할 수 있도록 합니다. 따라서 이 시나리오에서는 연결해야 할 두 부분이 있지만 서로 일치하지 않습니다. 바로 이 부분에서 어댑터가 필요합니다. RecyclerView는 우리가 사용하는 어댑터와 유사한 기능을 제공합니다. AmericanToBritishChargerAdapter() 앞서 언급했듯이, 저희 영국 기기와 호환되지 않던 미국 충전기를 실제 생활에서 사용할 수 있는 전원 어댑터처럼 변환할 수 있었습니다.
이 시나리오에서 어댑터는 연락처 배열과 뷰를 받아 RecyclerView가 수용할 수 있는 ViewHolder를 생성합니다.
RecyclerView는 RecyclerView.Adapter 클래스를 통해 확장할 수 있는 인터페이스를 제공합니다. 이 어댑터 내부에는 RecyclerView가 함께 사용할 ViewHolder 클래스를 생성하는 방법이 있습니다. 따라서 이전과 동일한 상황이지만 어댑터라는 요소가 하나 더 추가되었습니다.
연락처 배열, 하나의 연락처를 표시하는 뷰(OneContactView), 그리고 뷰들을 재활용하는 기능을 하지만 ViewHolder만 허용하는 RecyclerView가 있습니다. 이 시나리오에서는 ViewHolder를 생성하는 메서드를 포함하는 RecyclerView.Adapter 클래스가 필요합니다.
fun createViewHolder(@NonNull parent: ViewGroup, viewType: Int): ViewHolder
RecyclerView.ViewHolder는 절대적인 것입니다.trac이 클래스는 View를 인수로 받아 ViewHolder로 변환합니다. 클래스의 기능을 확장하는 데 사용되는 래퍼 패턴을 활용합니다.
기초 작업 #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() 메서드를 호출하면 전달된 동물의 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"
이 패턴을 사용하면 클래스에 기능을 추가할 수 있습니다. 클래스 인스턴스와 래퍼에 정의된 새로운 메서드만 전달하면 됩니다.ping 클래스입니다.
위의 예에서는 구체적인 클래스를 사용했습니다. 하지만 절대적인 방식으로도 동일한 기능을 구현할 수 있습니다.tract 클래스입니다. SpeechPoweredAnimalByWrapper 클래스를 abs로 변경해야 합니다.tract, 이제 끝났습니다. 가독성을 높이기 위해 수업 이름을 더 짧게 변경하겠습니다.
abstract class SpeechPowered(var myAnimal:Animal){ fun sound(){ myAnimal.sound() } fun speak(){ println("Hello, my name is ${myAnimal.name}") } }
이전과 동일하지만 의미는 다릅니다. 일반적인 클래스에서는 cat1과 dog1을 만든 것과 같은 방식으로 클래스의 인스턴스를 가질 수 있습니다.trac하지만 `t` 클래스는 인스턴스화하기 위한 것이 아니라 다른 클래스를 확장하기 위한 것입니다. 그렇다면 새로운 `SpeechPowered(var myAnimal:Animal)`은 어떻게 사용할까요?tract 클래스는 어떻게 사용하나요? 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 클래스는 절대적인 패턴입니다.tract 클래스는 뷰에 기능을 추가하는 클래스로, 마치 동물에 말하기 메서드를 추가한 것과 같습니다. 추가된 기능 덕분에 RecyclerView를 다룰 때 제대로 작동합니다.
OneContactView에서 OneContactViewHolder를 생성하는 방법은 다음과 같습니다.
//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에는 RecyclerView를 통해 연락처 배열을 ContactsView에 연결할 수 있는 어댑터가 있습니다.
보기 추가
ViewGroup은 자동으로 다시 그려지지 않고 특정 일정에 따라 다시 그려집니다. 기기에 따라 10ms 또는 100ms마다 다시 그려질 수 있습니다. 예를 들어 1분 간격으로 다시 그려진다고 가정하면, ViewGroup에 View를 추가했을 때 ViewGroup이 "새로 고침"되는 1분 후에 변경 사항이 표시됩니다.
RecyclerView.Recycler
기초 작업 #3 – 캐싱
우리가 정기적으로 새로 고침을 하는 가장 좋은 예 중 하나는 웹 브라우저입니다. 예를 들어, 방문하는 사이트가 정적이고 콘텐츠를 동적으로 전송하지 않는다고 가정해 봅시다. 이 경우 변경 사항을 확인하려면 계속해서 새로 고침을 해야 할 것입니다.
예를 들어, 해당 사이트가 트위터라고 가정해 보겠습니다. 트위터에는 고정된 트윗 목록이 표시되고, 새로운 트윗을 보려면 새로고침 버튼을 클릭하여 콘텐츠를 다시 가져와야 합니다.
화면 전체를 다시 그리는 것은 분명히 비용이 많이 드는 일입니다. 예를 들어, 휴대폰 통신사의 대역폭이 제한적이고 트윗 목록에 이미지와 동영상이 많다고 가정해 보세요. 페이지를 새로 고칠 때마다 모든 콘텐츠를 다시 다운로드하는 것은 엄청난 비용이 들 것입니다.
이미 로드된 트윗을 저장하고 다음 요청에서 어떤 트윗을 이미 가져왔는지 알 수 있도록 하는 방법이 필요합니다. 이렇게 하면 모든 트윗을 다시 다운로드하지 않고 새 트윗만 가져올 수 있으며, 로컬에 저장된 트윗 중 더 이상 존재하지 않는 트윗이 있으면 삭제할 수 있습니다. 이것이 바로 캐싱입니다.
저희가 웹사이트에 보내는 콘텐츠 관련 정보를 메타데이터라고 합니다. 그러니까 실제로는 단순히 "사이트를 로드하고 싶습니다"라고 하는 게 아니라, "사이트를 로드하고 싶은데, 이전에 로드했을 때 저장해둔 콘텐츠가 있습니다. 여기에 없는 콘텐츠만 보내주시면 대역폭 사용량을 줄일 수 있습니다"라고 하는 겁니다.
레이아웃 호출 - 트윗 목록이 이상할 것 같아요
레이아웃 호출의 예로는 scrollToPosition이 있습니다. 이는 채팅 앱에서 흔히 볼 수 있는 예입니다. 채팅 스레드에서 누군가가 이전에 남긴 말풍선에 답글을 달면, 일부 채팅 앱은 답글과 함께 원래 말풍선으로 연결되는 링크를 표시합니다. 이 링크를 클릭하면 원래 메시지가 있던 위치로 이동합니다.
RecyclerView에 LayoutManager가 추가되기 전이나 RecyclerView.Adapter가 추가되기 전에 이 메서드를 호출하는 경우, scrollToPosition(n:Int)는 무시됩니다.
RecyclerView 구성요소 간 통신
기초 작업 #4 – 콜백
RecyclerView는 작동하는 과정에서 여러 구성 요소를 처리해야 합니다. 먼저, 뷰를 선형으로 배치할지 그리드 형태로 배치할지 결정하는 LayoutManager를 다뤄야 합니다. 또한, contactList 항목을 OneContactView로 변환하고, 다시 RecyclerView가 제공하는 메서드를 사용하여 처리할 수 있는 ViewHolder(OneContactViewHolder)로 변환하는 어댑터도 다뤄야 합니다.
RecyclerView의 원자재는 OneContactView와 같은 당사의 뷰와 데이터 소스입니다.
contactList:Array<Contact>
우리는 RecyclerView가 달성하고자 하는 목표를 이해하기 위해 간단한 시나리오를 시작점으로 삼았습니다. 1000개의 연락처가 저장된 정적 배열을 사용자에게 보여주는 기본 사례는 이해하기 쉽습니다. RecyclerView의 진정한 기능은 목록이 더 이상 정적이지 않을 때 시작됩니다. 동적 목록의 경우, 목록에 항목을 추가하거나 목록에서 항목을 제거할 때 화면의 뷰에 어떤 변화가 발생하는지 고려해야 합니다.
RecyclerView.LayoutManager
레이아웃 관리자는 뷰를 선형으로 배치할지 그리드로 배치할지 결정하는 것 외에도 내부적으로 많은 작업을 수행합니다.ping 재활용업체는 언제 재활용을 해야 하는지 알고 있습니다.
그것은 유지를 담당합니다ping trac현재 화면에 보이는 뷰의 개수를 k개 파악하고 이 정보를 재활용 메커니즘에 전달합니다. 사용자가 아래로 스크롤하면 레이아웃 관리자는 화면 상단에서 포커스를 잃는 뷰를 재활용 시스템에 알려 재사용될 수 있도록 합니다. 이렇게 하면 포커스를 잃은 뷰가 메모리를 계속 소모하거나, 새로운 뷰를 생성하기 위해 소멸되는 것을 방지할 수 있습니다.
즉, LayoutManager는 다음을 유지해야 합니다. trac사용자가 목록을 스크롤할 때 현재 위치를 k 값으로 나타냅니다. 이는 인덱스 기반 위치 목록을 사용하여 구현되며, 첫 번째 항목은 0부터 시작하여 목록의 항목 수만큼 증가합니다.
예를 들어 100개의 항목으로 이루어진 목록에서 처음에는 10개의 항목만 볼 수 있다고 가정해 보겠습니다. 그러면 LayoutManager는 처음에 View-0부터 View-9까지 모든 뷰에 포커스가 맞춰져 있음을 알 수 있습니다. 스크롤을 하면 LayoutManager는 포커스가 해제되는 뷰를 계산할 수 있습니다.
레이아웃 관리자는 이러한 뷰를 재활용 메커니즘에 전달하여 재사용할 수 있도록 합니다(새로운 데이터를 바인딩할 수 있습니다. 예를 들어, 뷰의 연락처 데이터를 제거하고 다음 세그먼트의 새 연락처 데이터로 자리 표시자를 대체할 수 있습니다).
만약 우리가 가진 목록이 고정적이라면 문제가 없겠지만, RecyclerView를 사용하는 가장 일반적인 사례 중 하나는 온라인 엔드포인트나 센서에서 데이터를 가져오는 동적 목록입니다. 데이터가 추가될 뿐만 아니라 목록에서 데이터가 삭제되거나 업데이트되기도 합니다.
데이터의 동적인 상태 때문에 레이아웃 관리자(LayoutManager)를 이해하기가 매우 어려울 수 있습니다. 이러한 이유로 레이아웃 관리자는 재활용 구성 요소가 사용하는 목록과는 별도로 항목과 위치에 대한 자체 목록을 유지합니다. 이를 통해 레이아웃 관리자가 레이아웃 작업을 정확하게 수행할 수 있습니다.
동시에, RecyclerView의 LayoutManager는 보유한 데이터를 잘못 표시하지 않도록 주의해야 합니다. 올바르게 작동하기 위해 LayoutManager는 정해진 간격(60ms)으로 RecyclerView.Adapter와 동기화하여 목록 항목에 대한 정보, 즉 항목이 추가, 업데이트, 삭제되거나 위치가 변경된 정보를 공유합니다. LayoutManager는 이 정보를 수신하면 필요에 따라 화면의 내용을 재구성하여 변경 사항을 반영합니다.
RecyclerView와 관련된 핵심 작업의 상당 부분은 RecyclerView.LayoutManager와 RecyclerView.Adapter 간의 통신을 중심으로 이루어지며, 이 두 객체는 때로는 정적이거나 때로는 동적인 데이터 목록을 저장합니다.
또한, RecyclerView는 RecyclerView.Adapter가 목록(예: 연락처)의 콘텐츠를 ViewHolder에 바인딩하여 화면에 정보를 표시하는 데 사용될 때 발생하는 onBindViewHolder와 같은 이벤트를 수신할 수 있는 메서드를 제공합니다.
또 다른 하나는 onCreateViewHolder인데, 이는 RecyclerView.Adapter가 OneContactView와 같은 일반 View를 가져와 RecyclerView에서 사용할 수 있는 ViewHolder 항목으로 변환하는 시점을 알려줍니다.
RecyclerView는 재활용을 가능하게 하는 핵심 메커니즘 외에도 재활용에 영향을 주지 않고 동작을 사용자 지정할 수 있는 방법을 제공합니다. 뷰 재사용으로 인해 onClick 이벤트에 반응하는 것과 같이 정적 뷰에서 익숙하게 하던 일반적인 작업을 수행하기가 어려워집니다.
아시다시피, RecyclerView.LayoutManager 사용자에게 뷰를 제공하는 뷰는 잠시 동안 뷰와 다른 항목 목록을 가질 수 있습니다. RecyclerView.Adapter 데이터베이스에 목록이 저장되어 있거나 소스에서 스트리밍되는 경우입니다. View에 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 필드를 추가했습니다. 이 필드는 기본적으로 생성되지 않습니다. 따라서 TextView에 바인딩될 데이터와 일치하도록 fruitName이라는 id를 지정했습니다.
기본 레이아웃에 RecyclerView 추가
같은 액티비티에는 기본적으로 생성된 main_layout.xml 레이아웃 파일이 있습니다. 만약 빈 프로젝트를 선택했다면 다른 파일이 생성되었을 것입니다. XML ConstraintLayout 안에 "Hello" 텍스트가 있는 TextView가 있는 레이아웃을 만들겠습니다. 모든 내용을 삭제하고 아래와 같이 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" />
또한 RecyclerView에 id 속성을 추가했으며, 이 속성을 사용하여 코드에서 참조할 것입니다.
android:id="@+id/fruitRecyclerView"
이제 MainActivity 파일로 돌아가겠습니다. 생성한 ID를 사용하여 방금 생성한 뷰들을 참조할 수 있습니다. 먼저, `findViewById()` 메서드를 사용하여 RecyclerView를 참조해 보겠습니다. Android이 작업은 onCreate() 메서드에서 수행할 것입니다. onCreate() 메서드는 다음과 같습니다.
override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) var myFruitRecyclerView: RecyclerView = findViewById(R.id.fruitRecyclerView) }
뷰홀더 생성
다음으로, 뷰를 뷰홀더로 변환하는 역할을 하는 RecyclerView.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) }
RecyclerViewAdapter 만들기
다음으로, RecyclerView.Adapter 클래스를 상속하는 FruitArrayAdapter 클래스를 생성하겠습니다. 이 FruitArrayAdapter는 다음과 같은 역할을 담당합니다. 과일 배열에서 과일 이름을 가져와 one_fruit_view.xml 파일을 사용하여 ViewHolder를 생성하고, 해당 ViewHolder에 과일 이름을 바인딩한 다음, 생성된 View에 과일 이름을 동적으로 바인딩합니다.
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() 메서드부터 시작하겠습니다. 배열의 size 속성을 호출하여 배열에 있는 항목 수를 가져오는 방법을 알고 있습니다.
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)
이를 위해서는 우리가 one_fruit_view.xml이라는 XML 파일로 생성한 fruitView가 필요합니다. 다음과 같이 이 XML 파일에 대한 참조를 생성하고 이를 뷰로 변환할 수 있습니다.
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) { } }
남은 부분은 onBindViewHolder 오버라이드입니다.
fun onBindViewHolder(holder: FruitViewHolder, position: Int)
RecyclerView.Adapter는 정수 위치 값을 인수로 받는데, 이 값을 사용하여 목록에서 항목을 가져옵니다. 또한, 가져온 fruitArray의 항목을 뷰 홀더에 바인딩할 수 있도록 뷰 홀더를 제공합니다. 뷰 홀더에 있는 뷰는 ViewHolder.itemView 필드를 통해 접근할 수 있습니다. 뷰를 가져온 후에는 이전에 생성한 fruitName ID를 사용하여 콘텐츠를 설정할 수 있습니다.
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의 나머지 부분을 연결할 준비가 되었습니다. 여기에는 목록의 내용을 선형 방식으로 표시할지, 아니면 GridLayoutManager나 StaggeredGridLayoutManager를 사용하여 그리드 방식으로 표시할지를 RecyclerView에 알려주는 LayoutManager를 생성하는 작업이 포함됩니다.
레이아웃 관리자 생성
이제 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) } } }




