隨著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