Android RecyclerView: คืออะไร เรียนรู้จากตัวอย่างง่ายๆ

⚡ สรุปอย่างชาญฉลาด

RecyclerView เป็นเครื่องมือขั้นสูง Android วิดเจ็ตที่แสดงชุดข้อมูลขนาดใหญ่ที่เลื่อนได้มีประสิทธิภาพโดยการใช้มุมมองจำนวนจำกัดซ้ำๆ โดยอาศัยอะแดปเตอร์ ตัวจัดการเลย์เอาต์ และตัวยึดมุมมองเพื่อผูกชุดข้อมูลที่เปลี่ยนแปลงขณะทำงาน

  • 📱 มันคืออะไร: RecyclerView เป็นตัวเลือกการแสดงข้อมูลขนาดใหญ่ที่ยืดหยุ่นกว่า ListView และ GridView
  • 🧱 มุมมองและกลุ่มมุมมอง: Android สร้างอินเทอร์เฟซโดยใช้คลาส View และ ViewGroup โดยมี LayoutParams ควบคุมขนาดและตำแหน่ง
  • 🔄 อะแดปเตอร์: รูปแบบ Adapter จะแปลงอาร์เรย์ข้อมูลให้เป็น ViewHolder ที่ RecyclerView สามารถรับได้
  • 📐 ตัวจัดการเค้าโครง: ตัวจัดการเลย์เอาต์จะจัดเรียงรายการต่างๆ ในแบบเชิงเส้นหรือแบบตาราง และส่งสัญญาณเมื่อสามารถนำมุมมองกลับมาใช้ใหม่ได้
  • ♻️ การรีไซเคิล: หน้าจอจะแสดงภาพซ้ำแทนที่จะสร้างใหม่ ซึ่งช่วยประหยัดหน่วยความจำและทำให้การเลื่อนหน้าจอราบรื่น
  • 🛠️ ขั้นตอนการสร้าง: เพิ่ม Gradle ในการสร้างเลย์เอาต์ของรายการ, ViewHolder, Adapter และ LayoutManager นั้น ต้องอาศัยการพึ่งพาซึ่งกันและกัน

Android รีไซเคิลวิว

RecyclerView คืออะไร Android?

การขอ รีไซเคิลวิว RecyclerView เป็นเวอร์ชันที่ยืดหยุ่นและทันสมัยกว่า GridView และ ListView เป็นคอนเทนเนอร์สำหรับแสดงชุดข้อมูลขนาดใหญ่ที่สามารถเลื่อนดูได้อย่างมีประสิทธิภาพโดยการรักษาจำนวนมุมมองที่จำกัด คุณสามารถใช้ Widget RecyclerView เมื่อคุณมีชุดข้อมูลที่องค์ประกอบเปลี่ยนแปลงในขณะทำงาน ขึ้นอยู่กับเหตุการณ์เครือข่ายหรือการกระทำของผู้ใช้

เข้าชม

การขอ Android แพลตฟอร์มใช้คลาส View และ ViewGroup เพื่อวาดองค์ประกอบต่างๆ บนหน้าจอ คลาสเหล่านี้เป็นคลาสพื้นฐานtracคลาส t และคลาสย่อยต่างๆ ถูกขยายไปสู่การใช้งานที่แตกต่างกันเพื่อให้เหมาะกับกรณีการใช้งาน ตัวอย่างเช่น TextView มีจุดประสงค์ง่ายๆ คือการแสดงเนื้อหาข้อความบนหน้าจอ ในขณะที่ EditText ขยายมาจากคลาส View เดียวกันและเพิ่มฟังก์ชันการทำงานเพิ่มเติมเพื่อให้ผู้ใช้สามารถป้อนข้อมูลได้

เราสามารถสร้างมุมมองแบบกำหนดเองเพื่อให้มีความยืดหยุ่นมากขึ้นในการพัฒนาได้ping ส่วนติดต่อผู้ใช้ คลาส View มีเมธอดที่เราสามารถเขียนทับเพื่อวาดลงบนหน้าจอ และเป็นวิธีการส่งพารามิเตอร์ เช่น ความกว้าง ความสูง และคุณลักษณะที่กำหนดเองที่เราต้องการเพิ่มลงใน View เพื่อให้มันทำงานได้ตามที่เราต้องการ

ดูกลุ่ม

คลาส ViewGroup เป็นคลาสประเภท View แต่แตกต่างจากคลาส View ทั่วไปที่มีหน้าที่เพียงแค่แสดงผล ViewGroup ช่วยให้เราสามารถรวม View หลายๆ ตัวไว้ใน View เดียว ซึ่งเราสามารถอ้างอิงถึง View นั้นได้โดยรวม ในกรณีนี้ View ที่สร้างขึ้นในระดับบนสุดที่เราเพิ่ม View อื่นๆ เข้าไปเรียกว่า "Parent" และ View ที่เพิ่มเข้าไปข้างในเรียกว่า "Children"

เราสามารถจินตนาการว่า View เป็น Array และ ViewGroup เป็น Array ของ Array ได้ เนื่องจาก Array ของ Array ก็คือ Array นั่นเอง เราจึงเห็นได้ว่า ViewGroup สามารถถูกมองว่าเป็นเหมือน View ได้อย่างไร

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

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

นอกจากนี้ ViewGroup ยังช่วยให้เรากำหนดวิธีการจัดเรียงองค์ประกอบย่อยภายในวิวได้ เช่น จะจัดเรียงในแนวตั้งหรือแนวนอน เราสามารถกำหนดกฎการโต้ตอบที่แตกต่างกันภายในวิวได้ ตัวอย่างเช่น TextView ที่อยู่ติดกันควรมีระยะห่าง 12dp ในขณะที่ ImageView ที่ตามด้วย TextView ควรมีระยะห่าง 5dp

กรณีนี้จะเป็นเช่นนั้นหากเรากำลังพัฒนาping เราสร้าง ViewGroup ของเราเองตั้งแต่เริ่มต้น เพื่อให้การตั้งค่าเหล่านี้ง่ายขึ้น Android จัดเตรียมคลาสที่เรียกว่า LayoutParams ซึ่งเราสามารถใช้เพื่ออินพุตการกำหนดค่าเหล่านี้

การขอ Android เอกสาร มีการกำหนดพารามิเตอร์เริ่มต้นบางอย่างที่เราจะนำไปใช้เมื่อกำหนดค่า ViewGroup ของเราเอง พารามิเตอร์ทั่วไปบางอย่างเกี่ยวข้องกับความกว้าง ความสูง และระยะขอบ โดยค่าเริ่มต้น การกำหนดค่าเหล่านี้จะมีโครงสร้างเป็น android:layout_height สำหรับความสูง และ android:layout_width สำหรับความกว้าง ในส่วนนี้ เมื่อคุณสร้าง ViewGroup คุณสามารถสร้าง LayoutParams เพิ่มเติมที่เฉพาะเจาะจงกับวิธีที่คุณต้องการให้ ViewGroup ของคุณทำงานได้

Android มาพร้อมกับ Views และ ViewGroups เริ่มต้นที่เราสามารถใช้เพื่อทำภารกิจทั่วไปหลายอย่างได้ ตัวอย่างหนึ่งที่เราได้กล่าวถึงคือ TextView ซึ่งเป็น View ที่เรียบง่าย มีคุณสมบัติที่สามารถกำหนดค่าได้ เช่น ความสูง ความกว้าง และขนาดตัวอักษร นอกจากนี้เรายังมี ImageView สำหรับแสดงรูปภาพ และ EditText ดังที่เราได้กล่าวไปแล้ว รวมถึง View อื่นๆ อีกมากมาย Android ยังมี ViewGroups แบบกำหนดเองที่เราสามารถเพิ่ม Views ของเราและรับลักษณะการทำงานที่คาดหวังได้

เลย์เอาต์เชิงเส้น

LinearLayout ช่วยให้เราสามารถเพิ่ม View เข้าไปได้ LinearLayout มีแอตทริบิวต์ orientation ที่กำหนดวิธีการจัดวางบนหน้าจอ นอกจากนี้ยังมี LinearLayout.LayoutParams ที่กำหนดกฎสำหรับ View ภายใน ตัวอย่างเช่น แอตทริบิวต์ android:center_horizontal จะจัดวาง View ให้อยู่ตรงกลางตามแนวนอน ในขณะที่ android:center_vertical จะจัดวางเนื้อหาของ View ให้อยู่ตรงกลางตามแนวตั้ง

ต่อไปนี้เป็นภาพประกอบเพื่อช่วยให้เข้าใจการจัดกึ่งกลาง เราสมมติว่านี่เป็นเพียง TextView ธรรมดาภายในพื้นที่ขนาด 200 พิกเซล x 200 พิกเซล คุณสมบัติการจัดกึ่งกลางจะทำให้มันทำงานดังนี้

แอนดรอยด์:กึ่งกลางแนวนอน

เนื้อหาจัดกึ่งกลางแนวนอน

เนื้อหาจัดกึ่งกลางแนวนอน

แอนดรอยด์:ศูนย์กลางแนวตั้ง

เนื้อหาจัดกึ่งกลางแนวตั้ง

เนื้อหาจัดกึ่งกลางแนวตั้ง

แอนดรอยด์:ศูนย์กลาง

เนื้อหาเป็นศูนย์กลาง

เนื้อหาเป็นศูนย์กลาง

ส่วนประกอบหลักของ RecyclerView

ส่วนประกอบหลักของ RecyclerView

ส่วนประกอบหลักของ RecyclerView

ต่อไปนี้เป็นส่วนประกอบสำคัญของ RecyclerView:

RecyclerView.อะแดปเตอร์

รากฐาน #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()

การพยายามเรียกใช้เมธอด charge() บน myBritishDevice ด้วย americanChargerIFound จะไม่ทำงาน เนื่องจาก AmericanDevice รองรับเฉพาะ AmericanCharger เท่านั้น ดังนั้นจึงเป็นไปไม่ได้ที่จะทำเช่นนี้:

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

ในสถานการณ์นี้ อะแดปเตอร์ที่เราสร้างขึ้น AmericanToBritishChargerAdapter จะมีประโยชน์อย่างมาก เราสามารถใช้เมธอด returnNewCharger() เพื่อสร้าง BritishCharger ใหม่ ซึ่งเราสามารถใช้ในการชาร์จได้ สิ่งที่เราต้องทำก็คือสร้างอินสแตนซ์ของอะแดปเตอร์ของเราและป้อน AmericanCharger ที่เรามีเข้าไป จากนั้นมันจะสร้าง BritishCharger ที่เราสามารถใช้งานได้

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

RecyclerView.LayoutManager

เมื่อเราใช้งาน ViewGroup เราจะมี View ต่างๆ วางอยู่ภายในนั้น โดย LayoutManager มีหน้าที่ในการกำหนดวิธีการจัดวาง View เหล่านั้นภายใน ViewGroup

เพื่อเป็นการเปรียบเทียบ เมื่อทำงานกับ LinearLayout ViewGroup กรณีการใช้งานที่เราต้องการคือความสามารถในการวางรายการต่างๆ ในแนวตั้งหรือแนวนอน ซึ่งทำได้ง่ายๆ โดยการเพิ่มแอตทริบิวต์ orientation ซึ่งจะบอกเราว่า LinearLayout จะถูกวางบนหน้าจออย่างไร เราสามารถทำได้โดยใช้ android:orientation=VERTICAL|HORIZONTAL คุณลักษณะ

นอกจากนี้เรายังมี ViewGroup อีกประเภทหนึ่งชื่อ GridLayout ซึ่งใช้เมื่อเราต้องการจัดวาง View ในโครงสร้างตารางสี่เหลี่ยม เหตุผลอาจเช่น เพื่อให้ผู้ใช้แอปเข้าใจข้อมูลได้ง่ายขึ้น โดยปกติแล้ว GridLayout จะอนุญาตให้กำหนดค่าต่างๆ เพื่อให้บรรลุเป้าหมายนี้ โดยการกำหนดขนาดของตาราง เช่น เราสามารถใช้ตารางขนาด 4x4 หรือ 3x2 ได้

RecyclerView.ViewHolder

ViewHolder คือ ABStracคลาส ViewHolder เป็นคลาสที่เราสืบทอดมาจาก RecyclerView เช่นกัน โดย ViewHolder จะมีเมธอดทั่วไปที่ช่วยให้เราอ้างอิงถึง View ที่เราวางไว้บน RecyclerView ได้ แม้ว่ากลไกการรีไซเคิลใน RecyclerView จะเปลี่ยนแปลงการอ้างอิงต่างๆ ที่เราไม่ทราบก็ตาม

รายการขนาดใหญ่

RecyclerViews ถูกใช้เมื่อเราต้องการแสดงมุมมองจำนวนมากให้ผู้ใช้เห็น โดยไม่ทำให้หน่วยความจำเต็ม แรม บนอุปกรณ์ของเราสำหรับแต่ละอินสแตนซ์ของมุมมองที่สร้างขึ้น

ถ้าเรายกตัวอย่างรายชื่อผู้ติดต่อ เราจะพอนึกภาพออกว่าผู้ติดต่อแต่ละคนจะปรากฏในรายชื่ออย่างไร จากนั้นเราจะสร้างเทมเพลตเลย์เอาต์ ซึ่งก็คือวิว (View) ที่มีช่องสำหรับใส่ข้อมูลต่างๆ จากรายชื่อผู้ติดต่อของเรา โค้ดจำลองต่อไปนี้จะอธิบายจุดประสงค์ทั้งหมด:

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

จากนั้นเราจะมีรายชื่อผู้ติดต่อในลักษณะนี้:

<ContactList>
</ContactList>

หากเรากำหนดเนื้อหาแบบตายตัว เราจะไม่มีวิธีเพิ่มเนื้อหาใหม่ลงในรายการโดยไม่ต้องเขียนแอปใหม่ทั้งหมด โชคดีที่การเพิ่ม View ลงใน ViewGroup นั้นได้รับการสนับสนุนโดย addView(view:View) วิธีการนี้ไม่ใช่วิธีที่ RecyclerView เพิ่มวิวลูกเข้าไป

ในกรณีการใช้งานของเรา เราจะมีรายชื่อผู้ติดต่อจำนวนมาก สำหรับผู้ติดต่อแต่ละรายในรายการ เราจะต้องสร้าง OneContactView และใส่ข้อมูลลงใน View นั้นให้ตรงกับฟิลด์ในคลาส Contact ของเรา จากนั้น เมื่อเราได้ View แล้ว เราจะต้องเพิ่ม View นั้นลงใน RecyclerView เพื่อแสดงรายการ

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

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

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

เรามีอาร์เรย์ของรายชื่อผู้ติดต่อ OneContactView มีช่องสำหรับดึงเนื้อหาจากคลาส Contact และแสดงผล ส่วนใน RecyclerView เราต้องเพิ่ม Views เข้าไปเพื่อให้สามารถใช้งานฟังก์ชันรีไซเคิลได้

RecyclerView ไม่ได้อนุญาตให้เราเพิ่มวิวโดยตรง แต่ช่วยให้เราเพิ่ม ViewHolder ได้ ดังนั้น ในสถานการณ์นี้ เรามีสองส่วนที่เราต้องการเชื่อมต่อ แต่ไม่ตรงกัน นี่คือจุดที่ Adapter ของเราเข้ามามีบทบาท RecyclerView มี Adapter ให้เราใช้ คล้ายกับ Adapter ของเรา AmericanToBritishChargerAdapter() จากข้อมูลก่อนหน้านี้ที่ทำให้เราสามารถแปลง AmericanCharger ของเรา ซึ่งใช้ไม่ได้กับอุปกรณ์ของอังกฤษ ให้กลายเป็นสิ่งที่ใช้งานได้ คล้ายกับอะแดปเตอร์แปลงไฟในชีวิตจริง

ในสถานการณ์นี้ อะแดปเตอร์จะรับอาร์เรย์ของรายชื่อผู้ติดต่อและมุมมองของเรา จากนั้นจะสร้าง ViewHolder ที่ RecyclerView ยอมรับได้

RecyclerView มีอินเทอร์เฟซที่เราสามารถขยายเพื่อสร้างอะแดปเตอร์ของเราเองได้ผ่านคลาส RecyclerView.Adapter ภายในอะแดปเตอร์นี้จะมีวิธีการสร้างคลาส ViewHolder ที่ RecyclerView ต้องการใช้งาน ดังนั้น สิ่งที่เรามีก็คือสถานการณ์เดียวกันกับก่อนหน้านี้ แต่มีสิ่งเพิ่มเติมเข้ามาอีกอย่างหนึ่ง นั่นก็คืออะแดปเตอร์

เรามีอาร์เรย์ของรายชื่อผู้ติดต่อ มุมมองสำหรับแสดงรายชื่อผู้ติดต่อหนึ่งราย (OneContactView) และ RecyclerView ซึ่งเป็นรายการของมุมมองที่ให้บริการการรีไซเคิล แต่รับเฉพาะ ViewHolder เท่านั้น ในสถานการณ์นี้ เรามีคลาส RecyclerView.Adapter ซึ่งมีเมธอดสำหรับสร้าง ViewHolder อยู่ภายใน

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

RecyclerView.ViewHolder เป็น abstracคลาสที่รับ View ของเราเป็นอาร์กิวเมนต์และแปลงเป็น ViewHolder โดยใช้รูปแบบ Wrapper ซึ่งเป็นรูปแบบที่ใช้ในการขยายความสามารถของคลาสต่างๆ

รากฐาน #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 ชั้นเรียน

ในกรณีข้างต้น เราใช้คลาสที่เป็นรูปธรรม แต่ก็สามารถใช้งานในลักษณะเดียวกันได้โดยใช้คลาสแบบสัมบูรณ์เช่นกันtracคลาส t เราจะต้องเปลี่ยนคลาส SpeechPoweredAnimalByWrapper เป็น abstracและเราก็เสร็จแล้ว เราจะเปลี่ยนชื่อคลาสให้สั้นลงเพื่อให้ง่ายต่อการอ่าน

abstract class SpeechPowered(var myAnimal:Animal){

    fun sound(){
        myAnimal.sound()
    }

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

มันเหมือนเดิม แต่มีความหมายต่างออกไป ในคลาสปกติ เราสามารถสร้างอินสแตนซ์ของคลาสได้ในลักษณะเดียวกับที่เราสร้าง cat1 และ dog1 Abstracอย่างไรก็ตาม คลาส t ไม่ได้มีไว้สำหรับการสร้างอินสแตนซ์ แต่มีไว้สำหรับการขยายคลาสอื่น ดังนั้นเราจะใช้ SpeechPowered(var myAnimal:Animal) abs ใหม่ได้อย่างไรtracเราสามารถใช้คลาส t ได้โดยการสร้างคลาสใหม่ที่จะสืบทอดมาจากคลาส t และในทางกลับกันก็จะได้รับฟังก์ชันการทำงานของคลาส 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 เป็นคลาสแบบ abstracคลาส t ที่เพิ่มฟังก์ชันการทำงานให้กับ View คล้ายกับที่เราเพิ่มเมธอด speak ให้กับสัตว์ต่างๆ ฟังก์ชันการทำงานที่เพิ่มเข้ามานี้เองที่ทำให้มันทำงานได้เมื่อใช้งานร่วมกับ RecyclerView

นี่คือวิธีการสร้าง OneContactViewHolder จาก OneContactView:

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

RecyclerView มีอะแดปเตอร์ที่ช่วยให้เราเชื่อมต่ออาร์เรย์รายชื่อผู้ติดต่อเข้ากับ ContactsView ภายใน RecyclerView ได้

การเพิ่มมุมมอง

ViewGroup จะไม่ทำการวาดใหม่โดยอัตโนมัติ แต่จะทำตามกำหนดเวลาที่แน่นอน อาจจะเป็นกรณีที่อุปกรณ์ของคุณทำการวาดใหม่ทุกๆ 10 มิลลิวินาที หรือ 100 มิลลิวินาที หรือถ้าเราเลือกตัวเลขที่ดูไม่สมเหตุสมผล เช่น 1 นาที เมื่อเราเพิ่ม View ลงใน ViewGroup คุณจะเห็นการเปลี่ยนแปลงในอีก 1 นาทีต่อมาเมื่อ ViewGroup “รีเฟรช”

RecyclerView.Recycler

การเตรียมการขั้นพื้นฐาน #3 – การแคชข้อมูล

หนึ่งในตัวอย่างที่ดีที่สุดของการรีเฟรชหน้าเว็บเป็นประจำคือในเบราว์เซอร์ ลองนึกภาพว่าเว็บไซต์ที่เรากำลังเยี่ยมชมเป็นเว็บไซต์แบบคงที่และไม่ได้ส่งเนื้อหาแบบไดนามิก เราจะต้องรีเฟรชหน้าเว็บอยู่เรื่อยๆ เพื่อดูการเปลี่ยนแปลง

ในตัวอย่างนี้ สมมติว่าเว็บไซต์ที่เรากำลังพูดถึงคือ Twitter เราจะมีทวีตคงที่แสดงอยู่ และวิธีเดียวที่เราจะเห็นทวีตใหม่ได้ก็คือการคลิกปุ่มรีเฟรชเพื่อดึงข้อมูลมาใหม่

การแสดงผลหน้าจอใหม่ทั้งหมดนั้นย่อมมีค่าใช้จ่ายสูง ลองนึกภาพว่าถ้าเรามีแบนด์วิดท์จำกัดกับผู้ให้บริการโทรศัพท์ และรายการทวีตของเรามีรูปภาพและวิดีโอจำนวนมาก การดาวน์โหลดเนื้อหาทั้งหมดของหน้าเว็บใหม่ทุกครั้งที่รีเฟรชหน้าจะเป็นเรื่องที่สิ้นเปลืองมาก

เราจำเป็นต้องมีวิธีในการจัดเก็บทวีตที่โหลดไว้แล้ว และตรวจสอบให้แน่ใจว่าคำขอครั้งต่อไปสามารถระบุได้ว่ามีทวีตใดอยู่แล้วบ้าง ดังนั้นจึงไม่จำเป็นต้องดาวน์โหลดทุกอย่างใหม่ แต่จะดึงเฉพาะทวีตใหม่เท่านั้น และยังตรวจสอบด้วยว่าทวีตใดที่เคยบันทึกไว้ในเครื่องนั้นหายไปแล้วหรือไม่ เพื่อที่จะลบออกจากเครื่อง สิ่งที่เรากำลังอธิบายอยู่นี้เรียกว่า การแคช

ข้อมูลที่เราส่งไปยังเว็บไซต์เกี่ยวกับเนื้อหาที่เรามีเรียกว่าเมตาเดตา ดังนั้นในความหมายที่แท้จริง เราไม่ได้แค่บอกว่า "เราต้องการโหลดเว็บไซต์ของคุณ" แต่เราบอกว่า "เราต้องการโหลดเว็บไซต์ของคุณ และนี่คือเนื้อหาบางส่วนที่เราบันทึกไว้แล้วจากการโหลดครั้งล่าสุด โปรดใช้ข้อมูลนี้เพื่อส่งเฉพาะสิ่งที่ไม่ปรากฏอยู่ในนั้น เพื่อที่เราจะได้ไม่ใช้แบนด์วิดท์มากเกินไป"

การโทรแบบเลย์เอาต์ – รายการทวีตต้องบ้าแน่ๆ

ตัวอย่างหนึ่งของการเรียกใช้ฟังก์ชันจัดวางเลย์เอาต์คือ scrollToPosition นี่เป็นตัวอย่างที่พบได้ทั่วไปในแอปพลิเคชันแชท หากมีคนตอบกลับข้อความก่อนหน้าในแชท แอปแชทบางแอปจะแสดงข้อความตอบกลับและลิงก์ไปยังข้อความนั้น ซึ่งเมื่อคลิกแล้วจะนำคุณไปยังตำแหน่งที่ข้อความเดิมของคุณอยู่

ในกรณีที่เราเรียกเมธอดนี้ก่อนที่จะเพิ่ม LayoutManager ลงใน RecyclerView และก่อนที่จะมี RecyclerView.Adapter เมธอด scrollToPosition(n:Int) จะถูกละเลยไป

การสื่อสารระหว่างส่วนประกอบ RecyclerView

การเตรียมการขั้นพื้นฐาน #4 – การโทรกลับ

ในการทำงาน RecyclerView นั้นมีส่วนประกอบหลายอย่างที่ทำงานร่วมกัน มันต้องจัดการกับ LayoutManager ซึ่งบอกเราว่าจะจัดเรียง View อย่างไร ไม่ว่าจะเป็นแบบเส้นตรงหรือแบบตาราง นอกจากนี้ยังต้องจัดการกับ Adapter ที่ทำหน้าที่แปลงรายการ (contactList) ของเราให้เป็น View (OneContactView) และจากนั้นเป็น ViewHolder (OneContactViewHolder) ที่ RecyclerView สามารถใช้งานได้โดยใช้เมธอดต่างๆ ที่มันมีให้

วัตถุดิบหลักสำหรับ RecyclerView คือ Views ของเรา เช่น OneContactView และแหล่งข้อมูล

contactList:Array<Contact>

เราใช้สถานการณ์ง่ายๆ เป็นจุดเริ่มต้นเพื่อให้เข้าใจว่า RecyclerView พยายามทำอะไร กรณีพื้นฐานที่เรามีอาร์เรย์คงที่ของรายชื่อติดต่อ 1000 รายการที่เราต้องการแสดงให้ผู้ใช้เห็นนั้นเข้าใจง่าย กลไกของ RecyclerView จะเริ่มทำงานอย่างแท้จริงเมื่อรายการไม่คงที่อีกต่อไป สำหรับรายการแบบไดนามิก เราต้องคิดถึงสิ่งที่เกิดขึ้นกับมุมมองบนหน้าจอเมื่อเราเพิ่มรายการลงในรายการหรือลบรายการออกจากรายการ

RecyclerView.LayoutManager

นอกจากจะตัดสินใจว่ามุมมองของเราจะถูกจัดวางอย่างไร ไม่ว่าจะเป็นแบบเส้นตรงหรือแบบตารางแล้ว LayoutManager ยังทำงานเบื้องหลังอีกมากมายเพื่อช่วยเราping ผู้รีไซเคิลรู้ว่าควรเริ่มรีไซเคิลเมื่อใด

มีหน้าที่รับผิดชอบในการรักษาping tracLayoutManager จะแจ้งข้อมูลของ Views ที่มองเห็นได้บนหน้าจอไปยังกลไกการรีไซเคิล เมื่อผู้ใช้เลื่อนหน้าจอลง LayoutManager จะมีหน้าที่แจ้งระบบรีไซเคิลเกี่ยวกับ Views ที่หมดโฟกัสที่ด้านบน เพื่อให้สามารถนำกลับมาใช้ใหม่ได้ แทนที่จะปล่อยทิ้งไว้และเปลืองหน่วยความจำ หรือถูกทำลายเพื่อสร้าง Views ใหม่ขึ้นมาแทน

ซึ่งหมายความว่า LayoutManager จำเป็นต้องเก็บรักษาข้อมูลไว้ track คือค่าที่ใช้ระบุตำแหน่งของผู้ใช้ขณะเลื่อนดูรายการ โดยจะทำเช่นนี้โดยใช้รายการตำแหน่งที่อิงตามดัชนี กล่าวคือ รายการแรกเริ่มต้นจาก 0 และเพิ่มขึ้นเรื่อยๆ จนตรงกับจำนวนรายการในรายการของเรา

ถ้าเราสามารถดู 10 รายการจากรายการทั้งหมด 100 รายการได้ ในตอนเริ่มต้น LayoutManager จะรับรู้ว่า View-0 ถึง View-9 อยู่ในโฟกัส เมื่อเราเลื่อนหน้าจอ LayoutManager จะสามารถคำนวณได้ว่าวิวใดบ้างที่หลุดจากโฟกัส

LayoutManager สามารถส่งมุมมองเหล่านี้ไปยังกลไกการรีไซเคิลเพื่อให้สามารถนำกลับมาใช้ใหม่ได้ (สามารถผูกข้อมูลใหม่เข้ากับมุมมองเหล่านั้นได้ เช่น สามารถลบข้อมูลผู้ติดต่อของมุมมองหนึ่งออก และใช้ข้อมูลผู้ติดต่อใหม่จากกลุ่มถัดไปมาแทนที่ในตำแหน่งที่ว่างอยู่)

กรณีนี้จะเป็นเรื่องดีหากรายการที่เรามีนั้นเป็นแบบคงที่ แต่หนึ่งในกรณีการใช้งานที่พบบ่อยที่สุดของการใช้ RecyclerView คือการใช้กับรายการแบบไดนามิก ซึ่งข้อมูลอาจมาจากแหล่งข้อมูลออนไลน์หรืออาจมาจากเซ็นเซอร์ด้วยซ้ำ ไม่เพียงแต่มีการเพิ่มข้อมูลเท่านั้น แต่บางครั้งข้อมูลในรายการของเราก็ถูกลบหรืออัปเดตด้วย

สถานะข้อมูลที่มีการเปลี่ยนแปลงอยู่ตลอดเวลาอาจทำให้การทำความเข้าใจ LayoutManager เป็นเรื่องยาก ด้วยเหตุนี้ LayoutManager จึงเก็บรักษาลิสต์ของตัวเองเกี่ยวกับรายการและตำแหน่งต่างๆ แยกต่างหากจากลิสต์ที่คอมโพเนนต์การรีไซเคิลใช้ วิธีนี้ช่วยให้มั่นใจได้ว่า LayoutManager ทำงานได้อย่างถูกต้อง

ในขณะเดียวกัน LayoutManager ของ RecyclerView ก็ไม่ต้องการบิดเบือนข้อมูลที่มีอยู่ เพื่อให้ทำงานได้อย่างถูกต้อง LayoutManager จะซิงโครไนซ์กับ RecyclerView.Adapter เป็นระยะๆ (60 มิลลิวินาที) โดยแชร์ข้อมูลเกี่ยวกับรายการในลิสต์ เช่น รายการที่เพิ่ม อัปเดต ลบ หรือย้ายจากตำแหน่งหนึ่งไปยังอีกตำแหน่งหนึ่ง เมื่อได้รับข้อมูลนี้แล้ว LayoutManager จะจัดเรียงเนื้อหาบนหน้าจอใหม่ให้ตรงกับการเปลี่ยนแปลงเมื่อจำเป็น

การทำงานหลักหลายอย่างที่เกี่ยวข้องกับ RecyclerView นั้นเกี่ยวข้องกับการสื่อสารระหว่าง RecyclerView.LayoutManager และ RecyclerView.Adapter ซึ่งเป็นตัวจัดเก็บรายการข้อมูลของเรา ซึ่งบางครั้งอาจเป็นข้อมูลคงที่ บางครั้งอาจเป็นข้อมูลเปลี่ยนแปลงได้

นอกจากนี้ RecyclerView ยังมีเมธอดที่เราสามารถใช้เพื่อรับฟังเหตุการณ์ต่างๆ เช่น onBindViewHolder เมื่อ RecyclerView.Adapter ผูกเนื้อหาจากรายการของเรา (เช่น รายชื่อติดต่อ) กับ ViewHolder เพื่อให้ข้อมูลนั้นถูกนำไปใช้แสดงบนหน้าจอ

อีกฟังก์ชันหนึ่งคือ onCreateViewHolder ซึ่งจะบอกเราว่าเมื่อใดที่ RecyclerView.Adapter รับ View ทั่วไป เช่น OneContactView และแปลงให้เป็นรายการ ViewHolder ที่ RecyclerView สามารถใช้งานได้

นอกเหนือจากกลไกหลักที่ช่วยให้สามารถนำวิวกลับมาใช้ใหม่ได้แล้ว RecyclerView ยังมีวิธีการปรับแต่งพฤติกรรมโดยไม่ส่งผลกระทบต่อการนำวิวกลับมาใช้ใหม่ การนำวิวกลับมาใช้ใหม่ทำให้การทำสิ่งต่างๆ ที่เราคุ้นเคยกับการใช้วิวแบบคงที่ เช่น การตอบสนองต่อเหตุการณ์ onClick ทำได้ยากขึ้น

อย่างที่เราทราบกันดีว่า RecyclerView.LayoutManager ที่นำเสนอมุมมองให้กับผู้ใช้อาจมีรายการที่แตกต่างไปจาก RecyclerView.Adapter ที่เก็บรายชื่อไว้ในฐานข้อมูลหรือดึงข้อมูลมาจากแหล่งที่มาโดยตรง การใส่เหตุการณ์ OnClick ลงใน View โดยตรงอาจนำไปสู่พฤติกรรมที่ไม่คาดคิด เช่น การลบรายชื่อผิดคน

Gradle

ถ้าเราต้องการใช้ RecyclerView เราต้องเพิ่มมันเป็น dependency ในไฟล์ build.gradle ของเรา ในตัวอย่างต่อไปนี้ เราใช้ implementation “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 ในการทำเช่นนั้น เราจะไปยังไดเร็กทอรี layout ที่เก็บ layout ของเรา และสร้าง View ที่จะทำหน้าที่แสดงผลไม้หนึ่งชนิด

เค้าโครงที่จะใช้สำหรับแต่ละรายการในรายการของเรา

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

ใน TextView ด้านบน เราได้เพิ่มฟิลด์ id ซึ่งจะใช้สำหรับระบุ View โดยฟิลด์นี้ไม่ได้ถูกสร้างขึ้นโดยอัตโนมัติ เราได้กำหนด id ให้กับ TextView ของเราเป็น fruitName เพื่อให้ตรงกับข้อมูลที่จะผูกกับมัน

การเพิ่ม RecyclerView ให้กับเลย์เอาต์หลัก

ในกิจกรรมเดียวกันนี้ มีไฟล์เลย์เอาต์ main_layout.xml ที่ถูกสร้างขึ้นให้เราโดยค่าเริ่มต้น หากเราเลือกโปรเจ็กต์ว่างเปล่า ระบบจะสร้างไฟล์ดังกล่าวขึ้นมา XML เดิมทีเลย์เอาต์นี้จะมี ConstraintLayout และภายในจะมี TextView ที่แสดงข้อความ “Hello” เราจะลบเนื้อหาทั้งหมดออก และให้เลย์เอาต์เหลือเพียง RecyclerView ดังภาพด้านล่าง:

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

เราได้เพิ่มแอตทริบิวต์ id ให้กับ RecyclerView แล้ว ซึ่งเราจะใช้เพื่ออ้างอิงถึงมันในโค้ดของเรา

android:id="@+id/fruitRecyclerView"

จากนั้นเราจะกลับไปยังไฟล์ MainActivity ของเรา โดยใช้ ID ที่เราสร้างขึ้น เราจะสามารถอ้างอิงถึง View ที่เราเพิ่งสร้างได้ เราจะเริ่มต้นด้วยการอ้างอิงถึง RecyclerView โดยใช้เมธอด findViewById() ที่มีให้ Androidเราจะทำสิ่งนี้ในเมธอด onCreate() ของเรา เมธอด onCreate() ของเราจะมีลักษณะดังนี้

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

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

สร้าง ViewHolder

ต่อไป เราจะสร้าง RecyclerView.ViewHolder ซึ่งมีหน้าที่รับ View ของเราและแปลงให้เป็น ViewHolder ซึ่ง RecyclerView จะใช้ในการแสดงรายการต่างๆ เราจะทำขั้นตอนนี้หลังจากเมธอด onCreate() ทันที

package com.guru99.learnrecycler

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

class MainActivity : AppCompatActivity() {

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

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

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

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

}

สร้าง RecyclerViewAdapter

ต่อไป เราจะสร้างคลาส FruitArrayAdapter ที่สืบทอดมาจากคลาส RecyclerView.Adapter FruitArrayAdapter ที่เราสร้างขึ้นนี้จะมีหน้าที่ดังต่อไปนี้: มันจะรับชื่อผลไม้จากอาร์เรย์ผลไม้ สร้าง ViewHolder โดยใช้ไฟล์วิว one_fruit_view.xml ของเรา จากนั้นผูกผลไม้เข้ากับ ViewHolder และผูกเนื้อหาเข้ากับวิวที่เราสร้างขึ้นแบบไดนามิก

package com.guru99.learnrecycler

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

class MainActivity : AppCompatActivity() {

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

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

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

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

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

Android Studio จะแสดงเส้นหยักสีแดงบน FruitArrayAdapter ของเรา ซึ่งบอกเราว่าเราจำเป็นต้องใช้งานเมธอดที่ RecyclerView สามารถใช้เพื่อเชื่อมต่ออาร์เรย์ของเรากับ ViewHolder ได้

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

     }

     override fun getItemCount(): Int {

     }

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

     }
 }

เราจะเริ่มจากส่วนที่ง่ายที่สุดจากโค้ดที่สร้างขึ้น นั่นคือเมธอด getItemCount() เราทราบวิธีการนับจำนวนรายการในอาร์เรย์ของเราโดยการเรียกใช้คุณสมบัติขนาดของอาร์เรย์แล้ว

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

        }

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

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

        }
    }

จากนั้นเราจะทำการเขียนโค้ดสำหรับเมธอด onCreateViewHolder ตรงนี้ RecyclerView จะขอให้เราช่วยสร้าง FruitViewHolder หากเราจำได้ นี่คือหน้าตาของคลาส FruitViewHolder ของเรา:

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

จำเป็นต้องใช้ fruitView ของเรา ซึ่งเราสร้างไว้ในไฟล์ XML ชื่อ one_fruit_view.xml เราสามารถสร้างการอ้างอิงไปยังไฟล์ XML นี้และแปลงเป็น View ได้ดังนี้

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

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

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

            var fruitViewHolder = FruitViewHolder(fruitView)

            return fruitViewHolder

        }

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

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

        }
    }

ส่วนที่เหลือคือการเขียนทับเมธอด onBindViewHolder

fun onBindViewHolder(holder: FruitViewHolder, position: Int)

RecyclerView.Adapter จะขอค่าจำนวนเต็มตำแหน่ง ซึ่งเราใช้ในการดึงรายการจากลิสต์ของเรา นอกจากนี้ยังให้ตัวเก็บข้อมูล (holder) เพื่อให้เราสามารถผูกรายการที่เราได้จาก fruitArray กับ View ที่อยู่ในตัวเก็บข้อมูลนั้นได้ View ที่อยู่ใน ViewHolder สามารถเข้าถึงได้ผ่านฟิลด์ ViewHolder.itemView เมื่อเราได้ View แล้ว เราสามารถใช้ id fruitName ที่เราสร้างไว้ก่อนหน้านี้เพื่อกำหนดเนื้อหาได้

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

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

          var aFruitName = fruitArray.get(position)

          ourFruitTextView.setText(aFruitName)
      }

ด้วยเหตุนี้ FruitArrayAdapter ของเราจึงเสร็จสมบูรณ์และมีลักษณะดังต่อไปนี้

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

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

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

            var fruitViewHolder = FruitViewHolder(fruitView)

            return fruitViewHolder
        }

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

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

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

            var aFruitName = fruitArray.get(position)

            ourFruitTextView.setText(aFruitName)
        }
    }

สุดท้ายนี้ เราพร้อมที่จะเชื่อมต่อส่วนที่เหลือของ RecyclerView แล้ว ซึ่งก็คือการสร้าง LayoutManager ที่จะบอก RecyclerView ว่าจะแสดงเนื้อหาของรายการอย่างไร ไม่ว่าจะเป็นการแสดงแบบเชิงเส้นโดยใช้ LinearLayoutManager หรือแบบตารางโดยใช้ GridLayoutManager หรือ StaggeredGridLayoutManager

สร้างตัวจัดการเค้าโครง

เราจะกลับเข้าไปในฟังก์ชัน onCreate และเพิ่ม LayoutManager เข้าไป

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

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

       var fruitLinearLayout = LinearLayoutManager(this)

       myFruitRecyclerView.layoutManager =fruitLinearLayout

   }

เชื่อมต่ออะแดปเตอร์ของเรากับรายการต่างๆ และตั้งค่าบน RecyclerView

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

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

        var fruitLinearLayout = LinearLayoutManager(this)

        myFruitRecyclerView.layoutManager =fruitLinearLayout

        var fruitListAdapter = FruitListAdapter(fruitNames)

        myFruitRecyclerView.adapter =fruitListAdapter
    }

เราได้สร้างอินสแตนซ์ของ fruitListAdapter และป้อนอาร์เรย์ของชื่อผลไม้เข้าไปแล้ว และโดยพื้นฐานแล้วเราก็ทำเสร็จหมดแล้ว ไฟล์ MainActivity.kt ฉบับสมบูรณ์มีลักษณะดังต่อไปนี้

package com.guru99.learnrecycler

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

class MainActivity : AppCompatActivity() {

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

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

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

        var fruitLinearLayout = LinearLayoutManager(this)

        myFruitRecyclerView.layoutManager =fruitLinearLayout

        var fruitListAdapter = FruitListAdapter(fruitNames)

        myFruitRecyclerView.adapter =fruitListAdapter
    }

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

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

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

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

            var fruitViewHolder = FruitViewHolder(fruitView)

            return fruitViewHolder
        }

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

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

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

            var aFruitName = fruitArray.get(position)

            ourFruitTextView.setText(aFruitName)
        }
    }
}

ดาวน์โหลดโครงการ

คำถามที่พบบ่อย

RecyclerView เป็นตัวเลือกที่ดีกว่าและมีประสิทธิภาพกว่า ListView มันใช้รูปแบบ ViewHolder รองรับการจัดวางแบบเส้นตรง ตาราง และแบบสลับซับซ้อนผ่านตัวจัดการการจัดวาง และเพิ่มแอนิเมชันของรายการในตัว ในขณะที่ ListView ไม่มีคุณสมบัติเหล่านี้โดยค่าเริ่มต้น

GridView แสดงรายการได้เฉพาะในตารางคงที่เท่านั้น ในขณะที่ RecyclerView สามารถสร้างตารางได้โดยใช้ GridLayoutManager แต่ยังรองรับการจัดวางแบบเส้นตรงและแบบสลับ การรีไซเคิลมุมมอง และแอนิเมชัน ทำให้มีความหลากหลายในการใช้งานมากกว่า GridView มาก

ตัวอย่าง RecyclerView นี้เขียนด้วยภาษา Kotlin ซึ่งเป็นภาษาที่ทางหน่วยงานแนะนำอย่างเป็นทางการ Google สำหรับ Android การพัฒนา API เดียวกันของ RecyclerView, adapter และ layout manager ก็ใช้งานได้ใน Java.

เพิ่มตัวฟังการคลิกเข้าไปใน ViewHolder หรือ onBindViewHolder จากนั้นเปิดเผยตัวฟังการคลิกนั้นผ่านทางฟังก์ชันเรียกกลับของอินเทอร์เฟซไปยัง Activity หรือ Fragment การใช้ตำแหน่งของ ViewHolder จะช่วยหลีกเลี่ยงการผูกการคลิกกับรายการที่นำกลับมาใช้ใหม่ที่ไม่ถูกต้อง

DiffUtil เป็นคลาสยูทิลิตี้ที่คำนวณความแตกต่างระหว่างลิสต์สองรายการและส่งชุดการอัปเดตขั้นต่ำไปยังอะแดปเตอร์ ทำให้การเปลี่ยนแปลงลิสต์แบบไดนามิกมีประสิทธิภาพและสร้างภาพเคลื่อนไหวของการเพิ่ม การลบ และการย้ายโดยอัตโนมัติ

Jetpack Compose ใช้ LazyColumn และ LazyRow แทน RecyclerView สำหรับหน้าจอใหม่ อย่างไรก็ตาม RecyclerView ยังคงถูกใช้งานอย่างแพร่หลายในโปรเจ็กต์ที่ใช้ View เป็นหลัก และสามารถฝังลงใน Compose ได้ผ่าน API สำหรับการทำงานร่วมกัน

ใช่แล้ว ผู้ช่วย AI เช่น GitHub Copilot และเครื่องมือที่ใช้ GPT สามารถสร้างโครงสร้างพื้นฐานของ RecyclerView adapter, ViewHolder และ onBindViewHolder ได้จากข้อความแจ้งสั้นๆ นักพัฒนาควรตรวจสอบความถูกต้องและประสิทธิภาพของโค้ดที่สร้างขึ้นอีกครั้ง

การเรียนรู้เครื่อง Android แอปต่างๆ ใช้ RecyclerView เพื่อแสดงผลลัพธ์แบบไดนามิก เช่น คำแนะนำ การจัดอันดับการค้นหา หรือการจัดประเภทภาพ อะแดปเตอร์จะเชื่อมโยงผลลัพธ์ของโมเดลเข้ากับรายการในลิสต์ เพื่อให้ชุดข้อมูลขนาดใหญ่ที่เปลี่ยนแปลงบ่อยสามารถเลื่อนได้อย่างราบรื่น

สรุปโพสต์นี้ด้วย: