随着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类有两个重点知识:
a、子线程异步查询的实现
a、参考常规分页网络请求API,调用方应传参:起始位置、每页数量 b、网络查询,需要在子线程中操作,当前案例直接使用Thread{}.start()实现子线程 -----------------------------------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
b、数据回调监听
此案例通过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也独占一行)
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、滑动到列表底部事件回调(当列表滑动到底部时,则需要查询下一页数据了)
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 项目工具类
此案例中主要用于判断网络连接情况:
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、其他略(都比较基础了,大家熟悉下即可)
此篇文章主要是为了讲解常规分页的实现,有些内容并未进行完全解耦,如果想在项目中使用,建议还是抽象一下,扩展性会更好一些。
如果有疑问,也欢迎留言咨询O(∩_∩)O~
Demo下载地址:
https://pan.baidu.com/s/1gH0Zcd0QXdm4mRNMqJgS8Q