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

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

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

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

准备工作

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

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

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

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

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

class DataService { private val random = Random() private var dataModelEventHandler: EventHandler 
  
    ? = null fun registerLoadingEvent(dataModelEventHandler: EventHandler 
   
     ) { this.dataModelEventHandler = dataModelEventHandler } fun unregisterLoadingEvent() { this.dataModelEventHandler = null } / * 加载数据 */ fun fetch(start: Int, count: Int) { //延迟2s模拟刷新操作 Handler().postDelayed({ val rvListModel = getData(start, count) //分发消息,更新界面,会走到onDataLoaded dataModelEventHandler!!.dispatchEvent(rvListModel) }, 2000) } / * 刷新操作 */ fun reFetch(start: Int, count: Int) { Handler().postDelayed({ val rvListModel = getData(start, count) //分发消息,更新界面,会走到onDataLoaded dataModelEventHandler!!.dispatchEvent(rvListModel) }, 2000) } fun getData(start: Int, count: Int): RvListModel { val rvListModel = RvListModel() var list = mutableListOf 
    
      () 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_launcher rvItemBean.imageUrl = mImgUrls[0] rvItemBean.clickStr = "click$i" rvItemBean.isSubRv = true if (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//_0_2.png", "http://k.zol-img.com.cn/sjbbs/7692/a_s.jpg", "http://pic9.nipic.com//_3_2.jpg", "http://pic25.nipic.com//_00_2.jpg", "http://img1.imgtn.bdimg.com/it/u=,&fm=26&gp=0.jpg", "http://img2.imgtn.bdimg.com/it/u=,&fm=26&gp=0.jpg", "http://img4.imgtn.bdimg.com/it/u=,&fm=26&gp=0.jpg", "http://img3.imgtn.bdimg.com/it/u=,&fm=26&gp=0.jpg", "http://img1.imgtn.bdimg.com/it/u=,&fm=26&gp=0.jpg", "http://img3.imgtn.bdimg.com/it/u=,&fm=26&gp=0.jpg", "http://img3.imgtn.bdimg.com/it/u=,&fm=26&gp=0.jpg", "http://img5.imgtn.bdimg.com/it/u=,&fm=26&gp=0.jpg", "http://img1.imgtn.bdimg.com/it/u=,&fm=26&gp=0.jpg", "http://img2.imgtn.bdimg.com/it/u=,&fm=26&gp=0.jpg" ) } 
     
    
  

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

RecyclerView的使用

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

@LayoutSpec object SubRvItemSpec { @OnCreateLayout fun 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 { @OnCreateChildren fun onCreateChildren(c: SectionContext, @Prop list: List 
  
    ): 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 { @OnCreateLayout fun 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 
  
    { val images = ArrayList 
   
     () 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注解的变量名保持一致 */ @OnCreateInitialState fun createInitialState(c: SectionContext, list: StateValue 
  
    >, start: StateValue 
   
     , count: StateValue 
    
      , isFetching: StateValue 
     
       ) { start.set(0) count.set(15) list.set(DataService().getData(0, 15).list) isFetching.set(false) } / * 布局 */ @OnCreateChildren fun onCreateChildren(c: SectionContext, @State list: MutableList 
      
        ): 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() } / * 创建请求服务 */ @OnCreateService fun onCreateService(c: SectionContext, @State list: MutableList 
       
         , @State start: Int, @State count: Int): DataService { return DataService() } / * 绑定请求服务 */ @OnBindService fun onBindService(c: SectionContext, service: DataService) { service.registerLoadingEvent(RvSection.onDataLoaded(c)) } / * 解绑请求服务 */ @OnUnbindService fun onUnbindService(c: SectionContext, service: DataService) { service.unregisterLoadingEvent() } / * 接受到获取数据的消息 */ @OnEvent(RvListModel::class) fun onDataLoaded(c: SectionContext, @FromEvent list: MutableList 
        
          ) { //更新数据,看源码可以看到走到了updateData设置数据, // 然后调用了SectionTree的updateState,利用CalculateChangeSetRunnable,走到了calculateNewChangeSet, // 调用createNewTreeAndApplyStateUpdates,里面会调用nextRoot.createChildren更新视图。这里也体现了Litho的异步measure、layout RvSection.updateData(c, list) //设置已经获取数据 RvSection.setFetching(c, false) //发送刷新已经完成消息,即隐藏刷新进度条 SectionLifecycle.dispatchLoadingEvent(c, false, LoadingEvent.LoadingState.SUCCEEDED, null) } / * 更新数据 */ @OnUpdateState fun updateData(list: StateValue 
         
           >, start: StateValue 
          
            , @Param newList: MutableList 
           
             ) { if (start.get() == 0) { list.set(newList) } else { val listContain =mutableListOf 
            
              () listContain.addAll(list.get()!!) listContain.addAll(newList) list.set(listContain) } } / * 下拉刷新 */ @OnRefresh fun onRefresh(c: SectionContext, service: DataService, @State list: MutableList 
             
               , @State start: Int, @State count: Int) { RvSection.updateStartParam(c, 0) service.reFetch(0, 15) } / * 设置是否刷新,并保存到State中的isFetching属性 */ @OnUpdateState fun setFetching(isFetching: StateValue 
              
                , @Param fetch: Boolean) { isFetching.set(fetch) } / * 更新 */ @OnUpdateState fun updateStartParam(start: StateValue 
               
                 , @Param newStart: Int) { start.set(newStart) } / * 滑动监听,可以用来判断上拉加载 */ @OnViewportChanged fun onViewportChanged(c: SectionContext, firstVisiblePosition: Int, lastVisiblePosition: Int, firstFullyVisibleIndex: Int, lastFullyVisibleIndex: Int, totalCount: Int, service: DataService, @State list: MutableList 
                
                  , @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() } 
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请联系我们举报,一经查实,本站将立刻删除。

发布者:全栈程序员-站长,转载请注明出处:https://javaforall.net/211858.html原文链接:https://javaforall.net

(0)
上一篇 2026年3月18日 下午9:25
下一篇 2026年3月18日 下午9:26


相关推荐

  • Web安全-一句话木马

    Web安全-一句话木马概述在很多的渗透过程中,渗透人员会上传一句话木马(简称Webshell)到目前web服务目录继而提权获取系统权限,不论asp、php、jsp、aspx都是如此,那么一句话木马到底是什么呢?先来看看最简单的一句话木马:<?php@eval($_POST[‘attack’])?>【基本原理】利用文件上传漏洞,往目标网站中上传一句话木马,然后你就

    2022年5月11日
    37
  • Spring中bean的作用域与生命周期

    Spring中bean的作用域与生命周期在Spring中,那些组成应用程序的主体及由SpringIoC容器所管理的对象,被称之为bean。简单地讲,bean就是由IoC容器初始化、装配及管理的对象,除此之外,bean就与应用程序中的其他对象没有什么区别了。而bean的定义以及bean相互间的依赖关系将通过配置元数据来描述。  Spring中的bean默认都是单例的,这些单例Bean在多线程程序下如何保证线程安全呢?例如对于Web应用来

    2022年4月30日
    60
  • c++ int转char*

    c++ int转char*第一种方法 inti 0 charitc 10 sprintf itc d bmp i 第二种方法 inti 5 charerr str 10 itoa i err str 10

    2026年3月26日
    3
  • imfilter使用方法

    imfilter使用方法功能 对任意类型数组或多维图像进行滤波 用法 B imfilter A H B imfilter A H option1 option2 或写作 g imfilter f w filtering mode boundary options size options 其中 f 为输入图像 w 为滤波掩模 g 为滤波后图像 filtering mode 用

    2026年3月17日
    3
  • Mybatis-plus最新代码生成器(3.5.1+)的使用

    Mybatis-plus最新代码生成器(3.5.1+)的使用快速上手

    2026年3月18日
    1
  • 智谱发布AutoGLM沉思版:推动AI Agent进入「边想边干」阶段

    智谱发布AutoGLM沉思版:推动AI Agent进入「边想边干」阶段

    2026年3月12日
    2

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

关注全栈程序员社区公众号