Android RecyclerView: 간단한 예를 통해 알아보기

RecyclerView란 무엇인가요? Android?

The 리사이클러뷰 GridView와 ListView의 더욱 유연하고 고급 버전인 위젯입니다. 제한된 뷰 수를 유지하여 효율적으로 스크롤할 수 있는 대규모 데이터 세트를 표시하기 위한 컨테이너입니다. 네트워크 이벤트나 사용자 작업에 따라 런타임 시 요소가 변경되는 데이터 컬렉션이 있는 경우 RecyclerView 위젯을 사용할 수 있습니다.

조회수

The Android 플랫폼은 View 및 ViewGroup 클래스를 사용하여 화면에 항목을 그립니다. 이러한 클래스는 추상적이며 사용 사례에 맞게 다양한 구현으로 확장됩니다. 예를 들어 TextView는 화면에 텍스트 내용을 표시하는 간단한 목적을 가지고 있습니다. EditText는 동일한 View 클래스에서 확장되어 사용자가 데이터를 입력할 수 있도록 더 많은 기능을 추가합니다.

사용자 인터페이스를 개발할 때 더 많은 유연성을 얻을 수 있도록 자체 사용자 정의 보기를 만드는 것이 가능합니다. View 클래스는 화면에 그리기 위해 재정의할 수 있는 메서드와 너비, 높이 및 뷰가 원하는 대로 작동하도록 뷰에 추가할 사용자 정의 속성과 같은 매개변수를 전달하는 수단을 제공합니다.

보기그룹

ViewGroup 클래스는 일종의 View이지만 단순히 표시만 담당하는 단순한 View 클래스와 달리 ViewGroup은 여러 뷰를 하나의 뷰에 넣어 전체적으로 참조할 수 있는 기능을 제공합니다. 이 경우 다른 간단한 뷰(viewGroups도 추가할 수 있음)를 추가하는 최상위 수준에서 생성된 뷰를 "부모"라고 하며 내부에 추가된 뷰는 "자식"입니다.

View를 배열로 이미지화하고 ViewGroup을 배열 배열로 이미지화할 수 있습니다. 배열의 배열이 배열 자체라는 점을 고려하면 ViewGroup이 어떻게 뷰처럼 처리될 수 있는지 알 수 있습니다.

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을 사용하면 뷰 내부에서 자식을 구성하는 방법, 예를 들어 수직 또는 수평으로 배치하는 방법을 정의할 수도 있습니다. View 내부에서 상호 작용에 대한 다른 규칙을 가질 수 있습니다. 예를 들어, 서로 이어지는 TextView는 12dp 거리를 가져야 하고 TextView 뒤에 이어지는 ImageView는 5dp 거리를 가져야 합니다.

자체 ViewGroup을 처음부터 개발하는 경우가 이에 해당합니다. 이러한 구성을 더 쉽게 만들려면 Android 이러한 구성을 입력하는 데 사용할 수 있는 LayoutParams라는 클래스를 제공합니다.

Android 선적 서류 비치 는 자체 ViewGroup을 구성할 때 구현할 몇 가지 기본 매개변수를 제공합니다. 몇 가지 일반적인 매개변수는 너비, 높이 및 여백과 관련된 것입니다. 기본적으로 이러한 구성은 높이에 대한 android:layout_height 구조를 갖습니다(예: android:layout_width). 이와 관련하여 ViewGroup을 만들 때 ViewGroup이 동작하기를 원하는 방식에 따라 LayoutParams를 추가로 만들 수 있습니다.

Android 우리가 필요로 하는 많은 일반적인 작업을 수행하는 데 사용할 수 있는 기본 보기 및 보기 그룹이 함께 제공됩니다. 우리가 언급한 한 가지 예는 TextView입니다. 이는 높이, 너비, textSize 등과 같은 구성 가능한 측면이 제공되는 간단한 보기입니다. 우리는 앞서 언급한 것처럼 Images와 EditText를 표시하기 위한 ImageView를 가지고 있습니다. Android 또한 뷰를 추가하고 예상되는 동작을 얻을 수 있는 사용자 정의 ViewGroup도 있습니다.

선형 레이아웃

LinearLayout을 사용하면 View 항목을 추가할 수 있습니다. LinearLayout은 화면에 배치되는 방식을 지시하는 방향 속성입니다. 또한 내부 뷰에 대한 규칙을 지시하는 LinearLayout.LayoutParams가 있습니다. 예를 들어, android:center_horizontal 속성은 수평 축을 따라 뷰를 중앙에 배치하고, `android:center_vertical`은 수직 축을 따라 뷰의 콘텐츠를 중앙에 배치합니다.

다음은 센터링에 대한 이해를 돕기 위한 몇 가지 이미지입니다. 우리는 이것을 200px x 200px 공간 내의 간단한 TextView로 간주하고 센터링 속성을 사용하면 다음과 같이 작동합니다.

안드로이드:center_horizontal

가로 중앙 정렬 콘텐츠
가로 중심 콘텐츠

안드로이드:center_vertical

세로로 가운데 정렬된 콘텐츠
세로로 가운데 정렬된 콘텐츠

안드로이드:센터

중심 콘텐츠
중앙 정렬된 콘텐츠

RecyclerView의 핵심 구성요소

RecyclerView의 핵심 구성요소
RecyclerView의 핵심 구성요소

RecyclerView의 중요한 구성 요소는 다음과 같습니다.

RecyclerView.Adapter

기초 작업 #1 - 어댑터 패턴

어댑터는 시스템 또는 장치의 속성을 그렇지 않으면 호환되지 않는 장치 또는 시스템의 속성으로 변환하는 장치입니다. 일부는 신호 또는 전원 속성을 수정하는 반면, 다른 일부는 단순히 한 커넥터의 물리적 형태를 다른 커넥터에 맞게 조정합니다.

어댑터를 설명하기 위해 실생활에서 찾을 수 있는 간단한 예는 장치를 함께 연결해야 하지만 장치의 연결 포트가 서로 일치하지 않는 경우입니다. 다양한 종류의 소켓을 사용하는 다른 국가를 방문하는 경우가 이에 해당할 수 있습니다. 휴대폰이나 노트북 충전기를 가지고 다니면 전원 콘센트에 연결할 수 없습니다. 하지만 포기하지 말고 전원 콘센트와 충전기 사이에 끼워 충전할 수 있는 어댑터를 구입하기만 하면 됩니다.

작업을 수행하기 위해 두 개의 데이터 구조를 함께 연결하려는 프로그래밍의 경우이지만 기본 포트에는 서로 통신할 수 있는 방법이 없습니다.

우리는 장치와 충전기의 간단한 예를 사용하겠습니다. 충전기 인스턴스가 두 개 있습니다. 미국거랑 영국거

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

그런 다음 두 개의 장치를 만듭니다.

class AmericanDevice() 
class BritishDevice()

예를 들어, 함께 플레이할 장치의 일부 인스턴스를 생성할 수 있습니다.

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

그런 다음 충전()이라는 장치에 메서드를 추가하여 두 장치 모두에 대한 충전 개념을 소개하겠습니다.

이 메서드는 해당 충전기를 입력으로 받아들이고 이를 기반으로 충전을 수행합니다.

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
    }
}

이 경우 비유에 따르면 어떤 이유로든 AmericanDevice를 사용할 때 BritishCharger를 사용해야 하거나 그 반대의 경우도 마찬가지입니다.

프로그래밍 세계에서는 일반적으로 동일한 기능을 제공하는 라이브러리를 함께 혼합할 때 발생합니다(우리의 맥락에서 공유 기능은 충전입니다). 우리는 이것을 가능하게 하는 방법을 찾아야 합니다.

비유를 따른다면, 우리는 전자 제품 가게에 가서 어댑터를 사야 할 것입니다. 어댑터를 사면 British Chargers를 통해 AmericanDevices를 충전할 수 있습니다. 프로그래밍 관점에서 보면, 어댑터를 제조하는 사람은 우리입니다.

다른 하나를 생성하는 데 필요한 정확한 패턴과 일치하는 어댑터를 만들 것입니다. 다음과 같이 클래스로 구현하겠습니다. 반드시 클래스일 필요는 없으며 어댑터 패턴이 일반적으로 수행하는 작업을 강조하는 함수일 수 있습니다. 우리는 가장 많이 사용되는 클래스를 사용할 것입니다. 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
    }
}

프로그래밍 세계에서 소켓의 차이는 내부 충전 방법의 차이와 유사합니다. 충전기의 방식이 다르기 때문에 충전기를 사용할 수 없습니다.

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

AmericanChargerIound를 사용하여 myBritishDevice에서 Charge() 메서드를 호출하려고 하면 AmericanDevice가 AmericanCharger만 허용하므로 작동하지 않습니다.

그러니 이런 일은 불가능하다

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

이 시나리오에서는 우리가 만든 어댑터가

이제 AmericanToBritishChargerAdapter가 유용하게 사용될 수 있습니다. returnNewCharger() 메서드를 사용하여 요금을 청구하는 데 사용할 수 있는 새로운 BritishCharger를 생성할 수 있습니다. 우리에게 필요한 것은 어댑터의 인스턴스를 생성하고 이를 우리가 가지고 있는 AmericanCharger로 공급하는 것뿐입니다. 그러면 우리가 사용할 수 있는 BritishCharger가 생성됩니다.

var myBritishDevice = new BritishDevice()
var americanChargerIFound = new AmericanCharger()
//We create the adapter and feed it the americanCharger
var myAdapter =  AmericanToBritishChargerAdapter(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

ViewGroup을 다룰 때 우리는 그 안에 View를 배치하게 됩니다. LayoutManager는 뷰가 내부에 어떻게 배치되는지 설명하는 작업을 수행합니다.

비교 목적으로, Linearlayout ViewGroup으로 작업할 때 우리가 원하는 사용 사례는 항목을 수직 또는 수평으로 배치하는 기능입니다. 이는 선형 레이아웃이 화면에 배치되는 방법을 알려주는 방향 속성을 추가하여 쉽게 구현됩니다. 우리는 다음을 사용하여 이를 수행할 수 있습니다. android:orientation=VERTICAL|HORIZONTAL 속성을 사용하지 않는 것입니다.

GridLayout이라는 또 다른 ViewGroup도 있는데, 이 사용 사례는 직사각형 그리드 구조에 뷰를 배치하려는 경우입니다. 이는 우리가 앱 사용자에게 제공하는 데이터를 쉽게 사용할 수 있도록 만드는 것과 같은 이유 때문일 수 있습니다. 의도적으로 GridLayout은 그리드의 크기를 정의할 수 있는 구성을 통해 이 목표를 달성하는 데 도움이 되는 구성을 가능하게 합니다. 예를 들어 4×4 그리드, 3 x 2 그리드를 가질 수 있습니다.

RecyclerView.ViewHolder

ViewHolder는 RecyclerView에서도 확장되는 추상 클래스입니다. ViewHolder는 RecyclerView의 재활용 기계가 우리가 알지 못하는 다양한 참조를 변경한 후에도 RecyclerView에 배치한 뷰를 참조하는 데 도움이 되는 일반적인 방법을 제공합니다.

큰 목록

RecyclerViews는 사용자에게 매우 큰 뷰 세트를 제공하려고 할 때 사용됩니다. 생성된 뷰의 모든 인스턴스에 대해 우리 장치에서.

연락처 목록의 경우를 예로 들면, 목록에서 한 연락처가 어떻게 보일지에 대한 일반적인 아이디어를 얻을 수 있습니다. 그런 다음 연락처 목록의 다양한 데이터가 채워질 슬롯이 있는 템플릿 레이아웃(실제로는 뷰)을 만듭니다. 다음은 전체 목적을 설명하는 의사 코드입니다.

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

그런 다음 이러한 성격의 ContactList를 갖게 됩니다.

 <ContactList>
</ContactList>

그렇다면 콘텐츠를 하드코딩했다면 앱을 다시 작성하지 않고 목록에 새 콘텐츠를 추가하는 프로그래밍 방식이 없을 것입니다. 다행스럽게도 우리에게는. ViewGroup에 보기 추가는 다음에서 지원됩니다. addView(view:View) 방법.

그런 경우에도 RecyclerView가 하위 뷰를 추가하는 방법은 아닙니다.

우리의 사용 사례에서는 긴 연락처 목록이 있습니다. 목록의 각 연락처에 대해 OneContactView를 만들고 연락처 클래스의 필드와 일치하도록 보기 내부에 데이터를 채워야 합니다. 그런 다음 뷰가 있으면 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() 이를 통해 BritishDevice에서는 사용할 수 없었던 AmericanCharger를 실제 전원 어댑터와 유사한 사용 가능한 것으로 변환할 수 있었습니다.

이 시나리오에서 어댑터는 연락처 배열과 뷰를 가져와 거기에서 RecyclerView가 허용할 ViewHolders를 생성합니다.

RecyclerView는 RecyclerView.Adapter 클래스를 통해 어댑터를 생성하기 위해 확장할 수 있는 인터페이스를 제공합니다. 이 어댑터 내부에는 RecyclerView가 작업하려는 ViewHolder 클래스를 생성하는 방법이 있습니다. 따라서 우리가 가진 것은 이전과 동일한 상황이지만 한 가지 추가 사항이 바로 어댑터입니다.

하나의 연락처 OneContactView를 표시하는 보기인 연락처 배열이 있습니다. RecyclerView는 재활용 서비스를 제공하지만 ViewHolders만 담당할 의향이 있는 뷰 목록입니다.

하지만 이 시나리오에는 내부에 ViewHolder를 생성하는 메서드가 있는 RecyclerView.Adapter 클래스가 있습니다.

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

RecyclerView.ViewHolder는 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()
    }

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

이제 SpeechPoweredAnimalByWrapper에 동물 인스턴스를 전달할 수 있습니다. sound() 메서드를 호출하면 전달된 동물 sound() 메서드가 호출됩니다. 또한 전달된 동물에 추가하는 새로운 기능으로 간주되는 추가 talk() 메서드도 있습니다. 다음과 같이 사용할 수 있습니다.

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"

이 패턴을 사용하여 수업을 듣고 기능을 추가할 수 있습니다. 우리에게 필요한 것은 클래스 인스턴스와 래핑 클래스에 의해 정의된 새 메서드를 전달하는 것뿐입니다.

위의 사례에서 우리는 구체적 클래스를 사용했습니다. 추상 클래스에서 동일한 것을 구현하는 것도 가능합니다. SpeechPoweredAnimalByWrapper 클래스를 추상으로 변경하면 됩니다. 클래스 이름을 더 짧게 변경하여 읽기 쉽게 만들겠습니다.

abstract class SpeechPowered(var myAnimal:Animal){

    fun sound(){
        myAnimal.sound()
    }

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

이전과 동일하지만 다른 의미가 있습니다. 일반 클래스에서는 cat1 및 dog1을 만든 것과 같은 방식으로 클래스의 인스턴스를 가질 수 있습니다. 그러나 추상 클래스는 인스턴스화하기 위한 것이 아니라 다른 클래스를 확장하기 위한 것입니다. 그렇다면 새로운 SpeechPowered(var myAnimal:Animal) 추상 클래스를 어떻게 사용할까요? 이를 확장하고 기능을 얻는 새로운 클래스를 만들어 이를 사용할 수 있습니다.

이 예에서는 클래스를 확장하는 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 클래스는 동물에게 talk 메소드를 추가한 것처럼 View에 기능을 추가하는 추상 클래스입니다. 추가된 기능은 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를 사용하여 Contacts 배열을 ContactsView에 연결할 수 있는 어댑터가 있습니다.

보기 추가

ViewGroup은 ViewGroup을 자동으로 다시 그리지 않고 특정 일정을 따릅니다. 장치에서 10ms 또는 100ms마다 다시 그리거나 ViewGroup에 View를 추가할 때 1분과 같은 터무니없는 숫자를 선택하면 ViewGroup이 "새로 고침"될 때 1분 후에 변경 사항을 볼 수 있습니다.

RecyclerView.Recycler

기초 작업 #3. 캐싱

정기적으로 새로 고침을 수행하는 가장 좋은 예 중 하나는 브라우저입니다. 예를 들어, 우리가 방문하는 사이트가 정적이고 콘텐츠를 동적으로 전송하지 않는다고 가정해 보겠습니다. 변경 사항을 보려면 계속 새로 고쳐야 합니다.

이 예에서는 문제의 사이트가 트위터라고 가정해 보겠습니다. 일련의 정적 트윗이 나열되며 새 트윗을 볼 수 있는 유일한 방법은 새로 고침 버튼을 클릭하여 콘텐츠를 다시 가져오는 것입니다.

전체 화면을 다시 그리는 것은 분명히 비용이 많이 드는 일입니다. 만약 그렇다면 우리는 전화 제공업체의 대역폭이 제한되어 있었습니다. 그리고 우리의 트윗 목록에는 이미지와 비디오가 너무 많아서 새로 고칠 때마다 페이지의 모든 콘텐츠를 다시 다운로드하는 데 비용이 많이 듭니다.

이미 로드된 트윗을 저장하고 다음 요청에 이미 있는 트윗을 말할 수 있는 기능이 있는지 확인하는 방법이 필요합니다. 따라서 모든 것을 다시 다운로드하지 않고 새 트윗만 가져오고 로컬에 저장된 일부 트윗이 더 이상 존재하지 않는지 확인하여 로컬에서 삭제할 수 있습니다. 우리가 설명하는 것을 캐싱이라고 합니다.

우리가 보유한 콘텐츠에 관해 웹사이트로 보내는 정보를 메타데이터라고 합니다. 따라서 실제 의미에서 우리는 "귀하의 사이트를 로드하고 싶습니다"라고 말하는 것이 아니라 "귀하의 사이트를 로드하고 싶습니다. 여기에 마지막으로 로드할 때 이미 저장한 콘텐츠 중 일부가 있습니다. 이를 사용하여 거기에 없는 것만 보내주므로 리소스가 많지 않기 때문에 대역폭을 많이 사용하지 않습니다.”

레이아웃 호출 - 트윗 목록이 이상할 것 같아요

레이아웃 호출의 예는 scrollToPosition입니다.

이는 채팅 앱과 같은 곳에 존재하는 일반적인 예입니다. 채팅 스레드의 누군가가 이전의 채팅 풍선에 응답하는 경우 일부 채팅 앱에는 응답과 채팅 풍선에 대한 링크가 포함되어 있습니다. 이를 클릭하면 원래 메시지가 있던 위치로 이동됩니다.

이 경우 RecyclerView에 LayoutManager를 추가하기 전과 RecyclerView.Adapter를 갖기 전에 이 메서드를 호출하면 scrollToPosition(n:Int)이 무시됩니다.

RecyclerView 구성요소 간 통신

기초 작업 #4. 콜백

RecyclerView는 작업을 수행하면서 움직이는 부분이 많이 있습니다. 뷰를 선형적으로 또는 그리드로 구성하는 방법을 알려주는 LayoutManager를 처리해야 합니다. 이는 항목 contactList를 Views OneContactView로 변환한 다음 RecyclerView가 우리에게 제공하는 메서드 내에서 기꺼이 작동할 ViewHolders OneContactViewHolder로 변환하는 작업을 수행하는 어댑터를 처리해야 합니다.

RecyclerView의 원자재는 OneContactView 및 데이터 소스와 같은 뷰입니다.

contactList:Array<Contact>

RecyclerView가 달성하려는 목표를 파악하기 위한 시작점으로 간단한 시나리오를 사용했습니다.

사용자에게 보여주고 싶은 1000개의 연락처로 구성된 정적 배열이 있는 경우의 기본 사례는 이해하기 쉽습니다.

RecyclerView 기계는 목록이 더 이상 정적이 아닐 때 실제로 생명을 앗아가기 시작합니다.

동적 목록을 사용하면 목록에 항목을 추가하거나 목록에서 항목을 제거할 때 화면의 보기에 어떤 일이 발생하는지 생각해야 합니다.

RecyclerView.LayoutManager

뷰를 선형으로 또는 그리드로 배치하는 방법을 결정하는 것 외에도 LayoutManager는 Recycler가 언제 Recycling을 해야 하는지 알 수 있도록 후드 아래에서 많은 작업을 수행합니다.

현재 화면에 표시되는 뷰를 추적하고 이 정보를 재활용 메커니즘에 전달하는 역할을 담당합니다. 사용자가 아래로 스크롤하면 레이아웃 관리자는 상단에서 초점을 벗어난 뷰를 재활용 시스템에 알려서 뷰가 거기 남아 메모리를 소비하거나 파괴하고 새로 생성하는 대신 재사용될 수 있도록 하는 일을 담당합니다. 새 것.

이것이 의미하는 바는 LayoutManager가 사용자가 목록을 스크롤할 때 사용자의 위치를 ​​추적해야 한다는 것입니다. 이는 인덱스 기반인 위치 목록을 가짐으로써 이를 수행합니다. 즉, 첫 번째 항목은 0부터 시작하여 목록의 항목 수와 일치하도록 증가합니다.

10개의 목록에서 100개의 항목을 볼 수 있는 경우 처음에 LayoutManager는 해당 항목이 View-0에서 View-9까지 포커스에 있음을 인식합니다. 스크롤할 때 LayoutManager는 나가는 뷰를 계산할 수 있습니다. 초점의.

LayoutManager는 이러한 뷰를 재사용할 수 있도록 재활용 메커니즘에 해제할 수 있습니다(새 데이터를 뷰에 바인딩할 수 있습니다. 예를 들어 뷰의 연락처 데이터를 제거하고 다음 세그먼트의 새 연락처 데이터가 자리 표시자를 대체할 수 있습니다).

우리가 가지고 있는 목록이 정적이라면 이는 좋은 사례가 될 것입니다. 그러나 RecyclerView를 사용하는 가장 일반적인 사용 사례 중 하나는 데이터가 온라인 엔드포인트 또는 센서에서 올 수 있는 동적 목록을 사용하는 것입니다. 데이터가 추가될 뿐만 아니라 목록의 데이터가 때때로 제거되거나 업데이트됩니다.

데이터의 동적 상태로 인해 LayoutManager에 대해 추론하기가 매우 어려울 수 있습니다. 이러한 이유로 LayoutManager는 재활용 구성 요소가 사용하는 목록과 별도로 항목 및 위치에 대한 자체 목록을 유지 관리합니다. 이렇게 하면 레이아웃 작업을 올바르게 수행할 수 있습니다.

동시에 RecyclerView의 LayoutManager는 자신이 가지고 있는 데이터를 잘못 표현하고 싶어하지 않습니다. 올바르게 작동하기 위해 LayoutManager는 지정된 간격(60ms)으로 RecyclerView.Adapter와 동기화하여 목록 항목에 대한 정보를 공유합니다. 즉, 추가, 업데이트, 제거, 한 위치에서 다른 위치로 이동한 항목). LayoutManager는 이 정보를 수신하면 필요에 따라 변경 사항에 맞게 화면의 콘텐츠를 재구성합니다.

RecylerView를 다루는 핵심 작업의 대부분은 때로는 정적이고 때로는 동적인 데이터 목록을 저장하는 RecyclerView.LayoutManager와 RecyclerView.Adapter 간의 통신을 중심으로 이루어집니다.

게다가 RecyclerView는 RecyclerView.Adapter가 목록의 콘텐츠(예: 연락처를 ViewHolder에 바인딩할 때) onBindViewHolder와 같은 이벤트를 수신하는 데 사용할 수 있는 메서드를 제공하므로 이제 화면에 정보를 표시하는 데 사용할 수 있습니다.

또 다른 하나는 RecyclerView가 언제 우리에게 알려주는 onCreateViewHolder입니다. 어댑터가 OneContactView와 같은 일반 보기를 가져와 이를 RecyclerView와 함께 사용할 수 있는 ViewHolder 항목으로 변환합니다. RecyclerView에서 사용하기 위한 ViewHolder에서. onViewDetached

재활용을 가능하게 하는 핵심 메커니즘과는 별개입니다. RecyclerView는 재활용에 영향을 주지 않고 동작을 맞춤설정하는 방법을 제공합니다.

뷰를 재사용하면 onClick 이벤트에 반응하는 등 정적 뷰를 사용하여 수행했던 일반적인 작업을 수행하기가 어렵습니다.

우리가 알고 있듯이 RecyclerView.LayoutManager 사용자에게 뷰를 제공하는 뷰는 잠시 동안 뷰와 다른 항목 목록을 가질 수 있습니다. RecyclerView.Adapter 여기에는 데이터베이스에 저장했거나 소스에서 스트리밍한 목록이 있습니다. OnClick 이벤트를 뷰에 직접 배치하면 잘못된 연락처 삭제 또는 변경과 같은 예상치 못한 동작이 발생할 수 있습니다.

Gradle

RecyclerView를 사용하려면 빌드 .gradle 파일에 종속성으로 추가해야 합니다.

다음 예제에서는 이 문서에 따라 가장 최신 버전인 "androidx.recyclerview:recyclerview:1.1.0" 구현을 사용했습니다.

우리의 종속성을 추가한 후 Gradle 파일을 입력하면 다음과 같은 메시지가 표시됩니다. Android 스튜디오로 Sync변경 사항을 기록하고,

이것은 우리의 방법입니다 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 ID인 FruitName을 설정했습니다.

기본 레이아웃에 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.ViewHolder를 만듭니다.

우리는 재미있는 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에 바인딩하고 내용을 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는 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() 메서드부터 시작하겠습니다. 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) {
          
        }
    }

그런 다음 메소드 재정의 fun onCreateViewHolder를 구현합니다.

RecyclerView가 FruitHolder를 구성하는 데 도움을 요청하는 곳입니다.

기억해 보면 FruitViewHolder 클래스는 다음과 같았습니다.

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

XML 파일 one_fruit_view.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) {

        }
    }

나머지 비트는 재정의입니다.

fun onBindViewHolder(holder: FruitViewHolder, position: Int)

RecyclerView.Adapter는 목록에서 항목을 가져오는 데 사용할 위치 정수를 요청합니다. 또한 FruitArray에서 가져온 항목을 뷰 홀더 내부에 있는 뷰에 바인딩할 수 있도록 홀더를 제공합니다.

ViewHoder 내부에 있는 뷰는 ViewHolder.itemView 필드를 통해 액세스할 수 있습니다. 뷰를 얻은 후에는 앞서 만든 id FruitName을 사용하여 콘텐츠를 설정할 수 있습니다.

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

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

            var aFruitName = fruitArray.get(position)

            ourFruitTextView.setText(aFruitName)
        }

이로써 FruitArrayAdapter가 완성되었으며 다음과 같습니다.

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

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

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

            var fruitViewHolder = FruitViewHolder(fruitView)

            return fruitViewHolder
        }

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

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

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

            var aFruitName = fruitArray.get(position)

            ourFruitTextView.setText(aFruitName)
        }
    }

마지막으로 RecyclerView의 나머지 부분을 연결할 준비가 되었습니다. RecyclerView에 목록의 내용을 표시하는 방법을 알려주는 LayoutManager를 생성합니다. LinearLayoutManager를 사용하여 선형 방식으로 표시할지, GridLayoutManager 또는 StaggeredGridLayoutManager를 사용하여 그리드에 표시할지 여부입니다.

레이아웃 관리자 생성

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)
        }
    }
}

프로젝트 다운로드