Litho的使用–下拉刷新上拉加载

上篇介绍了Litho的基本使用,本文看下日常操作RecyclerView的嵌套使用以及下拉刷新上拉加载。

准备工作

先模拟一下网络请求以及使用的bean类

@Event
class RvListModel {//kotlin默认成员变量注解后,反射修饰符为private//@JvmField注解过后的对象,编译后修饰符变为默认修饰符public@JvmFieldvar list: MutableList<RvItemBean>? = null
}

这里的@Event注解后面会用到,注入列表数据的。其中RvItemBean:

class RvItemBean {var title: String? = nullvar content: String? = nullvar clickStr: String? = nullvar isSubRv: Boolean = false//item是否是子RecyclerViewvar iconRes: Int = 0var imageUrl: String? = null
}

bean类很简单,下面看下模拟的网络请求工具类:

class DataService {private val random = Random()private var dataModelEventHandler: EventHandler<RvListModel>? = nullfun registerLoadingEvent(dataModelEventHandler: EventHandler<RvListModel>) {this.dataModelEventHandler = dataModelEventHandler}fun unregisterLoadingEvent() {this.dataModelEventHandler = null}/*** 加载数据*/fun fetch(start: Int, count: Int) {//延迟2s模拟刷新操作Handler().postDelayed({val rvListModel = getData(start, count)//分发消息,更新界面,会走到onDataLoadeddataModelEventHandler!!.dispatchEvent(rvListModel)}, 2000)}/*** 刷新操作*/fun reFetch(start: Int, count: Int) {Handler().postDelayed({val rvListModel = getData(start, count)//分发消息,更新界面,会走到onDataLoadeddataModelEventHandler!!.dispatchEvent(rvListModel)}, 2000)}fun getData(start: Int, count: Int): RvListModel {val rvListModel = RvListModel()var  list = mutableListOf<RvListModel>()rvListModel.list = ArrayList()for (i in start until start + count) {val rvItemBean = getRvItemBean(i)rvListModel.list!!.add(rvItemBean)}return rvListModel}private fun getRvItemBean(i: Int): RvItemBean {val rvItemBean = RvItemBean()rvItemBean.title = "我是标题${(random.nextInt(10) + i)}"rvItemBean.content = "我是内容$i"rvItemBean.iconRes = R.mipmap.ic_launcherrvItemBean.imageUrl = mImgUrls[0]rvItemBean.clickStr = "click$i"rvItemBean.isSubRv = trueif (random.nextInt(10) % 3 == 0) {rvItemBean.imageUrl = mImgUrls[random.nextInt(mImgUrls.size)]rvItemBean.isSubRv = false}return rvItemBean}/*** 网络图片资源,便于随机取图片*/private val mImgUrls = arrayOf("http://pic37.nipic.com/20140113/8800276_184927469000_2.png","http://k.zol-img.com.cn/sjbbs/7692/a7691515_s.jpg","http://pic9.nipic.com/20100923/2531170_140325352643_2.jpg","http://pic25.nipic.com/20121205/10197997_003647426000_2.jpg","http://img1.imgtn.bdimg.com/it/u=1483033257,4287748004&fm=26&gp=0.jpg","http://img2.imgtn.bdimg.com/it/u=667158997,739004683&fm=26&gp=0.jpg","http://img4.imgtn.bdimg.com/it/u=852943962,2632646500&fm=26&gp=0.jpg","http://img3.imgtn.bdimg.com/it/u=1451961535,3314663922&fm=26&gp=0.jpg","http://img1.imgtn.bdimg.com/it/u=1533275126,1287779573&fm=26&gp=0.jpg","http://img3.imgtn.bdimg.com/it/u=2132929001,1562758156&fm=26&gp=0.jpg","http://img3.imgtn.bdimg.com/it/u=1239957118,2147373077&fm=26&gp=0.jpg","http://img5.imgtn.bdimg.com/it/u=3558462636,3096335901&fm=26&gp=0.jpg","http://img1.imgtn.bdimg.com/it/u=3893146502,314297687&fm=26&gp=0.jpg","http://img2.imgtn.bdimg.com/it/u=1409224092,1124266154&fm=26&gp=0.jpg")
}

已经做了简单的注释,一看就懂。

RecyclerView的使用

做了准备工作,直接开干,先造一个item的Spec

@LayoutSpec
object SubRvItemSpec {@OnCreateLayoutfun onCreateLayout(c: ComponentContext, @Prop imgRes: Int?): Component {return card(c){paddingDip(YogaEdge.ALL, 16f)backgroundColor(Color.WHITE)content(image(c){drawableRes(imgRes ?: R.mipmap.ic_launcher)})clickHandler(ImageViewComponent.onClick(c, "click native"))}.build()}@OnEvent(ClickEvent::class)fun onClick(c: ComponentContext, @FromEvent view: View, @Param someProp: String) {Toast.makeText(c.applicationContext, "click:$someProp", Toast.LENGTH_SHORT).show()}
}

Litho中的RecyclerView需要一个Section组件生成列表,如下:

@GroupSectionSpec
object SubRvSectionSpec {@OnCreateChildrenfun onCreateChildren(c: SectionContext, @Prop list: List<Int>): Children {val builder = Children.create()for (i in list.indices) {builder.child(SingleComponentSection.create(c).key(i.toString()).component(SubRvItem.create(c).imgRes(list[i]).build()))}return builder.build()}
}

编译后,会生成SubRvItem和SubRvSection,Litho使用RecyclerView是通过RecyclerCollectionComponent构建的:

recyclerCollectionComponent(c){heightDip(100f)recyclerConfiguration(ListRecyclerConfiguration.create().orientation(LinearLayoutManager.HORIZONTAL).reverseLayout(false).build())section(SubRvSection.create(SectionContext(c)).list(RvItemSpec.getImageArray(bean)).build())disablePTR(true)}.build()

其中disablePTR(true),设置为true,不要下拉刷新。这里就不再试了,最后运行一下嵌套的RecyclerView看下效果。

嵌套的RecyclerView

首先看item的spec:

@LayoutSpec
object RvItemSpec {@OnCreateLayoutfun onCreateLayout(c: ComponentContext, @Prop bean: RvItemBean): Component {return column(c){paddingDip(YogaEdge.ALL, 16f)backgroundColor(Color.WHITE)child(text(c){marginDip(YogaEdge.TOP, 8f)marginDip(YogaEdge.LEFT, 16f)marginDip(YogaEdge.RIGHT, 16f)marginDip(YogaEdge.BOTTOM, 8f)text(bean.title)textSizeSp(40f)})child(text(c){marginDip(YogaEdge.LEFT, 16f)marginDip(YogaEdge.RIGHT, 16f)marginDip(YogaEdge.BOTTOM, 8f)text(bean.content)textSizeSp(20f)})child(if (bean.isSubRv) getSubRv(c, bean) else getImgUrlComponent(c, bean))clickHandler(ImageViewComponent.onClick(c, bean.clickStr))}}/*** 图片组件*/private fun getImgUrlComponent(c: ComponentContext, bean: RvItemBean): GlideImage {return GlideImage.create(c).heightDip(200f).imageUrl(bean.imageUrl?:"").build()}/*** 嵌套的子RecyclerView组件*/private fun getSubRv(c: ComponentContext, bean: RvItemBean): Component {return recyclerCollectionComponent(c){heightDip(100f)recyclerConfiguration(ListRecyclerConfiguration.create().orientation(LinearLayoutManager.HORIZONTAL).reverseLayout(false).build())section(SubRvSection.create(SectionContext(c)).list(RvItemSpec.getImageArray(bean)).build())disablePTR(true)}.build()}/*** 获取子RecyclerView的图片列表,图片都用默认图片,这里只做个数的随机*/private fun getImageArray(bean: RvItemBean): List<Int> {val images = ArrayList<Int>()for (i in 0 until Random().nextInt(5) + 1) {images.add(bean.iconRes)}return images}@OnEvent(ClickEvent::class)fun onClick(c: ComponentContext, @FromEvent view: View, @Param someProp: String) {Toast.makeText(c.applicationContext, "click:$someProp", Toast.LENGTH_SHORT).show()}
}

主要是根据bean类的isSubRv字段判断是普通图片组件还是子RecyclerView组件。下面看下SectionSpec,这个是重头戏:

@GroupSectionSpec
object RvSectionSpec {private const val TAG = "RvSectionSpec"/*** 初始化数据* StateValue的变量名要与@State注解的变量名保持一致*/@OnCreateInitialStatefun createInitialState(c: SectionContext, list: StateValue<MutableList<RvItemBean>>, start: StateValue<Int>, count: StateValue<Int>, isFetching: StateValue<Boolean>) {start.set(0)count.set(15)list.set(DataService().getData(0, 15).list)isFetching.set(false)}/*** 布局*/@OnCreateChildrenfun onCreateChildren(c: SectionContext, @State list: MutableList<RvItemBean>): Children {val builder = Children.create()for (i in list.indices) {builder.child(SingleComponentSection.create(c).key(i.toString()).component(RvItem.create(c).bean(list[i]).build()))}//上拉加载的进度条builder.child(SingleComponentSection.create(c).component(ProgressLayout.create(c)).build())return builder.build()}/*** 创建请求服务*/@OnCreateServicefun onCreateService(c: SectionContext, @State list: MutableList<RvItemBean>, @State start: Int, @State count: Int): DataService {return DataService()}/*** 绑定请求服务*/@OnBindServicefun onBindService(c: SectionContext, service: DataService) {service.registerLoadingEvent(RvSection.onDataLoaded(c))}/*** 解绑请求服务*/@OnUnbindServicefun onUnbindService(c: SectionContext, service: DataService) {service.unregisterLoadingEvent()}/*** 接受到获取数据的消息*/@OnEvent(RvListModel::class)fun onDataLoaded(c: SectionContext, @FromEvent list: MutableList<RvItemBean>) {//更新数据,看源码可以看到走到了updateData设置数据,// 然后调用了SectionTree的updateState,利用CalculateChangeSetRunnable,走到了calculateNewChangeSet,// 调用createNewTreeAndApplyStateUpdates,里面会调用nextRoot.createChildren更新视图。这里也体现了Litho的异步measure、layoutRvSection.updateData(c, list)//设置已经获取数据RvSection.setFetching(c, false)//发送刷新已经完成消息,即隐藏刷新进度条SectionLifecycle.dispatchLoadingEvent(c, false, LoadingEvent.LoadingState.SUCCEEDED, null)}/*** 更新数据*/@OnUpdateStatefun updateData(list: StateValue<MutableList<RvItemBean>>, start: StateValue<Int>, @Param newList: MutableList<RvItemBean>) {if (start.get() == 0) {list.set(newList)} else {val listContain =mutableListOf<RvItemBean>()listContain.addAll(list.get()!!)listContain.addAll(newList)list.set(listContain)}}/*** 下拉刷新*/@OnRefreshfun onRefresh(c: SectionContext, service: DataService, @State list: MutableList<RvItemBean>, @State start: Int, @State count: Int) {RvSection.updateStartParam(c, 0)service.reFetch(0, 15)}/*** 设置是否刷新,并保存到State中的isFetching属性*/@OnUpdateStatefun setFetching(isFetching: StateValue<Boolean>, @Param fetch: Boolean) {isFetching.set(fetch)}/*** 更新*/@OnUpdateStatefun updateStartParam(start: StateValue<Int>, @Param newStart: Int) {start.set(newStart)}/*** 滑动监听,可以用来判断上拉加载*/@OnViewportChangedfun onViewportChanged(c: SectionContext, firstVisiblePosition: Int, lastVisiblePosition: Int, firstFullyVisibleIndex: Int, lastFullyVisibleIndex: Int, totalCount: Int, service: DataService, @State list: MutableList<RvItemBean>, @State start: Int, @State count: Int, @State isFetching: Boolean) {Log.e(TAG, "firstVisiblePosition=$firstVisiblePosition;lastVisiblePosition=$lastVisiblePosition;firstFullyVisibleIndex=$firstFullyVisibleIndex;lastFullyVisibleIndex=$lastFullyVisibleIndex;totalCount=$totalCount;list.size()=${list.size};start=$start count=$count isFetching=$isFetching")//滑动到最后一个位置的时候if (totalCount == list.size && !isFetching) {//上拉加载更多的判断RvSection.setFetching(c, true)RvSection.updateStartParam(c, list.size)service.fetch(list.size, count)}}
}

关键代码加了注释,下面再稍微说一下:

  1. @OnCreateInitialState注解的方法是初始数据的方法,可以看到从DataService获取数据。值得注意的一点:StateValue的变量名要与@State注解的变量名保持一致。
  2. @OnCreateChildren注解不用说了,创建布局,值得注意的是布局最下方追加了上拉加载的进度条。
  3. @OnCreateService,@OnBindService,@OnUnbindService注入管理服务的方法。
  4. @OnRefresh这是下拉刷新的关键代码,可以看出,就是在这里进行请求获取数据的。同时看下DataService的刷新方法reFetch里在请求成功调用了EventHandler.dispatchEvent(),分发消息,更新界面,会走到SectionSpec的onDataLoaded方法。
  5. 可以看到onDataLoaded的注解@OnEvent(RvListModel::class),而RvListModel类同时也有@Event注解,相对应。onDataLoaded方法中的注释已经相当详细了,更新完数据后,会再走到onCreateChildren更新视图。
  6. 上拉加载:主要是由@OnViewportChanged注解的方法实现,这个方法可以进行滑动监听,判断上拉至最后一个item,就加载数据,之后跟刷新比较类似,就是折腾一下数据。
    使用起来就相当简单了:
 /*** 下拉刷新上拉加载的RecyclerView复合组件* @param context* @return*/private fun getRvComponent(context: ComponentContext): Component {return RecyclerCollectionComponent.create(context).section(RvSection.create(SectionContext(context)).build()).build()}

下面看下效果:

附上demo地址:LithoDemo

Litho的使用--下拉刷新上拉加载相关推荐

  1. Flutter开发之ListView下拉刷新上拉加载更多(35)

    在Flutter开发之ListView组件(21) 文章中,我们了解了ListView组件的基本使用.但是数据比较少,没有涉及分页加载.而实际开发中,下拉刷新和分页加载几乎是所有APP的标配.在iOS ...

  2. 下拉加载 实现 java_[Java教程]iscroll5实现一个下拉刷新上拉加载的效果

    [Java教程]iscroll5实现一个下拉刷新上拉加载的效果 0 2016-08-24 15:00:08 直接上代码!!! * { margin: 0; padding: 0; } ul, li { ...

  3. 小程序在父组件执行子组件方法,可适用于下拉刷新上拉加载之后执行子组件方法

    当父组件引用了子组件的时候,会遇到父组件执行子组件的方法,比如下拉刷新上拉加载等事件只有在页面中才能检测到,但是获取数据的方法在子组件,这时就可以执行子组件方法. 思路很简单,类似于vue中给子组件加 ...

  4. 分享轮子-flutter下拉刷新上拉加载

    flutter下拉上拉组件轮子 什么是flutter? 首先说下flutter,估计这个应该挺多人没听过flutter这个框架,它是一个google推出的跨平台的移动应用UI框架,和React Nat ...

  5. 使用MJRefresh自定义下拉刷新,上拉加载动画

    有时候我们需要自己设置下拉刷新,上拉加载动画的实现,这里主要是记录下使用MJRefresh自定义下拉刷新,上拉加载动画..... 下拉刷新我们只需要继承MJRefreshGifHeader即可: 实现 ...

  6. android listview下拉刷新动画,android 安卓 listview 支持下拉刷新 上拉加载更多

    [1]重写listViewimport java.text.SimpleDateFormat; import java.util.Date; import com.example.testdddlea ...

  7. Android 下拉刷新上拉载入 多种应用场景 超级大放送(上)

    转载请标明原文地址:http://blog.csdn.net/yalinfendou/article/details/47707017 关于Android下拉刷新上拉载入,网上的Demo太多太多了,这 ...

  8. react-native 自定义 下拉刷新 / 上拉加载更多 组件

    1.封装 Scroller 组件 src/components/Scroller/index.js /*** 下拉刷新/上拉加载更多 组件(Scroller)*/ import React, {Com ...

  9. Android ListView 实现下拉刷新上拉加载

    转载请注明出处:http://blog.csdn.net/allen315410/article/details/39965327 1.简介 无疑,在Android开发中,ListView是使用非常频 ...

最新文章

  1. jQuery Autocomplete
  2. 英文论文中i.e.,e.g.,etc.的正确用法
  3. stm32 DMA 配置 串口程序
  4. python顺时针打印矩阵_python实现顺时针打印矩阵
  5. 1.1节 Buck Converter--降压转换器 part1
  6. [渝粤题库]陕西师范大学《幼儿园课程》(专科)作业
  7. alternate rows shading using conditional formatting
  8. linux下查看进程与线程
  9. 七:如何实现代理与日志清除技术
  10. SPSS如何验证是否符合正态分布
  11. 互联网深处有趣网站——进阶篇
  12. 插入数据 java_JAVA插入数据笔记
  13. php获取月初月末时间戳
  14. 专升本英语——语法知识——高频语法——第四节 定语从句(限制性定语从句-非限制性定语从句)【学习笔记】
  15. adb 连接安卓手机远程调试
  16. ROS踩坑|warning:clock skew detected. Your build may be incomplete
  17. 微型计算机中奇偶校验,奇偶校验位
  18. 这么骚的SQL进阶技巧,不怕被揍么?
  19. 如何利用DLL注入绕过火绒和360主动防御写入扇区?
  20. 元宇宙爆火,虚拟人的商业落地现实又残酷

热门文章

  1. overflow:hidden作用
  2. Solr 07 - Solr从MySQL数据库中导入数据 (Solr DIH的使用示例)
  3. handlebar JS模板使用笔记
  4. A hierarchical heterogeneous ant colony optimization based fingerprint recognition system
  5. bzoj 5308 [ZJOI2018] 胖
  6. 批量测试DNS访问速度的脚本
  7. Nginx配置——动静分离
  8. IOS OC IPA内购流程
  9. Qt与halcon联合开发实现基于形状的模板匹配
  10. 图像清晰度评价(C#)