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.