Kotlin實戰案例:帶你實現RecyclerView分頁查詢功能(仿照主流電商APP,可切換列表和網格效果)

来源:https://www.cnblogs.com/qixingchao/archive/2019/12/03/11978229.html
-Advertisement-
Play Games

隨著Kotlin的推廣,一些國內公司的安卓項目開發,已經從Java完全切成Kotlin了。雖然Kotlin在各類編程語言中的排名比較靠後(據TIOBE發佈了 19 年 8 月份的編程語言排行榜,Kotlin竟然排名45位),但是作為安卓開發者,掌握該語言,卻已是大勢所趨了。 Kotlin的基礎用法, ...


演示:分頁查詢數據,切換列表樣式(列表、網格)

演示:網路異常時分頁查詢

隨著Kotlin的推廣,一些國內公司的安卓項目開發,已經從Java完全切成Kotlin了。雖然Kotlin在各類編程語言中的排名比較靠後(據TIOBE發佈了 19 年 8 月份的編程語言排行榜,Kotlin竟然排名45位),但是作為安卓開發者,掌握該語言,卻已是大勢所趨了。

Kotlin的基礎用法,整體還是比較簡單的,網上已經有很多文章了,大家熟悉下即可。

案例需求

此次案例,之所以選擇分頁列表,主要是因為該功能通用性強,涵蓋的技術點也較多,對開發者熟悉Kotlin幫助性較大。

案例的主要需求如下( 參考主流電商APP實現 ):
1、列表支持手勢滑動分頁查詢(滑動到底部時,自動查詢下一頁,直到沒有更多數據)
2、可切換列表樣式和網格樣式
3、切換樣式後,數據位置保持不變(如當前在第100條位置,切換樣式後,位置不變)
4、footview根據查詢狀態,顯示不同內容:

a、數據載入中... (正在查詢數據時顯示)
b、沒有更多數據了 (查詢成功,但是已沒有可返回的數據了)
c、出錯了,點擊重試!!(查詢時出現異常,可能是網路,也可能是其他原因)

5、當查詢出錯時,再次點擊footview,可重新發起請求(例如:網路異常了)
6、當切換網格樣式時,footview應獨占一行

設計

雖然是簡單案例,咱們開發時,也應先進行簡單的設計,讓各模塊、各類都各司其職、邏輯解耦,這樣大家學起來會更簡單一些。
此處,不畫類圖了,直接根據項目結構,簡單介紹一下吧:

1、pagedata 是指數據模塊,包含:

DataInfo 實體類,定義商品欄位屬性
DataSearch 數據訪問類,模擬子線程非同步查詢分頁數據,可將數據結果通過lambda回調回去

2、pageMangage 是指分頁管理模塊,將分頁的全部邏輯托管給該模塊處理。為了簡化分頁邏輯的實現,根據功能單一性進行了簡單拆分:

PagesManager 分頁管理類,用於統籌列表數據查詢、展示、樣式切換
PagesLayoutManager 分頁佈局管理類,用於列表樣式和網格樣式的管理、數據位置記錄
PagesDataManager 分頁數據管理類,用於分頁列表數據、footview數據的封裝

3、adapter 是指適配器模塊,主要用於定義各類適配器

PagesAdapter 分頁適配器類,用於創建、展示 itemview、footview,處理footview回調事件

4、utils 是指工具模塊,用於定義一些常用工具

AppUtils 工具類,用於判斷網路連接情況

代碼實現

在文章的最後,會將Demo源碼下載地址分享給大家,以供參考。

1、pagedata 數據模塊

1.1、DataInfo.kt 實體類

kotlin類中定義了屬性,則已包含了預設的get、set

package com.qxc.kotlinpages.pagedata

/**
 * 實體類
 *
 * @author 齊行超
 * @date 19.11.30
 */
class DataInfo {
    /**
     * 標題
     */
    var title: String = ""
    /**
     * 描述
     */
    var desc: String = ""
    /**
     * 圖片
     */
    var imageUrl: String = ""
    /**
     * 價格
     */
    var price: String = ""
    /**
     * 鏈接
     */
    var link: String = ""
}
1.2、DataSearch 數據訪問類:
package com.qxc.kotlinpages.pagedata
import com.qxc.kotlinpages.utils.AppUtils

/**
 * 數據查詢類:模擬網路請求,從伺服器資料庫獲取數據
 *
 * @author 齊行超
 * @date 19.11.30
 */
class DataSearch {
    //伺服器資料庫中的數據總數量(模擬)
    private val totalNum = 25

    //聲明回調函數(Lambda表達式參數:errorCode錯誤碼,dataInfos數據,無返回值)
    private lateinit var listener: (errorCode: Int, dataInfos: ArrayList<DataInfo>) -> Unit

    /**
     * 設置數據請求監聽器
     *
     * @param plistener 數據請求監聽器的回調類對象
     */
    fun setDataRequestListener(plistener: 
                              (errorCode: Int, dataInfos: ArrayList<DataInfo>) -> Unit) {
        this.listener = plistener
    }

    /**
     * 查詢方法(模擬從資料庫查詢)
     * positionNum: 數據起始位置
     * pageNum: 查詢數量(每頁查詢數量)
     */
    fun search(positionNum: Int, pageNum: Int) {
        //模擬網路非同步請求(子線程中,進行非同步請求)
        Thread()
        {
            //模擬網路查詢耗時
            Thread.sleep(1000)

            //獲得數據查詢結束位置
            var end: Int = if (positionNum + pageNum > totalNum) totalNum 
                           else positionNum + pageNum
            //創建集合
            var datas = ArrayList<DataInfo>()

            //判斷網路狀態
            if (!AppUtils.instance.isConnectNetWork()) {
                //回調異常結果
                this.listener(1,datas)
                //回調異常結果
                //this.listener.invoke(-1, datas)
                return@Thread
            }

            //組織數據(模擬獲取到數據)
            for (index in positionNum..end - 1) {
                var data = DataInfo()
                data.title = "Android Kotlin ${index + 1}"
                data.desc = "Kotlin 是一個用於現代多平臺應用的靜態編程語言,由 JetBrains ..."
                data.price = (100 * (index + 1)).toString()
                data.imageUrl = ""
                data.link = "跳轉至Kotlin櫃臺 -> JetBrains"
                datas.add(data)
            }

            //回調結果
            this.listener.invoke(0, datas)

        }.start()
    }
}

DataSearch類有兩個重點知識:

1.2.1、子線程非同步查詢的實現

a、參考常規分頁網路請求API,數據查詢方法應包含參數:起始位置、每頁數量
b、安卓中的網路查詢,需要在子線程中操作,當前案例直接使用Thread{}.start()實現子線程

線程實現的寫法與Java中不太一樣,為什麼這麼寫呢?咱們具體展開說明一下:
-----------------------------------Thread{}.start()-------------------------------------
通常情況下,Java中實現一個線程,可這麼寫:
new Thread(new Runnable() {
            @Override
            public void run() {

            }
        }).start();

如果完全按照Java的寫法,將其轉為Kotlin,應該這麼寫:
Thread(object: Runnable{
            override fun run() {

            }
        }).start()

但是在本案例中卻是:Thread{}.start(),並未看到Runnable對象和run方法,其實是被Lambda表達式替換了:
>>  Runnable介面有且僅有1個抽象函數run(),符合“函數式介面”定義(即:一個介面僅有一個抽象方法) 
>>  這樣的介面可以使用Lambda表達式來簡化代碼的編寫 :

使用Lambda表示Runnable介面實現,因run()無參數、無返回值,對應的Lambda實現結構應該是:
 { -> } 

當Lambda表達式無任何參數時,可以省略箭頭符號:
 { } 

我們將Lambda表達式替換Runnable介面實現,Kotlin代碼如下所示:
Thread({ }) .start()

如果Lambda表達式是函數是最後一個實參,則可以放在小括弧後面:
Thread() { }  .start()

如果Lambda是函數的唯一實參的時候,小括弧可以直接省略,也就變成了咱們案例的效果了:
Thread{ } .start()

-----------------------------------Thread{}.start()-------------------------------------

以上是線程Lambda表達式的簡化過程!!!
使用Lambda表達式,使得我們可以在 “Thread{ }” 的大括弧里直接寫子線程實現,代碼變簡單了

更多Lambda表達式介紹,請參考文章:https://www.cnblogs.com/Jetictors/p/8647888.html

1.2.2、數據回調監聽

此案例通過Lambda表達式實現了數據回調監聽(與iOS的block類似):

a、聲明Lambda表達式,用於定義回調方法結構(參數、返回值),如:
      var listener: (errorCode: Int, dataInfos: ArrayList<DataInfo>) -> Unit
      Lambda表達式可理解為一種特殊類型,即:抽象方法類型

b、由調用方傳遞過來Lambda表達式的實參對象(即:調用方已實現的Lambda表達式所表示的抽象方法)
      setDataRequestListener(plistener:....)

c、當執行完數據查詢時,將結果通過調用Lambda表達式實參對象回傳回去,如:
      this.listener(1,datas)
      this.listener.invoke(0, datas)
      這兩種調用方式都是可以的,invoke是指執行自身


當然,我們也可以在Kotlin中使用介面回調的那種方式,與Java一樣,只是代碼會繁瑣一些而已!!!

2、pageMangage 分頁管理模塊

為了簡化分頁邏輯,讓大家更好理解,此處將分頁數據、分頁佈局拆分出來,使其邏輯解耦,也便於代碼的管理維護。

2.1、PagesDataManager 分頁數據管理類

主要內容,包括:

1、配置分頁數據的查詢位置、每頁數量,每次查詢數據後重新計算下次查詢位置
2、分頁數據介面查詢
3、分頁狀態文本處理(數據查詢中、沒有更多數據了、查詢異常...)
package com.qxc.kotlinpages.pagemanage

import android.os.Handler
import android.os.Looper
import android.util.Log
import com.qxc.kotlinpages.pagedata.DataInfo
import com.qxc.kotlinpages.pagedata.DataSearch

/**
 * 分頁數據管理類:
 * 1、分頁數據的查詢位置、每頁數量
 * 2、分頁數據介面查詢
 * 3、分頁狀態文本處理
 *
 * @author 齊行超
 * @date 19.11.30
 */
class PagesDataManager {
    var startIndex = 0 //分頁起始位置
    val pageNum = 10 //每頁數量
    val TYPE_PAGE_MORE = "數據載入中..." //分頁載入類型:更多數據
    val TYPE_PAGE_LAST = "沒有更多數據了" //分頁載入類型:沒有數據了
    val TYPE_PAGE_ERROR = "出錯了,點擊重試!!" //分頁載入類型:出錯了,當這種狀態時可點擊重試

    //定義數據回調監聽
    //參數:dataInfos 當前頁數據集合, footType 分頁狀態文本
    lateinit var listener: ((dataInfos: ArrayList<DataInfo>, footType: String) -> Unit)

    /**
     * 設置回調
     */
    fun setDataListener(pListener: 
                       (dataInfos: ArrayList<DataInfo>, footType: String) -> Unit) {
        this.listener = pListener
    }

    /**
     * 查詢數據
     */
    fun searchPagesData() {
        //創建數據查詢對象(模擬數據查詢)
        var dataSearch = DataSearch()
        //設置數據回調監聽
        dataSearch.setDataRequestListener { errorCode, datas ->
            //切回主線程
            Handler(Looper.getMainLooper()).post {
                if (errorCode == 0) {
                    //累計當前位置
                    startIndex += datas.size
                    //判斷後面是否還有數據
                    var footType = if (pageNum == datas.size) TYPE_PAGE_MORE 
                                   else TYPE_PAGE_LAST
                    //回調結果
                    listener.invoke(datas, footType)
                } else {
                    //回調錯誤結果
                    listener.invoke(datas, TYPE_PAGE_ERROR)
                }
            }
        }
        //查詢數據
        dataSearch.search(startIndex, pageNum)
    }

    /**
     * 重置查詢
     */
    fun reset() {
        startIndex = 0;
    }
}
2.2、PagesLayoutManager 分頁佈局管理類

主要內容,包括:

1、創建列表佈局、網格佈局(只創建一次即可)
2、記錄數據位置(用於切換列表佈局、網格佈局時,保持位置不變)
package com.qxc.kotlinpages.pagemanage

import android.content.Context
import androidx.recyclerview.widget.GridLayoutManager
import androidx.recyclerview.widget.LinearLayoutManager

/**
 * 分頁佈局管理類:
 * 1、創建列表佈局、網格佈局
 * 2、記錄數據位置(用於切換列表佈局、網格佈局時,保持位置不變)
 *
 * @author 齊行超
 * @date 19.11.30
 */
class PagesLayoutManager(
    pcontext: Context
) {
    val STYLE_LIST = 1 //列表樣式(常量標識)
    val STYLE_GRID = 2 //網格樣式(常量標識)

    var llManager: LinearLayoutManager //列表佈局管理器對象
    var glManager: GridLayoutManager //網格佈局管理器對象

    var position: Int = 0 //數據位置(當切換樣式時,需記錄列表數據的位置,用以保持數據位置不變)
    var context = pcontext //上下文對象

    init {
        llManager = LinearLayoutManager(context)
        glManager = GridLayoutManager(context, 2)
    }

    /**
     * 獲得佈局管理器對象
     */
    fun getLayoutManager(pagesStyle: Int): LinearLayoutManager {
        //記錄當前數據位置
        recordDataPosition(pagesStyle)

        //根據樣式返回佈局管理器對象
        if (pagesStyle == STYLE_LIST) {
            return llManager
        }
        return glManager
    }

    /**
     * 獲得數據位置
     */
    fun getDataPosition(): Int {
        return position
    }

    /**
     * 記錄數據位置
     */
    private fun recordDataPosition(pagesStyle: Int) {
        //pagesStyle表示目標樣式,此處需要記錄的是原樣式時的數據位置
        if (pagesStyle == STYLE_LIST) {
            position = glManager?.findFirstVisibleItemPosition()
        } else if (pagesStyle == STYLE_GRID) {
            position = llManager?.findFirstVisibleItemPosition()
        }
    }
}
2.3、PagesManager 分頁管理類

主要內容,包含:

 1、創建、刷新適配器
 2、查詢、綁定分頁數據
 3、切換分頁佈局(列表佈局、網格佈局)
 4、當切換至網格佈局時,設置footview獨占一行(即使網格佈局每行顯示多個item,footview也獨占一行)

主要技術點,包括:

1、設置grid footview獨占一行
2、RecyclerView控制項的使用(數據綁定,刷新,樣式切換等)
package com.qxc.kotlinpages.pagemanage
import android.content.Context
import androidx.recyclerview.widget.GridLayoutManager
import androidx.recyclerview.widget.RecyclerView
import com.qxc.kotlinpages.adapter.PagesAdapter
import com.qxc.kotlinpages.pagedata.DataInfo

/**
 * 分頁管理類:
 * 1、創建適配器
 * 2、查詢、綁定分頁數據
 * 3、切換分頁佈局
 * 4、當切換至網格佈局時,設置footview獨占一行
 *
 * @author 齊行超
 * @date 19.11.30
 */
class PagesManager(pContext: Context, pRecyclerView: RecyclerView) {
    private var context = pContext //上下文對象
    private var recyclerView = pRecyclerView //列表控制項對象
    private var adapter:PagesAdapter? = null //適配器對象
    private var pagesLayoutManager = PagesLayoutManager(context) //分頁佈局管理對象
    private var pagesDataManager = PagesDataManager() //分頁數據管理對象
    private var datas = ArrayList<DataInfo>() //數據集合

    /**
     * 設置分頁樣式(列表、網格)
     *
     * @param isGrid 是否網格樣式
     */
    fun setPagesStyle(isGrid: Boolean): PagesManager {
        //通過樣式獲得對應的佈局類型
        var style = if (isGrid) pagesLayoutManager.STYLE_GRID 
                    else pagesLayoutManager.STYLE_LIST
        var layoutManager = pagesLayoutManager.getLayoutManager(style)

        //獲得當前數據位置(切換樣式後,滑動到記錄的數據位置)
        var position = pagesLayoutManager.getDataPosition()

        //創建適配器對象
        adapter = PagesAdapter(context, datas, pagesDataManager.TYPE_PAGE_MORE)
        //通知適配器,itemview當前使用哪種分頁佈局樣式(列表、網格)
        adapter?.setItemStyle(style)

        //列表控制項設置適配器
        recyclerView.adapter = adapter
        //列表控制項設置佈局管理器
        recyclerView.layoutManager = layoutManager
        //列表控制項滑動到指定位置
        recyclerView.scrollToPosition(position)

        //當layoutManager是網格佈局時,設置footview獨占一行
        if(layoutManager is GridLayoutManager){
            setSpanSizeLookup(layoutManager)
        }

        //設置監聽器
        setListener()
        return this
    }

    /**
     * 設置監聽器:
     *
     * 1、當滑動到列表底部時,查詢下一頁數據
     * 2、當點擊了footview的"出錯了,點擊重試!!"時,重新查詢數據
     */
    fun setListener() {
        //1、當滑動到列表底部時,查詢下一頁數據
        adapter?.setOnFootViewAttachedToWindowListener {
            //查詢數據
            searchData()
        }

        //2、當點擊了footview的"出錯了,點擊重試!!"時,重新查詢數據
        adapter?.setOnFootViewClickListener {
            if (it.equals(pagesDataManager.TYPE_PAGE_ERROR)) {

                //點擊查詢,更改footview狀態
                adapter?.footMessage = pagesDataManager.TYPE_PAGE_MORE
                adapter?.notifyDataSetChanged()

                //"出錯了,點擊重試!!
                searchData()
            }
        }
    }

    /**
     * 設置grid footview獨占一行
     */
    fun setSpanSizeLookup(layoutManager: GridLayoutManager) {
        layoutManager.setSpanSizeLookup(object : GridLayoutManager.SpanSizeLookup() {
            override fun getSpanSize(position: Int): Int {
                return if (adapter?.TYPE_FOOTVIEW == adapter?.getItemViewType(position)) 
                          layoutManager.getSpanCount() 
                       else 1
            }
        })
    }

    /**
     * 獲得查詢結果,刷新列表
     */
    fun searchData() {
        pagesDataManager.setDataListener { pdatas, footMessage ->
            if (pdatas != null) {
                datas.addAll(pdatas)
                adapter?.footMessage = footMessage
                adapter?.notifyDataSetChanged()
            }
        }
        pagesDataManager.searchPagesData()
    }
}

3、adapter 適配器模塊

3.1、PagesAdapter 分頁適配器類

主要內容,包括:

1、創建、綁定itemview(列表item、網格item)、footview
2、判斷是否滑動到列表底部(更簡單的方式實現列表滑動到底部的監聽)
3、footview點擊事件回調(如果是footview顯示為“出錯了,點擊重試”,需要獲取點擊事件,重新查詢數據)
4、滑動到列表底部事件回調(當列表滑動到底部時,則需要查詢下一頁數據了)

主要技術點,包括:

1、多item項的應用
2、滑動到列表底部的判斷(比“監聽RecyclerView的Scroll坐標”這種常規做法要簡化很多,且精準)
package com.qxc.kotlinpages.adapter

import android.content.Context
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.TextView
import androidx.recyclerview.widget.RecyclerView
import com.qxc.kotlinpages.R
import com.qxc.kotlinpages.pagedata.DataInfo

/**
 * 分頁適配器類
 * 1、創建、綁定itemview(列表item、網格item)、footview
 * 2、判斷是否滑動到列表底部
 * 3、footview點擊事件回調
 * 4、滑動到列表底部事件回調
 *
 * @author 齊行超
 * @date 19.11.30
 */
class PagesAdapter(
    pContext: Context,
    pDataInfos: ArrayList<DataInfo>,
    pFootMessage : String
) : RecyclerView.Adapter<RecyclerView.ViewHolder>() {

    private var context = pContext //上下文對象,通過構造傳函數遞過來
    private var datas = pDataInfos //數據集合,通過構造函數傳遞過來
    var footMessage = pFootMessage //footview文本信息,可通過構造函數傳遞過來,也可再次修改

    val TYPE_FOOTVIEW: Int = 1 //item類型:footview
    val TYPE_ITEMVIEW: Int = 2 //item類型:itemview
    var typeItem = TYPE_ITEMVIEW

    val STYLE_LIST_ITEM = 1 //樣式類型:列表
    val STYLE_GRID_ITEM = 2 //樣式類型:網格
    var styleItem = STYLE_LIST_ITEM

    /**
     * 重寫創建ViewHolder的函數
     */
    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int)
            : RecyclerView.ViewHolder {
        //如果是itemview
        if (typeItem == TYPE_ITEMVIEW) {
            //判斷樣式類型(列表佈局、網格佈局)
            var layoutId =
                if (styleItem == STYLE_LIST_ITEM) R.layout.item_page_list 
                else R.layout.item_page_grid
            var view = LayoutInflater.from(context).inflate(layoutId, parent, false)
            return ItemViewHolder(view)
        }
        //如果是footview
        else {
            var view = LayoutInflater.from(context)
                            .inflate(R.layout.item_page_foot, parent, false)
            return FootViewHolder(view)
        }
    }

    /**
     * 重寫獲得項數量的函數
     */
    override fun getItemCount(): Int {
        //因列表中增加了footview(顯示分頁狀態信息),所以item總數量 = 數據數量 + 1
        return datas.size + 1
    }

    /**
     * 重寫綁定ViewHolder的函數
     */
    override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
        if (holder is ItemViewHolder) {
            if (datas.size <= position) {
                return
            }
            var data = datas.get(position)
            holder.tv_title.text = data.title
            holder.tv_desc.text = data.desc
            holder.tv_price.text = data.price
            holder.tv_link.text = data.link

        } else if (holder is FootViewHolder) {
            holder.tv_msg.text = footMessage

            //當點擊footview時,將該事件回調出去
            holder.tv_msg.setOnClickListener {
                footViewClickListener.invoke(footMessage)
            }
        }
    }

    /**
     * 重新獲得項類型的函數(項類型包括:itemview、footview)
     */
    override fun getItemViewType(position: Int): Int {
        //設置在數據最底部顯示footview
        typeItem = if (position == datas.size) TYPE_FOOTVIEW else TYPE_ITEMVIEW
        return typeItem
    }


    /**
     * 當footview第二次出現在列表時,回調該事件
     * (此處用於模擬用戶上滑手勢,當滑到底部時,重新請求數據)
     */
    var footviewPosition = 0
    override fun onViewAttachedToWindow(holder: RecyclerView.ViewHolder) {
        super.onViewAttachedToWindow(holder)
        if (footviewPosition == holder.adapterPosition) {
            return
        }
        if (holder is FootViewHolder) {
            footviewPosition = holder.adapterPosition
            //回調查詢事件
            footViewAttachedToWindowListener.invoke()
        }
    }

    /**
     * ItemViewHolder
     */
    class ItemViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
        var tv_title = itemView.findViewById<TextView>(R.id.tv_title)
        var tv_desc = itemView.findViewById<TextView>(R.id.tv_desc)
        var tv_price = itemView.findViewById<TextView>(R.id.tv_price)
        var tv_link = itemView.findViewById<TextView>(R.id.tv_link)
    }

    /**
     * FootViewHolder
     */
    class FootViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
        var tv_msg = itemView.findViewById<TextView>(R.id.tv_msg)
    }

    /**
     * 設置Item樣式(列表、網格)
     */
    fun setItemStyle(pstyle: Int) {
        styleItem = pstyle
    }

    //定義footview附加到Window上時的回調
    lateinit var footViewAttachedToWindowListener: () -> Unit
    fun setOnFootViewAttachedToWindowListener(pListener: () -> Unit) {
        this.footViewAttachedToWindowListener = pListener
    }

    //定義footview點擊時的回調
    lateinit var footViewClickListener:(String)->Unit
    fun setOnFootViewClickListener(pListner:(String)->Unit){
        this.footViewClickListener = pListner
    }
}

4、utils 工具模塊

4.1、AppUtils 項目工具類

此案例中主要用於判斷網路連接情況。
該類的主要技術點:Kotlin的共生對象、線程安全單例,詳見源碼:

package com.qxc.kotlinpages.utils

import android.content.Context
import android.net.ConnectivityManager
import android.net.NetworkCapabilities
import android.os.Build

/**
 * 工具類
 *
 * @author 齊行超
 * @date 19.11.30
 */
class AppUtils {
    //使用共生對象,表示靜態static
    companion object{
        /**
         * 線程安全的單例(懶漢式單例)
         */
        val instance : AppUtils by lazy(mode = LazyThreadSafetyMode.SYNCHRONIZED) {
            AppUtils()
        }

        private lateinit var context:Context

        /**
         * 註冊
         *
         * @param pContext 上下文
         */
        fun register(pContext: Context){
            context = pContext
        }
    }

    /**
     * 判斷是否連接了網路
     */
    fun isConnectNetWork():Boolean{
        var result = false
        val cm = context.getSystemService(Context.CONNECTIVITY_SERVICE) 
                 as ConnectivityManager?
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
            cm?.run {
                this.getNetworkCapabilities(cm.activeNetwork)?.run {
                    result = when {
                        this.hasTransport(NetworkCapabilities.TRANSPORT_WIFI) -> true
                        this.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR) -> true
                        this.hasTransport(NetworkCapabilities.TRANSPORT_ETHERNET) -> true
                        else -> false
                    }
                }
            }
        } else {
            cm?.run {
                cm.activeNetworkInfo?.run {
                    if (type == ConnectivityManager.TYPE_WIFI) {
                        result = true
                    } else if (type == ConnectivityManager.TYPE_MOBILE) {
                        result = true
                    }
                }
            }
        }
        return result
    }
}

5、UI模塊

5.1、MainActivity 主頁面,用於顯示分頁列表、切換分頁樣式(列表樣式、網格樣式)
package com.qxc.kotlinpages

import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import com.qxc.kotlinpages.pagemanage.PagesManager
import com.qxc.kotlinpages.utils.AppUtils
import kotlinx.android.synthetic.main.activity_main.*

class MainActivity : AppCompatActivity() {
    var isGrid = false
    var pagesManager: PagesManager? = null

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

        initEvent()
        initData()
    }

    fun initEvent() {
        //切換列表樣式按鈕的點擊事件
        iv_style.setOnClickListener {
            //切換圖標(列表與網格)
            var id: Int =
                if (isGrid) R.mipmap.product_search_list_style_grid 
                else R.mipmap.product_search_list_style_list
            iv_style.setImageResource(id)

            //記錄當前圖標類型
            isGrid = !isGrid

            //更改樣式(列表與網格)
            pagesManager!!.setPagesStyle(isGrid)
        }
    }

    fun initData() {
        //初始化PagesManager,預設查詢列表
        pagesManager = PagesManager(this, rv_data)
        pagesManager!!.setPagesStyle(isGrid).searchData()
    }
}
註意:頁面中引用了 kotlinx.android.synthetic.main.activity_main.*
      》》這表示無需再寫findViewById()了,直接使用xml中控制項id即可

MainActivity的佈局頁面,使用了約束佈局,層級嵌套少,且更簡單一些:

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout 
    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:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <View
        android:id="@+id/v_top"
        android:layout_width="match_parent"
        android:layout_height="50dp"
        android:background="#FD4D4D"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

    <TextView
        android:id="@+id/tv_title"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="分頁demo"
        android:textColor="#ffffff"
        android:textSize="18sp"
        app:layout_constraintBottom_toBottomOf="@id/v_top"
        app:layout_constraintLeft_toLeftOf="@id/v_top"
        app:layout_constraintRight_toRightOf="@id/v_top"
        app:layout_constraintTop_toTopOf="@id/v_top" />

    <ImageView
        android:id="@+id/iv_style"
        android:layout_width="40dp"
        android:layout_height="40dp"
        android:layout_marginRight="5dp"
        android:scaleType="center"
        android:src="@mipmap/product_search_list_style_grid"
        app:layout_constraintBottom_toBottomOf="@id/v_top"
        app:layout_constraintRight_toRightOf="@id/v_top"
        app:layout_constraintTop_toTopOf="@id/v_top" />

    <androidx.recyclerview.widget.RecyclerView
        android:id="@+id/rv_data"
        android:layout_width="match_parent"
        android:layout_height="0dp"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toBottomOf="@id/v_top" />

</androidx.constraintlayout.widget.ConstraintLayout>
5.2、item佈局(列表樣式),也是使用了約束佈局:

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout 
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="150dp"
    android:layout_marginLeft="10dp"
    android:layout_marginTop="10dp"
    android:layout_marginRight="10dp"
    android:background="#eeeeee">

    <ImageView
        android:id="@+id/iv_image"
        android:layout_width="80dp"
        android:layout_height="110dp"
        android:layout_marginLeft="10dp"
        android:scaleType="fitXY"
        android:src="@mipmap/kotlin"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

    <TextView
        android:id="@+id/tv_title"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginLeft="10dp"
        android:text="Android Kotlin"
        android:textColor="#333333"
        android:textSize="18sp"
        app:layout_constraintLeft_toRightOf="@id/iv_image"
        app:layout_constraintTop_toTopOf="@id/iv_image" />

    <TextView
        android:id="@+id/tv_desc"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_marginLeft="10dp"
        android:layout_marginRight="10dp"
        android:lines="2"
        android:text="Kotlin 是一個用於現代多平臺應用的靜態編程語言,由 JetBrains 開發..."
        android:textColor="#888888"
        android:textSize="12sp"
        app:layout_constraintLeft_toRightOf="@id/iv_image"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toBottomOf="@id/tv_title" />

    <TextView
        android:id="@+id/tv_price_symbol"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginLeft="10dp"
        android:layout_marginTop="20dp"
        android:text="¥"
        android:textColor="#FD4D4D"
        android:textSize="10dp"
        app:layout_constraintLeft_toRightOf="@id/iv_image"
        app:layout_constraintTop_toBottomOf="@id/tv_desc" />

    <TextView
        android:id="@+id/tv_price"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="128.00"
        android:textColor="#FD4D4D"
        android:textSize="22sp"
        app:layout_constraintBaseline_toBaselineOf="@id/tv_price_symbol"
        app:layout_constraintLeft_toRightOf="@id/tv_price_symbol" />

    <TextView
        android:id="@+id/tv_link"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginLeft="10dp"
        android:text="跳轉至Kotlin櫃臺 -> JetBrains"
        android:textColor="#aaaaaa"
        android:textSize="10sp"
        app:layout_constraintBottom_toBottomOf="@id/iv_image"
        app:layout_constraintLeft_toRightOf="@id/iv_image" />

</androidx.constraintlayout.widget.ConstraintLayout>
5.3、item佈局(網格樣式),仍然使用了約束佈局:

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout 
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:layout_marginLeft="10dp"
    android:layout_marginTop="10dp"
    android:layout_marginRight="10dp"
    android:paddingBottom="10dp"
    android:background="#eeeeee">

    <ImageView
        android:id="@+id/iv_image"
        android:layout_width="80dp"
        android:layout_height="110dp"
        android:layout_marginTop="10dp"
        android:scaleType="fitXY"
        android:src="@mipmap/kotlin"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

    <TextView
        android:id="@+id/tv_title"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginLeft="10dp"
        android:layout_marginTop="20dp"
        android:text="Android Kotlin"
        android:textColor="#333333"
        android:textSize="18sp"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintTop_toBottomOf="@id/iv_image" />

    <TextView
        android:id="@+id/tv_desc"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_marginRight="10dp"
        android:lines="2"
        android:text="Kotlin 是一個用於現代多平臺應用的靜態編程語言,由 JetBrains 開發..."
        android:textColor="#888888"
        android:textSize="12sp"
        app:layout_constraintLeft_toLeftOf="@id/tv_title"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toBottomOf="@id/tv_title" />

    <TextView
        android:id="@+id/tv_price_symbol"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="20dp"
        android:text="¥"
        android:textColor="#FD4D4D"
        android:textSize="10dp"
        app:layout_constraintLeft_toLeftOf="@id/tv_title"
        app:layout_constraintTop_toBottomOf="@id/tv_desc" />

    <TextView
        android:id="@+id/tv_price"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="128.00"
        android:textColor="#FD4D4D"
        android:textSize="22sp"
        app:layout_constraintBaseline_toBaselineOf="@id/tv_price_symbol"
        app:layout_constraintLeft_toRightOf="@id/tv_price_symbol" />

    <TextView
        android:id="@+id/tv_link"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="跳轉至Kotlin櫃臺 -> JetBrains"
        android:textColor="#aaaaaa"
        android:textSize="10sp"
        app:layout_constraintTop_toBottomOf="@id/tv_price"
        app:layout_constraintLeft_toLeftOf="@id/tv_title" />

</androidx.constraintlayout.widget.ConstraintLayout>
5.4、footview佈局

比較簡單,僅有一個文本控制項:

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout 
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="50dp"
    android:layout_margin="10dp"
    android:background="#eeeeee">

    <TextView
        android:id="@+id/tv_msg"
        android:layout_width="0dp"
        android:layout_height="0dp"
        android:gravity="center"
        android:text="載入中..."
        android:textColor="#777777"
        android:textSize="12sp"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

</androidx.constraintlayout.widget.ConstraintLayout>

總結

分頁實現難點彙總:

1、切換RecyclerView展示樣式(列表樣式、網格樣式),保持數據位置不變
2、網格樣式時,footview獨占一行
3、直接在adapter中判斷是否滑動到了底部,比常規做法(監聽滑動坐標)更簡單一些
4、分頁狀態管控(數據載入中、沒有更多數據了、出錯了點擊重試)

Kotlin主要技術點彙總:

1、多線程實現(Lambda表達式的應用)
2、非同步回調(Lambda表達式的應用、高階函數)
3、共生對象
4、線程安全單例
5、其他略(都比較基礎了,大家熟悉下即可)

此篇文章主要是為了講解常規分頁的實現,所以只是做了一些基礎的拆分解耦,如果想在項目中使用,建議還是抽象一下,擴展性會更好一些(如:footview介面化擴展、數據查詢介面化擴展等)。

如果有疑問,也歡迎留言咨詢O(∩_∩)O~

Demo下載地址:
https://pan.baidu.com/s/1gH0Zcd0QXdm4mRNMqJgS8Q


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

-Advertisement-
Play Games
更多相關文章
  • 有時會發現資料庫system表空間增長很快,使用以下語句查看system表空間使用量。也可以使用toad直接看。 執行以下語句查看是哪個對象占用較大 一般發現都是發現是AUD$審計表占用資源量大。 直接登錄資料庫,清理掉SYS.AUD$表。 如果想關閉資料庫審計,可以參考以下鏈接 https://w ...
  • spark 各個版本的application 調度演算法還是有這明顯的不同之處的。從spark1.3.0 到 spark 1.6.1、spark2.x 到 現在最新的spark 3.x ,調度演算法有了一定的修改。下麵大家一起學習一下,最新的spark 版本spark-3.0的Application 調 ...
  • 說明: 1)該實驗所有過程均是本人親自敲命令完成,所有代碼運行正確 2)安裝過程使用的是suse11 sp3操作系統,後續的實驗過程換成了麒麟中標,因此部分路徑可能存在差異 3)安裝過程使用了命令行安裝,圖形界面簡單,因此本文沒有介紹 4)job部分命令行操作太繁瑣,建議使用圖形界面操作,因此本文也 ...
  • sqlServer2012(936 簡體中文GBK )為例: 例如: varchar(10),只能存儲10個英文字元或數字,也只能存儲5個漢字; char(10),只能存儲10個英文字元或數字,也只能存儲5個漢字; nvarchar(10),即存儲10個英文字元或數字,也能存儲10個漢字; ncha ...
  • 知識點 △用資料庫的原因 1文件操作的複雜度 2同步 3併發處理 4安全 △資料庫系統(DBS) 資料庫(DB) + 資料庫管理系統 (DBS)+ 資料庫應用程式 + 資料庫管理員 (BDA)+ 最終用戶 △資料庫管理系統 DBM 網路應用服務端 我們要使用服務端的數據 需要有一個客戶端 客戶端可以 ...
  • 常見的SQL優化方式 1. 對查詢進行優化,應儘量避免全表掃描,首先應考慮在 where及order by 涉及的列上建立索引 。 2. 應儘量 避免 在 where 子句中對欄位進行null 值判斷,否則將導致引擎放棄使用索引而進行全表掃描,如: 可以在num上設置預設值0,確保表中num列是否存 ...
  • 前言 這是 "Android 9.0 AOSP 系列" 的第五篇了,先來回顧一下前面幾篇的大致內容。 "Java 世界的盤古和女媧 —— Zygote" 主要介紹了 Android 世界的第一個 Java 進程 的啟動過程。 註冊服務端 socket,用於響應客戶端請求 各種預載入操作,類,資源,共 ...
  • 我們編寫一個能夠用過按鈕動態更替碎片的APP,首先在主頁上顯示第一個碎片,點擊按鈕後可以替換到第二個碎片,或者刪除已經替換掉的第二個碎片。 一.MainActivity.java import androidx.fragment.app.FragmentActivity; import androi ...
一周排行
    -Advertisement-
    Play Games
  • Timer是什麼 Timer 是一種用於創建定期粒度行為的機制。 與標準的 .NET System.Threading.Timer 類相似,Orleans 的 Timer 允許在一段時間後執行特定的操作,或者在特定的時間間隔內重覆執行操作。 它在分散式系統中具有重要作用,特別是在處理需要周期性執行的 ...
  • 前言 相信很多做WPF開發的小伙伴都遇到過表格類的需求,雖然現有的Grid控制項也能實現,但是使用起來的體驗感並不好,比如要實現一個Excel中的表格效果,估計你能想到的第一個方法就是套Border控制項,用這種方法你需要控制每個Border的邊框,並且在一堆Bordr中找到Grid.Row,Grid. ...
  • .NET C#程式啟動閃退,目錄導致的問題 這是第2次踩這個坑了,很小的編程細節,容易忽略,所以寫個博客,分享給大家。 1.第一次坑:是windows 系統把程式運行成服務,找不到配置文件,原因是以服務運行它的工作目錄是在C:\Windows\System32 2.本次坑:WPF桌面程式通過註冊表設 ...
  • 在分散式系統中,數據的持久化是至關重要的一環。 Orleans 7 引入了強大的持久化功能,使得在分散式環境下管理數據變得更加輕鬆和可靠。 本文將介紹什麼是 Orleans 7 的持久化,如何設置它以及相應的代碼示例。 什麼是 Orleans 7 的持久化? Orleans 7 的持久化是指將 Or ...
  • 前言 .NET Feature Management 是一個用於管理應用程式功能的庫,它可以幫助開發人員在應用程式中輕鬆地添加、移除和管理功能。使用 Feature Management,開發人員可以根據不同用戶、環境或其他條件來動態地控制應用程式中的功能。這使得開發人員可以更靈活地管理應用程式的功 ...
  • 在 WPF 應用程式中,拖放操作是實現用戶交互的重要組成部分。通過拖放操作,用戶可以輕鬆地將數據從一個位置移動到另一個位置,或者將控制項從一個容器移動到另一個容器。然而,WPF 中預設的拖放操作可能並不是那麼好用。為瞭解決這個問題,我們可以自定義一個 Panel 來實現更簡單的拖拽操作。 自定義 Pa ...
  • 在實際使用中,由於涉及到不同編程語言之間互相調用,導致C++ 中的OpenCV與C#中的OpenCvSharp 圖像數據在不同編程語言之間難以有效傳遞。在本文中我們將結合OpenCvSharp源碼實現原理,探究兩種數據之間的通信方式。 ...
  • 一、前言 這是一篇搭建許可權管理系統的系列文章。 隨著網路的發展,信息安全對應任何企業來說都越發的重要,而本系列文章將和大家一起一步一步搭建一個全新的許可權管理系統。 說明:由於搭建一個全新的項目過於繁瑣,所有作者將挑選核心代碼和核心思路進行分享。 二、技術選擇 三、開始設計 1、自主搭建vue前端和. ...
  • Csharper中的表達式樹 這節課來瞭解一下表示式樹是什麼? 在C#中,表達式樹是一種數據結構,它可以表示一些代碼塊,如Lambda表達式或查詢表達式。表達式樹使你能夠查看和操作數據,就像你可以查看和操作代碼一樣。它們通常用於創建動態查詢和解析表達式。 一、認識表達式樹 為什麼要這樣說?它和委托有 ...
  • 在使用Django等框架來操作MySQL時,實際上底層還是通過Python來操作的,首先需要安裝一個驅動程式,在Python3中,驅動程式有多種選擇,比如有pymysql以及mysqlclient等。使用pip命令安裝mysqlclient失敗應如何解決? 安裝的python版本說明 機器同時安裝了 ...