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) } } }
关键代码加了注释,下面再稍微说一下:
- @OnCreateInitialState注解的方法是初始数据的方法,可以看到从DataService获取数据。值得注意的一点:StateValue的变量名要与@State注解的变量名保持一致。
- @OnCreateChildren注解不用说了,创建布局,值得注意的是布局最下方追加了上拉加载的进度条。
- @OnCreateService,@OnBindService,@OnUnbindService注入管理服务的方法。
- @OnRefresh这是下拉刷新的关键代码,可以看出,就是在这里进行请求获取数据的。同时看下DataService的刷新方法reFetch里在请求成功调用了EventHandler.dispatchEvent(),分发消息,更新界面,会走到SectionSpec的onDataLoaded方法。
- 可以看到onDataLoaded的注解@OnEvent(RvListModel::class),而RvListModel类同时也有@Event注解,相对应。onDataLoaded方法中的注释已经相当详细了,更新完数据后,会再走到onCreateChildren更新视图。
- 上拉加载:主要是由@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
