相信很多小伙伴們在專案實戰中,經常會用到界面的分頁顯示、加載更多等功能,需要針對具體功能做針對性開發和除錯,耗時耗力,
Paging組件的使用將這部分的作業簡化,從而讓開發者更專注于業務的具體實作,下面我們一起來學習下Paging組件的使用方法,
首先來看下使用Paging組件實作的分頁加載和重繪效果:
下面我們針對這兩個使用Paging組件的例子進行分析,
- 資料庫讀取分頁加載示例中,資料一次性獲取完成,界面分頁顯示,按需加載資料,減少了記憶體資源的使用
- 網路端分頁請求資料,每次請求固定長度的資料資訊進行顯示,減少網路帶寬的使用
Paging功能的實作用到了Room組件,Room也是Jetpack庫的一部分,在SQLite上提供了一個抽象層,為開發者提供了流暢的SQLite資料庫訪問體驗,
Room簡介
Room組件包含三個主要組成部分:
- 資料庫
其應該滿足四個條件:
- 含有@Database注解
- 是一個繼承自RoomDatabase的抽象類
- 注解內包含物體的串列資訊
- 包含一個回傳帶@Dao注解類的無參方法
- 資料物體
表示資料庫中表
- DAO
包含用于訪問資料庫的方法
應用程式使用Room組件獲取與資料庫關聯的資料訪問物件或DAO,然后獲取物體,將物體的所有更改同步到資料庫,Room三個部分之間的關系如下圖:
Paging的基本使用方法
Paging組件支持三種不同資料結構:
- 僅從網路獲取
- 僅從設備資料庫獲取
- 兩種資料來源的組合,使用設備資料庫作為快取
下面我們以僅從設備資料庫獲取的方式來了解下Paging分頁的基本使用方法,
環境配置
首先需要在模塊build.gradle中添加對應庫支持,
dependencies {
versions.room = "2.1.0-alpha06"
versions.lifecycle = "2.2.0-alpha03"
versions.paging = "2.1.0-rc01"
//room資料庫訪問依賴
implementation "androidx.room:room-runtime:$versions.room"
//lifecycle組件依賴,ViewModel
implementation "androidx.lifecycle:lifecycle-runtime:$versions.lifecycle"
//paging組件依賴
implementation "androidx.paging:paging-runtime-ktx:$versions.paging"
kapt "androidx.room:room-compiler:$versions.room"
}
布局檔案
界面的布局比較簡單,主界面包含一個輸入框,一個按鈕和一個RecyclerView,串列每一項的顯示采用卡片式布局,顯示文本,
<androidx.cardview.widget.CardView ...>
<TextView android:id="@+id/name" .../>
</androidx.cardview.widget.CardView>
資料準備
在主Activity進行資料獲取和顯示前,需要做幾點準備作業:
- 創建資料物體類Cheese
- 創建資料庫方法DAO
- 創建資料庫CheeseDb
- 創建自定義CheeseViewModel
1. 創建物體Cheese
物體代表了資料庫每條資料物件,需要注意必須加@Entity注解
@Entity
data class Cheese(@PrimaryKey(autoGenerate = true) val id: Int, val name: String)
此宣告創建了一個資料庫物體,欄位有ID和Name,主鍵為ID
2. 創建資料庫操作方法DAO
資料庫方法提供了對資料庫的基本操作,必須加@Dao注解
@Dao
interface CheeseDao {
@Query("SELECT * FROM Cheese ORDER BY name COLLATE NOCASE ASC")
fun allCheesesByName(): DataSource.Factory<Int, Cheese>
@Insert
fun insert(cheeses: List<Cheese>)
@Insert
fun insert(cheese: Cheese)
@Delete
fun delete(cheese: Cheese)
}
此處提供了針對資料庫的查詢,插入和洗掉方法,可以看到在查詢方法里面會指定資料源型別,當前使用默認型別,Paging還支持如下三種資料源:
- PageKeyedDataSource
實作按上下頁加載顯示
- ItemKeyedDataSource
根據上一條資料獲取下一條資料
- PositionalDataSource
從指定位置開始加載
關于這三種資料源的高級使用方法,請參考官方檔案說明和示例:
3. 創建資料庫
資料庫為界面顯示提供了資料支持,當前示例程式中,資料庫創建時,插入了預置資料,
-
必須加@Database注解
-
必須宣告資料串列資訊
-
必須含有無參抽象方法,回傳帶@Dao注解的類
-
必須為抽象類,且繼承RoomDatabase
@Database(entities = arrayOf(Cheese::class), version = 1)
abstract class CheeseDb : RoomDatabase() {
abstract fun cheeseDao(): CheeseDao//回傳DAO
...
//獲取資料庫實體,同步且單例
@Synchronized
fun get(context: Context): CheeseDb {
if (instance == null) {
instance = Room.databaseBuilder(context.applicationContext,
CheeseDb::class.java, "CheeseDatabase")
.addCallback(object : RoomDatabase.Callback() {
override fun onCreate(db: SupportSQLiteDatabase) {
//資料庫創建時插入預置資料
fillInDb(context.applicationContext)
}
}).build()
}
return instance!!
}
private fun fillInDb(context: Context) {
// inserts in Room are executed on the current thread, so we insert in the background
// CHEESE_DATA為默認資料串列
ioThread {
get(context).cheeseDao().insert(
CHEESE_DATA.map { Cheese(id = 0, name = it) })
}
}
}
4. 創建ViewModel
創建自定義ViewModel為界面和資料提供處理支持,其包含了DAO,資料串列資訊等,
class CheeseViewModel(app: Application) : AndroidViewModel(app) {
val dao = CheeseDb.get(app).cheeseDao()
val allCheeses = dao.allCheesesByName().toLiveData(Config(
pageSize = 30,//指定頁面顯示的資料項數量
enablePlaceholders = true,//是否允許使用占位符
maxSize = 200 //一次性加載資料的最大數量
),
fetchExecutor = Executor { }//自定義Executor更好地控制paging庫何時從應用程式的資料庫中加載串列
)
fun insert(text: CharSequence) = ioThread {
dao.insert(Cheese(id = 0, name = text.toString()))
}
fun remove(cheese: Cheese) = ioThread {
dao.delete(cheese)
}
}
小提示:自定義ViewModel直接繼承AndroidViewModel,可以在其中做一些依賴于Context的資源獲取等功能,
public class AndroidViewModel extends ViewModel {
...
public <T extends Application> T getApplication() {
return (T) mApplication;
}
}
ViewModel的創建,包含了資料的獲取和更新:
- 通過DAO獲取資料庫的資料串列
- 使用LiveData組件管理資料
- 增加分頁支持(pageSize,enablePlaceholders,maxSize)功能
- 增加自定義Executor
Paging組件是依賴頁面長度、占位符、最大長度三個屬性來進行小塊資料加載顯示的,
頁面大小:每頁顯示的物體數量
最大長度:也稱預取長度,此值應為pageSize的幾倍大小(具體專案可根據實際情況除錯)
占位符:如果設定為true,則為尚未完成加載的串列項顯示占位符
占位符的使用需要有可數的資料集合,默認顯示效果,資料項有相同大小的視圖顯示,有以下優點:
- 提供完整滾動條支持
- 無需顯示加載更多項
界面系結
資料已經準備好了,下面開始和界面進行系結顯示,
界面顯示時,需要提供與RecyclerView系結的adapter,需要注意使用Paging進行分頁加載,adapter需要繼承自PagedListAdapter,
class CheeseAdapter : PagedListAdapter<Cheese, CheeseViewHolder>(diffCallback) {
override fun onBindViewHolder(holder: CheeseViewHolder, position: Int) {
holder.bindTo(getItem(position))
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): CheeseViewHolder =
CheeseViewHolder(parent)
companion object {
//根據diffCallback來確認新加載的資料是否與舊資料有差異,確定是否更新顯示
private val diffCallback = object : DiffUtil.ItemCallback<Cheese>() {
override fun areItemsTheSame(oldItem: Cheese, newItem: Cheese): Boolean =
oldItem.id == newItem.id
//kotlin使用==會將物件的內容進行對比,使用java需要重寫equals方法并替換
override fun areContentsTheSame(oldItem: Cheese, newItem: Cheese): Boolean =
oldItem == newItem
}
}
}
//ViewHolder的實作比較簡單,將Cheese資料更新到TextView
class CheeseViewHolder(parent :ViewGroup) : RecyclerView.ViewHolder(
LayoutInflater.from(parent.context).inflate(R.layout.cheese_item, parent, false)) {
private val nameView = itemView.findViewById<TextView>(R.id.name)
var cheese : Cheese? = null
//未系結資料,或者打開占位符后快速滑動會出現cheese為null,實際專案中需要
//處理此種情況,資料加載時會重新rebind
fun bindTo(cheese : Cheese?) {
this.cheese = cheese
nameView.text = cheese?.name
}
}
class MainActivity : AppCompatActivity() {
private val viewModel by viewModels<CheeseViewModel>()//創建viewModel
override fun onCreate(savedInstanceState: Bundle?) {
...
val adapter = CheeseAdapter()//繼承PagedListAdapter的類物件
cheeseList.adapter = adapter //為RecyclerView添加配接器
//viewmodel資料與adapter系結,在資料變化時通知adapter更新UI
viewModel.allCheeses.observe(this, Observer(adapter::submitList))
initSwipeToDelete()//設定左滑/右滑洗掉資料項
initAddButtonListener()//設定點擊添加Cheese功能
...
}
好了,大功告成!

你也可以嘗試使用僅網路或網路+資料庫的方式進行功能開發,
原始碼在此:
資料庫分頁
網路請求分頁
轉載請註明出處,本文鏈接:https://www.uj5u.com/yidong/57460.html
標籤:Android
上一篇:Android JSON決議插件
下一篇:星之小說下載器Android版
