Jetpack系列:Paging組件幫你解決分頁載入實現的痛苦

来源:https://www.cnblogs.com/danvie/archive/2019/10/10/11646325.html
-Advertisement-
Play Games

相信很多小伙伴們在項目實戰中,經常會用到界面的 、`載入更多`等功能。需要針對具體功能做針對性開發和調試,耗時耗力。 Paging組件的使用將這部分的工作簡化,從而讓開發者更專註於業務的具體實現。下麵我們一起來學習下Paging組件的使用方法。 首先來看下使用Paging組件實現的分頁載入和刷新效果 ...


相信很多小伙伴們在項目實戰中,經常會用到界面的分頁顯示載入更多等功能。需要針對具體功能做針對性開發和調試,耗時耗力。

Paging組件的使用將這部分的工作簡化,從而讓開發者更專註於業務的具體實現。下麵我們一起來學習下Paging組件的使用方法。


首先來看下使用Paging組件實現的分頁載入和刷新效果:




資料庫讀取分頁載入






網路端分頁請求數據


下麵我們針對這兩個使用Paging組件的例子進行分析。

  • 資料庫讀取分頁載入示例中,數據一次性獲取完成,界面分頁顯示,按需載入數據,減少了記憶體資源的使用
  • 網路端分頁請求數據,每次請求固定長度的數據信息進行顯示,減少網路帶寬的使用

Paging功能的實現用到了Room組件,Room也是Jetpack庫的一部分,在SQLite上提供了一個抽象層,為開發者提供了流暢的SQLite資料庫訪問體驗。

Room簡介

Room組件包含三個主要組成部分:

  • 資料庫

    其應該滿足四個條件:

    1. 含有@Database註解
    2. 是一個繼承自RoomDatabase的抽象類
    3. 註解內包含實體的列表信息
    4. 包含一個返回帶@Dao註解類的無參方法
  • 數據實體

    表示資料庫中表

  • DAO

    包含用於訪問資料庫的方法

應用程式使用Room組件獲取與資料庫關聯的數據訪問對象或DAO,然後獲取實體,將實體的所有更改同步到資料庫。Room三個部分之間的關係如下圖:

Room架構圖



Room架構圖(引自官方文檔)


Paging的基本使用方法

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進行數據獲取和顯示前,需要做幾點準備工作:

  1. 創建數據實體類Cheese
  2. 創建資料庫方法DAO
  3. 創建資料庫CheeseDb
  4. 創建自定義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功能
    ...
}

好了,大功告成!

最終效果



你也可以嘗試使用僅網路或網路+資料庫的方式進行功能開發。

源碼在此:


資料庫分頁

網路請求分頁



歡迎關註公眾號,留言討論更多技術問題


file


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 此題,竟然一時間沒想到如何合理的解決方案,主要是有較長的時間沒有使用 與`NOT IN`. 也是一個手熟的活,需要經常鍛煉,以下是解題答案: ...
  • 以前使用 ,都是局限於單個數值使用,從未嘗試過多個數據使用 . 此題涉及兩個表,肯定需要使用 操作. 此外,需要選取每個 的最大數值,那麼肯定涉及 以及 操作. 綜合以上因素,答案如下所示: ...
  • 1、增加列 2、刪除列 3、修改欄位類型 4、重命名列: 5、重命名錶: 當修改表結構時,sql server會彈出對話框,顯示以下內容: 不允許保存更改。您所做的更改要求刪除並重新創建以下表。您對無法重新創建的表進行了更改或者啟用了“阻止保存要求重新創建表的更改”選項。 解決方案:菜單欄->工具- ...
  • 1. SQL語言包括哪些類型? 數據定義:Create Table,Alter Table,Drop Table, Craete/Drop Index 數據操縱:Select ,insert,update,delete 數據控制:grant,revoke 2. 內聯接,外聯接區別? 內連接是保證兩個 ...
  • 1、如果你使用root用戶進行安裝。 vi /etc/profile 即可 系統變數 2、如果你使用普通用戶進行安裝。 vi ~/.bashrc 用戶變數 export HADOOP_HOME=/export/servers/hadoop-2.8.5 export PATH=$PATH:$HADOO ...
  • 原本是想在酷安上架的,然而審核不通過。。只能通過網頁方式宣傳了 一款使用Jsoup開源庫網路爬蟲的APP,將線上閱讀的小說解析,把小說全本下載為txt文件 由於使用爬蟲技術,所以下載的速度不是很理想,後期可能還得優化優化 下載保存的路徑: 內置sd卡/星之小說下載器 PS:最近吃土了,覺得有用的,捐 ...
  • 試圖更改私有視圖的佈局邊距時出現錯誤 解決方案: ...
  • 如何提高iOS開發技能 1、閱讀博客: "https://github.com/tangqiaoboy/iOSBlogCN" 40多位iOS開發博主的博客地址 2、讀書:每年閱讀一本高質量的iOS開發書籍 3、看WWDC視頻 4、看蘋果的官方文檔 5、看開源項目的代碼 6、多寫代碼,多思考 7、多和 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...