主頁 > 移動端開發 > Kotlin實戰案例:帶你實作RecyclerView分頁查詢功能(仿照主流電商APP,可切換串列和網格效果)

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

2020-09-15 15:35:48 移動端開發

演示:分頁查詢資料,切換串列樣式(串列、網格)

演示:網路例外時分頁查詢

隨著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 = https://www.cnblogs.com/qixingchao/p/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 = https://www.cnblogs.com/qixingchao/p/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(R.id.tv_title)
        var tv_desc = itemView.findViewById(R.id.tv_desc)
        var tv_price = itemView.findViewById(R.id.tv_price)
        var tv_link = itemView.findViewById(R.id.tv_link)
    }

    /**
     * FootViewHolder
     */
    class FootViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
        var tv_msg = itemView.findViewById(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=https://www.cnblogs.com/qixingchao/p/"@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" />

    


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=https://www.cnblogs.com/qixingchao/p/"@mipmap/kotlin"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

    

    

    

    

    



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=https://www.cnblogs.com/qixingchao/p/"@mipmap/kotlin"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

    

    

    

    

    



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

轉載請註明出處,本文鏈接:https://www.uj5u.com/yidong/47850.html

標籤:Android

上一篇:Kotlin協程通信機制: Channel

下一篇:Android動態添加碎片

標籤雲
其他(157675) Python(38076) JavaScript(25376) Java(17977) C(15215) 區塊鏈(8255) C#(7972) AI(7469) 爪哇(7425) MySQL(7132) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5869) 数组(5741) R(5409) Linux(5327) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4554) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2429) ASP.NET(2402) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) 功能(1967) .NET技术(1958) Web開發(1951) python-3.x(1918) HtmlCss(1915) 弹簧靴(1913) C++(1909) xml(1889) PostgreSQL(1872) .NETCore(1853) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • 【從零開始擼一個App】Dagger2

    Dagger2是一個IOC框架,一般用于Android平臺,第一次接觸的朋友,一定會被搞得暈頭轉向。它延續了Java平臺Spring框架代碼碎片化,注解滿天飛的傳統。嘗試將各處代碼片段串聯起來,理清思緒,真不是件容易的事。更不用說還有各版本細微的差別。 與Spring不同的是,Spring是通過反射 ......

    uj5u.com 2020-09-10 06:57:59 more
  • Flutter Weekly Issue 66

    新聞 Flutter 季度調研結果分享 教程 Flutter+FaaS一體化任務編排的思考與設計 詳解Dart中如何通過注解生成代碼 GitHub 用對了嗎?Flutter 團隊分享如何管理大型開源專案 插件 flutter-bubble-tab-indicator A Flutter librar ......

    uj5u.com 2020-09-10 06:58:52 more
  • Proguard 常用規則

    介紹 Proguard 入口,如何查看輸出,如何使用 keep 設定入口以及使用實體,如何配置壓縮,混淆,校驗等規則。

    ......

    uj5u.com 2020-09-10 06:59:00 more
  • Android 開發技術周報 Issue#292

    新聞 Android即將獲得類AirDrop功能:可向附近設備快速分享檔案 谷歌為安卓檔案管理應用引入可安全隱藏資料的Safe Folder功能 Android TV新主界面將顯示電影、電視節目和應用推薦內容 泄露的Android檔案暗示了傳說中的谷歌Pixel 5a與折疊屏新機 谷歌發布Andro ......

    uj5u.com 2020-09-10 07:00:37 more
  • AutoFitTextureView Error inflating class

    報錯: Binary XML file line #0: Binary XML file line #0: Error inflating class xxx.AutoFitTextureView 解決: <com.example.testy2.AutoFitTextureView android: ......

    uj5u.com 2020-09-10 07:00:41 more
  • 根據Uri,Cursor沒有獲取到對應的屬性

    Android: 背景:呼叫攝像頭,拍攝視頻,指定保存的地址,但是回傳的Cursor檔案,只有名稱和大小的屬性,沒有其他諸如時長,連ID屬性都沒有 使用 cursor.getInt(cursor.getColumnIndexOrThrow(MediaStore.Video.Media.DURATIO ......

    uj5u.com 2020-09-10 07:00:44 more
  • Android連載29-持久化技術

    一、持久化技術 我們平時所使用的APP產生的資料,在記憶體中都是瞬時的,會隨著斷電、關機等丟失資料,因此android系統采用了持久化技術,用于存盤這些“瞬時”資料 持久化技術包括:檔案存盤、SharedPreference存盤以及資料庫存盤,還有更復雜的SD卡記憶體儲。 二、檔案存盤 最基本存盤方式, ......

    uj5u.com 2020-09-10 07:00:47 more
  • Android Camera2Video整合到自己專案里

    背景: Android專案里呼叫攝像頭拍攝視頻,原本使用的 MediaStore.ACTION_VIDEO_CAPTURE, 后來因專案需要,改成了camera2 1.Camera2Video 官方demo有點問題,下載后,不能直接整合到專案 問題1.多次拍攝視頻崩潰 問題2.雙擊record按鈕, ......

    uj5u.com 2020-09-10 07:00:50 more
  • Android 開發技術周報 Issue#293

    新聞 谷歌為Android TV開發者提供多種新功能 Android 11將自動填表功能整合到鍵盤輸入建議中 谷歌宣布Android Auto即將支持更多的導航和數字停車應用 谷歌Pixel 5只有XL版本 搭載驍龍765G且將比Pixel 4更便宜 [圖]Wear OS將迎來重磅更新:應用啟動時間 ......

    uj5u.com 2020-09-10 07:01:38 more
  • 海豚星空掃碼投屏 Android 接收端 SDK 集成 六步驟

    掃碼投屏,開放網路,獨占設備,不需要額外下載軟體,微信掃碼,發現設備。支持標準DLNA協議,支持倍速播放。視頻,音頻,圖片投屏。好點意思。還支持自定義基于 DLNA 擴展的操作動作。好像要收費,沒體驗。 這里簡單記錄一下集成程序。 一 跟目錄的build.gradle添加私有mevan倉庫 mave ......

    uj5u.com 2020-09-10 07:01:43 more
最新发布
  • 歡迎頁輪播影片

    如圖,引導開始,球從上落下,同時淡入文字,然后文字開始輪播,最后一頁時停止,點擊進入首頁。 在來看看效果圖。 重力球先不講,主要歡迎輪播簡單實作 首先新建一個類 TextTranslationXGuideView,用于影片展示 文本是類似的,最后會有個圖片箭頭影片,布局很簡單,就是一個 TextVi ......

    uj5u.com 2023-04-20 08:40:31 more
  • 【FAQ】關于華為推送服務因營銷訊息頻次管控導致服務通訊類訊息

    一. 問題描述 使用華為推送服務下發IM訊息時,下發訊息請求成功且code碼為80000000,但是手機總是收不到訊息; 在華為推送自助分析(Beta)平臺查看發現,訊息發送觸發了頻控。 二. 問題原因及背景 2023年1月05日起,華為推送服務對咨詢營銷類訊息做了單個設備每日推送數量上限管理,具體 ......

    uj5u.com 2023-04-20 08:40:11 more
  • 歡迎頁輪播影片

    如圖,引導開始,球從上落下,同時淡入文字,然后文字開始輪播,最后一頁時停止,點擊進入首頁。 在來看看效果圖。 重力球先不講,主要歡迎輪播簡單實作 首先新建一個類 TextTranslationXGuideView,用于影片展示 文本是類似的,最后會有個圖片箭頭影片,布局很簡單,就是一個 TextVi ......

    uj5u.com 2023-04-20 08:39:36 more
  • 【FAQ】關于華為推送服務因營銷訊息頻次管控導致服務通訊類訊息

    一. 問題描述 使用華為推送服務下發IM訊息時,下發訊息請求成功且code碼為80000000,但是手機總是收不到訊息; 在華為推送自助分析(Beta)平臺查看發現,訊息發送觸發了頻控。 二. 問題原因及背景 2023年1月05日起,華為推送服務對咨詢營銷類訊息做了單個設備每日推送數量上限管理,具體 ......

    uj5u.com 2023-04-20 08:39:13 more
  • iOS從UI記憶體地址到讀取成員變數(oc/swift)

    開發除錯時,我們發現bug時常首先是從UI顯示發現例外,下一步才會去定位UI相關連的資料的。XCode有給我們提供一系列debug工具,但是很多人可能還沒有形成一套穩定的除錯流程,因此本文嘗試解決這個問題,順便提出一個暴論:UI顯示例外問題只需要兩個步驟就能完成定位作業的80%: 定位例外 UI 組 ......

    uj5u.com 2023-04-19 09:16:23 more
  • FIDE重磅更新!性能飛躍!體驗有禮!

    FIDE 開發者工具重構升級啦!實作500%性能提升,誠邀體驗! 一直以來不少開發者朋友在社區反饋,在使用 FIDE 工具的程序中,時常會遇到諸如加載不及時、代碼預覽/渲染性能不如意的情況,十分影響開發體驗。 作為技術團隊,我們深知一件趁手的開發工具對開發者的重要性,因此,在2023年開年,FinC ......

    uj5u.com 2023-04-19 09:16:15 more
  • 游戲內嵌社區服務開放,助力開發者提升玩家互動與留存

    華為 HMS Core 游戲內嵌社區服務提供快速訪問華為游戲中心論壇能力,支持玩家直接在游戲內瀏覽帖子和交流互動,助力開發者擴展內容生產和觸達的場景。 一、為什么要游戲內嵌社區? 二、游戲內嵌社區的典型使用場景 1、游戲內打開論壇 您可以在游戲內繪制論壇入口,為玩家提供沉浸式發帖、瀏覽、點贊、回帖、 ......

    uj5u.com 2023-04-19 09:15:46 more
  • iOS從UI記憶體地址到讀取成員變數(oc/swift)

    開發除錯時,我們發現bug時常首先是從UI顯示發現例外,下一步才會去定位UI相關連的資料的。XCode有給我們提供一系列debug工具,但是很多人可能還沒有形成一套穩定的除錯流程,因此本文嘗試解決這個問題,順便提出一個暴論:UI顯示例外問題只需要兩個步驟就能完成定位作業的80%: 定位例外 UI 組 ......

    uj5u.com 2023-04-19 09:14:53 more
  • FIDE重磅更新!性能飛躍!體驗有禮!

    FIDE 開發者工具重構升級啦!實作500%性能提升,誠邀體驗! 一直以來不少開發者朋友在社區反饋,在使用 FIDE 工具的程序中,時常會遇到諸如加載不及時、代碼預覽/渲染性能不如意的情況,十分影響開發體驗。 作為技術團隊,我們深知一件趁手的開發工具對開發者的重要性,因此,在2023年開年,FinC ......

    uj5u.com 2023-04-19 09:14:08 more
  • 游戲內嵌社區服務開放,助力開發者提升玩家互動與留存

    華為 HMS Core 游戲內嵌社區服務提供快速訪問華為游戲中心論壇能力,支持玩家直接在游戲內瀏覽帖子和交流互動,助力開發者擴展內容生產和觸達的場景。 一、為什么要游戲內嵌社區? 二、游戲內嵌社區的典型使用場景 1、游戲內打開論壇 您可以在游戲內繪制論壇入口,為玩家提供沉浸式發帖、瀏覽、點贊、回帖、 ......

    uj5u.com 2023-04-19 09:08:34 more