隨着Kotlin的推廣,一些國內公司的安卓項目開發,已經從Java徹底切成Kotlin了。雖然Kotlin在各種編程語言中的排名比較靠後(據TIOBE發佈了 19 年 8 月份的編程語言排行榜,Kotlin居然排名45位),可是做爲安卓開發者,掌握該語言,卻已經是大勢所趨了。html
Kotlin的基礎用法,總體仍是比較簡單的,網上已經有不少文章了,你們熟悉下便可。android
這次案例,之因此選擇分頁列表,主要是由於該功能通用性強,涵蓋的技術點也較多,對開發者熟悉Kotlin幫助性較大。數據庫
案例的主要需求以下( 參考主流電商APP實現 ):
一、列表支持手勢滑動分頁查詢(滑動到底部時,自動查詢下一頁,直到沒有更多數據)
二、可切換列表樣式和網格樣式
三、切換樣式後,數據位置保持不變(如當前在第100條位置,切換樣式後,位置不變)
四、footview根據查詢狀態,顯示不一樣內容:編程
a、數據加載中... (正在查詢數據時顯示) b、沒有更多數據了 (查詢成功,可是已沒有可返回的數據了) c、出錯了,點擊重試!!(查詢時出現異常,多是網絡,也多是其餘緣由)
五、當查詢出錯時,再次點擊footview,可從新發起請求(例如:網絡異常了)
六、當切換網格樣式時,footview應獨佔一行安全
雖然是簡單案例,我們開發時,也應先進行簡單的設計,讓各模塊、各種都各司其職、邏輯解耦,這樣你們學起來會更簡單一些。
此處,不畫類圖了,直接根據項目結構,簡單介紹一下吧:服務器
一、pagedata 是指數據模塊,包含:網絡
DataInfo 實體類,定義商品字段屬性 DataSearch 數據訪問類,模擬子線程異步查詢分頁數據,可將數據結果經過lambda回調回去
二、pageMangage 是指分頁管理模塊,將分頁的所有邏輯託管給該模塊處理。爲了簡化分頁邏輯的實現,根據功能單一性進行了簡單拆分:多線程
PagesManager 分頁管理類,用於統籌列表數據查詢、展現、樣式切換 PagesLayoutManager 分頁佈局管理類,用於列表樣式和網格樣式的管理、數據位置記錄 PagesDataManager 分頁數據管理類,用於分頁列表數據、footview數據的封裝
三、adapter 是指適配器模塊,主要用於定義各種適配器app
PagesAdapter 分頁適配器類,用於建立、展現 itemview、footview,處理footview回調事件
四、utils 是指工具模塊,用於定義一些經常使用工具異步
AppUtils 工具類,用於判斷網絡鏈接狀況
在文章的最後,會將Demo源碼下載地址分享給你們,以供參考。
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 = "" }
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.一、子線程異步查詢的實現
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.二、數據回調監聽
此案例經過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同樣,只是代碼會繁瑣一些而已!!!
爲了簡化分頁邏輯,讓你們更好理解,此處將分頁數據、分頁佈局拆分出來,使其邏輯解耦,也便於代碼的管理維護。
主要內容,包括:
一、配置分頁數據的查詢位置、每頁數量,每次查詢數據後從新計算下次查詢位置 二、分頁數據接口查詢 三、分頁狀態文本處理(數據查詢中、沒有更多數據了、查詢異常...)
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 /** * 分頁數據管理類: * 一、分頁數據的查詢位置、每頁數量 * 二、分頁數據接口查詢 * 三、分頁狀態文本處理 * * @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; } }
主要內容,包括:
一、建立列表佈局、網格佈局(只建立一次便可) 二、記錄數據位置(用於切換列表佈局、網格佈局時,保持位置不變)
package com.qxc.kotlinpages.pagemanage import android.content.Context import androidx.recyclerview.widget.GridLayoutManager import androidx.recyclerview.widget.LinearLayoutManager /** * 分頁佈局管理類: * 一、建立列表佈局、網格佈局 * 二、記錄數據位置(用於切換列表佈局、網格佈局時,保持位置不變) * * @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() } } }
主要內容,包含:
一、建立、刷新適配器 二、查詢、綁定分頁數據 三、切換分頁佈局(列表佈局、網格佈局) 四、當切換至網格佈局時,設置footview獨佔一行(即便網格佈局每行顯示多個item,footview也獨佔一行)
主要技術點,包括:
一、設置grid footview獨佔一行 二、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 /** * 分頁管理類: * 一、建立適配器 * 二、查詢、綁定分頁數據 * 三、切換分頁佈局 * 四、當切換至網格佈局時,設置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 } /** * 設置監聽器: * * 一、當滑動到列表底部時,查詢下一頁數據 * 二、當點擊了footview的"出錯了,點擊重試!!"時,從新查詢數據 */ fun setListener() { //一、當滑動到列表底部時,查詢下一頁數據 adapter?.setOnFootViewAttachedToWindowListener { //查詢數據 searchData() } //二、當點擊了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() } }
主要內容,包括:
一、建立、綁定itemview(列表item、網格item)、footview 二、判斷是否滑動到列表底部(更簡單的方式實現列表滑動到底部的監聽) 三、footview點擊事件回調(若是是footview顯示爲「出錯了,點擊重試」,須要獲取點擊事件,從新查詢數據) 四、滑動到列表底部事件回調(當列表滑動到底部時,則須要查詢下一頁數據了)
主要技術點,包括:
一、多item項的應用 二、滑動到列表底部的判斷(比「監聽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 /** * 分頁適配器類 * 一、建立、綁定itemview(列表item、網格item)、footview * 二、判斷是否滑動到列表底部 * 三、footview點擊事件回調 * 四、滑動到列表底部事件回調 * * @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 } }
此案例中主要用於判斷網絡鏈接狀況。
該類的主要技術點: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 } }
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>
<?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>
<?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>
比較簡單,僅有一個文本控件:
<?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>
一、切換RecyclerView展現樣式(列表樣式、網格樣式),保持數據位置不變
二、網格樣式時,footview獨佔一行
三、直接在adapter中判斷是否滑動到了底部,比常規作法(監聽滑動座標)更簡單一些
四、分頁狀態管控(數據加載中、沒有更多數據了、出錯了點擊重試)
一、多線程實現(Lambda表達式的應用)
二、異步回調(Lambda表達式的應用、高階函數)
三、共生對象
四、線程安全單例
五、其餘略(都比較基礎了,你們熟悉下便可)
此篇文章主要是爲了講解常規分頁的實現,因此只是作了一些基礎的拆分解耦,若是想在項目中使用,建議仍是抽象一下,擴展性會更好一些(如:footview接口化擴展、數據查詢接口化擴展等)。
若是有疑問,也歡迎留言諮詢O(∩_∩)O~
Demo下載地址: https://pan.baidu.com/s/1gH0Zcd0QXdm4mRNMqJgS8Q