n实战案例:带你实现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类有两个重点知识:

1.2.1、子线程异步查询的实现

a、参考常规分页网络请求API,数据查询方法应包含参数:起始位置、每页数量
b、安卓中的网络查询,需要在子线程中操作,当前案例直接使用Thread{}.start()实现子线程线程实现的写法与Java中不太一样,为什么这么写呢?咱们具体展开说明一下:
-----------------------------------Thread{}.start()-------------------------------------
通常情况下,Java中实现一个线程,可这么写:
new Thread(new Runnable() {@Overridepublic 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>) -> UnitLambda表达式可理解为一种特殊类型,即:抽象方法类型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.pagemanageimport 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.pagemanageimport 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_LISTvar 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_MOREadapter?.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 = footMessageadapter?.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.adapterimport 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类型:footviewval TYPE_ITEMVIEW: Int = 2 //item类型:itemviewvar typeItem = TYPE_ITEMVIEWval STYLE_LIST_ITEM = 1 //样式类型:列表val STYLE_GRID_ITEM = 2 //样式类型:网格var styleItem = STYLE_LIST_ITEM/*** 重写创建ViewHolder的函数*/override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {//如果是itemviewif (typeItem == TYPE_ITEMVIEW) {//判断样式类型(列表布局、网格布局)var layoutId =if (styleItem == STYLE_LIST_ITEM) R.layout.item_page_list else R.layout.item_page_gridvar view = LayoutInflater.from(context).inflate(layoutId, parent, false)return ItemViewHolder(view)}//如果是footviewelse {var view = LayoutInflater.from(context).inflate(R.layout.item_page_foot, parent, false)return FootViewHolder(view)}}/*** 重写获得项数量的函数*/override fun getItemCount(): Int {//因列表中增加了footview(显示分页状态信息),所以item总数量 = 数据数量 + 1return 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.titleholder.tv_desc.text = data.descholder.tv_price.text = data.priceholder.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 {//设置在数据最底部显示footviewtypeItem = if (position == datas.size) TYPE_FOOTVIEW else TYPE_ITEMVIEWreturn typeItem}/*** 当footview第二次出现在列表时,回调该事件* (此处用于模拟用户上滑手势,当滑到底部时,重新请求数据)*/var footviewPosition = 0override 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: () -> Unitfun setOnFootViewAttachedToWindowListener(pListener: () -> Unit) {this.footViewAttachedToWindowListener = pListener}//定义footview点击时的回调lateinit var footViewClickListener:(String)->Unitfun setOnFootViewClickListener(pListner:(String)->Unit){this.footViewClickListener = pListner}
}

4、utils 工具模块

4.1、AppUtils 项目工具类

此案例中主要用于判断网络连接情况。
该类的主要技术点:Kotlin的共生对象、线程安全单例,详见源码:

package com.qxc.kotlinpages.utilsimport android.content.Context
import android.net.ConnectivityManager
import android.net.NetworkCapabilities
import android.os.Build/*** 工具类** @author 齐行超* @date 19.11.30*/
class AppUtils {//使用共生对象,表示静态staticcompanion 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 = falseval 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) -> truethis.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR) -> truethis.hasTransport(NetworkCapabilities.TRANSPORT_ETHERNET) -> trueelse -> 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.kotlinpagesimport 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 = falsevar pagesManager: PagesManager? = nulloverride 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_listiv_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"><Viewandroid: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" /><TextViewandroid: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" /><ImageViewandroid: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.RecyclerViewandroid: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"><ImageViewandroid: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" /><TextViewandroid: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" /><TextViewandroid: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" /><TextViewandroid: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" /><TextViewandroid: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" /><TextViewandroid: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"><ImageViewandroid: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" /><TextViewandroid: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" /><TextViewandroid: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" /><TextViewandroid: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" /><TextViewandroid: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" /><TextViewandroid: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"><TextViewandroid: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接口化扩展、数据查询接口化扩展等)。

Kotlin实战案例:实现RecyclerView分页查询功能(仿照主流电商APP,可切换列表)相关推荐

  1. JavaWeb-综合案例(用户信息)-学习笔记05【分页查询功能】

    Java后端 学习路线 笔记汇总表[黑马程序员] JavaWeb-综合案例(用户信息)-学习笔记01[列表查询] JavaWeb-综合案例(用户信息)-学习笔记02[登录功能] JavaWeb-综合案 ...

  2. 【项目实战】使用MyBatis-Plus实现分页查询功能

    一.分页查询功能介绍 1.1 分页查询功能概述 常规查询全部出现的风险 : 分页查询将数据库中庞大的数据分段显示,每页显示用户自定义的行数,提高用户体验度,最主要的是如果一次性从服务器磁盘中读出全部数 ...

  3. php ajax实现查询功能,ajax实现分页查询功能

    这次给大家带来ajax实现分页查询功能,ajax实现分页查询功能的注意事项有哪些,下面就是实战案例,一起来看一下. ajax分页查询功能的具体代码,供大家参考,具体内容如下 显示的效果如下: 实现效果 ...

  4. rowbounds分页oracle,Oracle使用MyBatis中RowBounds实现分页查询功能

    Oracle中分页查询因为存在伪列rownum,sql语句写起来较为复杂,现在介绍一种通过使用MyBatis中的RowBounds进行分页查询,非常方便. 使用MyBatis中的RowBounds进行 ...

  5. jpa mysql sql分页查询语句_JPA多条件复杂SQL动态分页查询功能

    概述 ORM映射为我们带来便利的同时,也失去了较大灵活性,如果SQL较复杂,要进行动态查询,那必定是一件头疼的事情(也可能是lz还没发现好的方法),记录下自己用的三种复杂查询方式. 环境 spring ...

  6. spring data jpa实现有条件的分页查询功能

    spring data jpa实现有条件的分页查询功能 前端部分代码.发送请求: $('#grid').datagrid({iconCls: 'icon-forward',fit: true,bord ...

  7. spring data jpa实现分页查询功能

    spring data jpa实现分页查询功能 HTML代码部分: // 收派标准信息表格 $('#grid').datagrid( {iconCls : 'icon-forward',fit : t ...

  8. ajax查询功能查询源码,ajax实现分页查询功能

    ajax分页查询功能的详细代码,供大家参考,详细内容如下 显示的效果如下: 实现效果的代码如下: 1.fenye.php 无标题文档 .list:hover{ cursor:pointer} #pre ...

  9. 使用mybatis-plus如何实现分页查询功能

    今天就跟大家聊聊有关使用mybatis-plus如何实现分页查询功能,可能很多人都不太了解,为了让大家更加了解,小编给大家总结了以下内容,希望大家根据这篇文章可以有所收获. 引入依赖: <!-- ...

最新文章

  1. 苹果如果无法弹出⏏️
  2. 不要在viewWillDisappear:方法中移除通知
  3. 三维重建:SLAM的尺度和方法论问题
  4. h5适配华为手机_知道为什么建站大多选H5自适应网站吗?现在我就告诉你
  5. Spring Tool Suite 4 自动提示功能
  6. 需要显卡还是cpu_玩游戏卡顿,帧数低,是该升级显卡,还是升级cpu?
  7. Restlet 短连接问题
  8. logistic 回归分析
  9. JQuery中三元运算
  10. macbookair有没有touchbar_如何评价新 MacBook Pro 上的 Multi-Touch Bar?
  11. pc、h5微信授权登录
  12. Python 文件IO操作
  13. System.IO.FileNotFoundException: Could not load file or assembly ‘System.Data.SQLite.dll‘ or one of
  14. python bins分箱,划分数值区间
  15. 51.【Java String方法的小结】
  16. C#WinForm二维码编码解码器
  17. 为了甩锅,我写了个牛逼的日志切面!
  18. 【LeetCode】1652. 拆炸弹(C++)
  19. java中的IO流(字节流和字符流)----读写文件数据
  20. oracle adg性能,ADG设计及优化的最佳实践

热门文章

  1. Asp.Net 汉字转(拼音)
  2. C++贪吃蛇的简单实现版
  3. 2018年数据泄露事件
  4. 使用POI 导出word模板文件
  5. 深入理解时区概念:GMT,UTC,UTS和AOE
  6. 概率论计算圆周率(π)
  7. 程序员副业接单做私活避坑指南
  8. iOS企业证书的申请与制作
  9. Angular7入门辅助教程(一)——Angular模块
  10. 事件监听 ActionListener