Kotlin实战案例:带你实现RecyclerView分页查询功能(仿照主流电商APP,可切换列表和网格效果)
随着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
上一篇: 初识爬虫之安装准备篇