Android Mastery by Dan Tech
Android Mastery by Dan Tech

Android View System: Kiến Thức Scrolling View

Kiến thức tiếp theo sau khi bạn đã am hiểu Android Activity Lifecycle chính là Android View System. Trong chuỗi bài viết về chủ đề này bạn sẽ học cách sử dụng các loại View thường dùng nhất trong một ứng dụng Android: ScrollView, RecyclerView, ViewPager2, LinearLayout, ConstraitLayout, CardView, …

Let’s goooo!

ScrollView

Đây là một Scrolling View cơ bản nhất trong Android. Hỗ trợ bạn Scroll content bên trong nó. Thông thường ta sẽ đặt một LinearLayout bên trong ScrollingView và tùy theo mục đích Scroll theo chiều dọc hay ngang mà sẽ set cho LinearLayout phù hợp.

<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="vertical">

        <TextView
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:text="Text 1" />

        <TextView
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:text="Text 2" />

        <!-- Add more views here as needed -->

    </LinearLayout>

</ScrollView>

ViewPager2

Tại sao lại là ViewPager2 mà không phải là ViewPager?

Trước khi có ViewPager2, thì ViewPager đã được sử dụng xuyên suốt gần 10 năm. Tuy nhiên nhiều năm gần đây ViewPager được cải tiến và tối ưu hơn. Các Lập trình viên Android của Google đã giới thiệu ViewPager2 được coi như là một nâng cấp đáng kể của ViewPager. Nâng cấp này giúp cho các Fragment bên trong ViewPager được tái sử dụng tốt hơn, đồng thời giảm hiệu ứng giật lag khi kéo, chuyển các Fragment bên trong ViewPager2 so với ViewPager.

Cách sử dụng ViewPager2 cũng hết sức đơn giản.

<androidx.viewpager2.widget.ViewPager2
    android:id="@+id/main_view_pager"
    android:layout_width="match_parent"
    android:layout_height="match_parent" />
private val viewPagerAdapter =  MainPagerAdapter(this)
fun setupViewPager2() {
	binding.mainViewPager.adapter = viewPagerAdapter
}

fun updateViewPager2(data: List<String>) {
	viewPagerAdapter.update(data)
}
class MainPagerAdapter(fragment: Fragment)
    : FragmentStateAdapter(fragment) {
    private val data: MutableList<String> = mutableListOf()
    
    fun update(list: List<String>){
	    data.clear()
	    data.addAll(list)
	    notifyDataSetChanged()
    }
    
    override fun createFragment(position: Int): Fragment {
				return Fragment.newInstance() // just an example, will show more in real use case
		}
    override fun getItemCount(): Int {
	    return data.size
    }
}

Trong thực tế ViewPager2 chúng ta rất ít khi sử dụng hàm update nhiều lần. Thường thì chỉ gọi hàm update của MainPagerAdapter 1 lần duy nhất lúc chương trình khởi chạy.

Trong bài tập thực hành chúng ta sẽ có ứng dụng demo cụ thể hơn.

RecyclerView

Đặt vấn đề

Để nói về RecyclerView có thể phải cần đền 10 bài viết mới khai thác đủ ưu điểm, nhược điểm của loại View này trong Android. Tuy nhiên đó là vấn đề nâng cao. Trong phạm vi bài viết này chúng ta sẽ tìm hiểu và nắm được công dụng của RecyclerView và cách sử dụng tối ưu loại View này trong quá trình phát triển ứng dụng.

Đặt vấn đề ứng dụng Danh Bạ Điện Thoại của bạn có 1000 SDT và bạn cần hiển thị chúng trong một ScrollView theo chiều dọc. Bạn có thể dễ dàng tạo một ScrollView và tạo 1000 Child View bên trong để đại diện cho 1000 số điện thoại. Mọi việc diễn ra suôn sẻ cho đến khi bạn chợt nhận ra 1000 Child View này chiếm quá nhiều bộ nhớ, tại mỗi thời điểm bạn chỉ có thể nhìn thấy khoảng 10 SDT tuy nhiên lại tốn chi phí Memory để lưu trữ 1000 Child View. Đây là một sự lãng phí lớn.

Giải pháp của Android RecyclerView

RecyclerView là một giải pháp cho khó khăn này. Đúng như tên gọi RecyclerView, khi được sử dụng nó sẽ tạo ra một số lượng Child View nhất định và trong quá trình bạn scroll để xem đến các Item khác, những View được kéo ra khỏi vùng nhìn thấy của User sẽ được Recycle và đưa vào sử dụng cho việc Draw các View sắp xuất hiện theo chiều Scrolling của User.

Để làm được việc này, kiến trúc của RecyclerView cần một lớp có tên là RecyclerView.Adapter . Lớp Adapter này sẽ chịu trách nhiệm quản lý các loại View nào cần tạo, và quản lý việc Recycle các Child View như thế nào cũng như tối ưu hóa quá trình Draw. Hiểu một cách đơn giản, RecyclerView.Adapter là một lớp chuyên trách cho việc quyết định View nào sẽ được Recycle, Draw lên RecyclerView trong quá trình sử dụng.

<androidx.recyclerview.widget.RecyclerView
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:id="@+id/recyclerView"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"/>
private val myAdapter by lazy { MyAdapter() }
fun setupRecyclerView() {
	binding.recyclerView.adapter = myAdapter
}
fun updateRecyclerView(dataList: List<String>) {
	myAdapter.submitList(dataList)
}
class MyAdapter() : RecyclerView.Adapter<RecyclerView.ViewHolder>() {
	private val dataList: MutableList<String> = mutableListOf()
	fun submitList(data: List<String>) {
		dataList.clear()
		dataList.addAll(data)
		notifyDataSetChanged()
	}
}

Hướng dẫn code mẫu Android RecyclerView

Ngoài cách code đơn giản trên, chúng ta có thể sử dụng một tính năng mới của RecyclerView. Adapter được gọi là ListAdapter. ListAdapter có cách quản lý riêng, giúp cho việc xử lý giữa các lần cập nhật được tối ưu hơn. Tuy nhiên nó vẫn có 1 số hạn chế so với cách truyền thống.

data class SimpleUIModel(val number: String)

class MyAdapter : ListAdapter<SimpleUIModel, RecyclerView.ViewHolder>(differCallback) {

    companion object {
        val differCallback = object : DiffUtil.ItemCallback<SimpleUIModel>() {
            override fun areItemsTheSame(oldItem: SimpleUIModel, newItem: SimpleUIModel): Boolean {
                return oldItem.id == newItem.id
            }

            override fun areContentsTheSame(oldItem: SimpleUIModel, newItem: SimpleUIModel): Boolean {
                return oldItem == newItem
            }

            override fun getChangePayload(oldItem: SimpleUIModel, newItem: SimpleUIModel): Any {
                return newItem
            }
        }
    }

    inner class SimpleViewHolder(val viewBinding: ItemSimpleViewHolderBinding) :
        RecyclerView.ViewHolder(viewBinding.root) {
        fun bind(simpleUI: SimpleUIModel) {
            log.d("NotesVH bind: $simpleUI")
        }
    }

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
        return  SimpleViewHolder(
            ItemSimpleViewHolderBinding.inflate(
                LayoutInflater.from(parent.context),
                parent, false
            )
        )
    }

    override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
        (holder as? SimpleViewHolder)?.bind(getItem(position))
    }
}

Về giải thích chi tiết các logic trong ListAdapter cũng như Adapter cơ bản sẽ có trong video khóa học với đầy đủ thông tin và ví dụ. Mình hi vọng rằng bài hướng dẫn này sẽ tạo động lực để bạn có thể tự học lập trình Android tốt hơn.