Litho RecyclerCollectionComponent 实战:基于 Sections 构建高性能 RecyclerView 列表
移动开发UI组件【免费下载链接】lithoA declarative framework for building efficient UIs on Android.项目地址https://gitcode.com/gh_mirrors/li/litho点击查看免费下载本指南面向使用 Litho 的 Java codegen 版 Sections API 的开发者系统讲解RecyclerCollectionComponent的创建方式、内置能力与关键配置。读完你将掌握如何把 Section 树快速接入RecyclerView、如何配置横向列表与网格、如何启用列表吸附Snapping、如何控制横向列表高度、如何开启/关闭下拉刷新以及如何通过 loading/empty/error 三个插槽完成数据加载状态的 UI 呈现。文中所有结论均以当前仓库源码litho-sections-widget、litho-widget为事实依据。:::caution 适用范围 本页讲解的是较老的 Java codegen 版 Sections API。如果你在 Kotlin 中新建列表应优先参考 Lazy Collection 文档。 :::RecyclerView是任何包含可滚动列表的 Android 应用的基础构件之一。Litho 官方推荐使用RecyclerCollectionComponent配合 Sections 基础教程 来轻松构建滚动列表。这套 API 既能支撑简单、同质化的 List也能支撑由多个数据源驱动的复杂、异构 List同时天然享受 Litho 的后台布局background layout与增量挂载incremental mount等特性。下文将逐一讲解RecyclerCollectionComponent的常用能力横向列表、网格、吸附、固定高度以及加载/空/错误三种状态的界面定制。创建一个 RecyclerCollectionComponent和任何其他组件一样RecyclerCollectionComponent通过 Builder 构建并作为子组件加入布局OnCreateLayout static Component onCreateLayout( final ComponentContext c) { return RecyclerCollectionComponent.create(c) .section(createSection()) .build(); }上述代码最终会渲染为一个RecyclerView其行rows由传入的section内容驱动。从源码实现看section是RecyclerCollectionComponentSpec.onCreateLayout中唯一的必填 prop其余所有能力均为可选 prop见 RecyclerCollectionComponentSpec.java。组件内部会把section作为根节点设置到一棵SectionTree上sectionTree.setRoot(section)并通过SectionBinderTarget把 Section 树产生的 changeset 交给底层的RecyclerBinder渲染。Batteries Included开箱即用的内置能力RecyclerCollectionComponent为列表场景内置了许多实用特性。以下是其全部可选 prop 及其默认值均来自 RecyclerCollectionComponentSpec.javaProp默认值说明recyclerConfigurationListRecyclerConfiguration.create().build()决定 LayoutManager 与 Binder 的配置对象nestedScrollingEnabledtrue是否允许嵌套滚动scrollBarStyleView.SCROLLBARS_INSIDE_OVERLAY滚动条样式overScrollModeView.OVER_SCROLL_ALWAYS过度滚动行为itemAnimatorNoUpdateItemAnimator条目动画需移除变化动画时保留默认值即可clipToPadding/clipChildrentrue/true裁剪行为incrementalMounttrue增量挂载开关refreshProgressBarColor0XFF4267B2蓝色下拉刷新进度条颜色isLeft/Right/Top/BottomFadingEnabledtrue四个方向的淡出边缘开关asyncStateUpdates/asyncPropUpdates/setRootAsyncfalse异步状态/属性更新开关此外还支持itemDecoration可多值、left/right/top/bottomPadding、fadingEdgeLength、edgeEffectFactory、recyclerViewId、recyclerContentDescription、touchInterceptor、itemTouchListener、onScrollListener可多值、loadEventsHandler、recyclerTouchEventHandler、sectionsViewLogger等 prop。它们会被直接透传给内部的Recycler组件。横向列表Horizontal ListsRecyclerCollectionComponent通过RecyclerConfigurationprop 决定使用哪种 LayoutManager。若未指定该 prop默认使用ListRecyclerConfiguration——它会创建一个垂直方向的LinearLayoutManager。要实现横向布局传入一个水平方向的ListRecyclerConfiguration即可final RecyclerCollectionComponentSpec.RecyclerConfiguration recyclerConfiguration new ListRecyclerConfiguration( LinearLayoutManager.HORIZONTAL, false /* reverse layout */); final Component component RecyclerCollectionComponent.create(c) .section(FooSection.create(new SectionContext(c)).build()) .recyclerConfiguration(recyclerConfiguration) .build();从 ListRecyclerConfiguration.java 源码可知Builder 的字段默认值为orientation LinearLayoutManager.VERTICAL、reverseLayout false、stackFromEnd false、snapMode SNAP_NONE。也就是说不传 orientation 时默认为垂直列表reverseLayout(true)会反转布局方向stackFromEnd(true)让内容从末尾堆叠如聊天列表从底部开始还可以通过linearLayoutInfoFactory(...)注入自定义的LinearLayoutInfo工厂。网格列表Grid Lists网格列表使用GridRecyclerConfiguration创建它会为RecyclerView生成一个GridLayoutManagerGridRecyclerConfiguration.create() .orientation(LinearLayoutManager.VERTICAL) .numColumns(BOOKMARKS_GRID_NUM_COLUMNS) .recyclerBinderConfiguration(RecyclerBinderConfiguration.create().build()) .build();对照 GridRecyclerConfiguration.java其 Builder 默认值包括orientation VERTICAL、numColumns 2、reverseLayout false、stackFromEnd false、allowMeasureOverride false、snapMode SNAP_NONE。常用的配置项还有reverseLayout(boolean)/stackFromEnd(boolean)布局方向与堆叠方式allowMeasureOverride(boolean)是否允许 Grid 测量被覆盖透传给GridLayoutInfogridLayoutInfoFactory(...)自定义GridLayoutInfo工厂snapMode(...)/snapHelper(...)与吸附相关见下文。吸附Snapping对于可横向滚动的列表吸附模式同样通过ListRecyclerConfiguration配置final RecyclerCollectionComponentSpec.RecyclerConfiguration recyclerConfiguration new ListRecyclerConfiguration( LinearLayoutManager.HORIZONTAL, false /* reverse layout */, SNAP_TO_START); final Component component RecyclerCollectionComponent.create(c) .section(FooSection.create(new SectionContext(c)).build()) .recyclerConfiguration(recyclerConfiguration) .build();其他可用的吸附选项还有SNAP_NONE、SNAP_TO_END和SNAP_TO_CENTER。这些常量定义在 SnapUtil.kt常量取值对应的 SnapHelperSNAP_NONEInt.MIN_VALUE无nullSNAP_TO_STARTLinearSmoothScroller.SNAP_TO_STARTStartSnapHelper支持 fling offset 与起始偏移SNAP_TO_ENDLinearSmoothScroller.SNAP_TO_END无nullSNAP_TO_CENTERInt.MAX_VALUEPagerSnapHelper翻页式吸附从源码看SNAP_TO_END与SNAP_NONE都不会挂载 SnapHelper也就是说SNAP_TO_END在ListRecyclerConfiguration下实际等同于关闭吸附而SNAP_TO_CENTER会启用PagerSnapHelper表现接近一页一页的翻页效果。另外仓库还额外提供了SNAP_TO_CENTER_CHILDLinearSnapHelper吸附到子项中心与SNAP_TO_CENTER_CHILD_WITH_CUSTOM_SPEED自定义速度的 LinearSnapHelper并通过SnapUtil.getSnapHelper(snapMode, deltaJumpThreshold, snapToStartFlingOffset, snapToStartOffset, isStrictMode)统一构建。纵向列表的限制ListRecyclerConfiguration的校验逻辑validate 方法规定垂直方向只允许SNAP_NONE、SNAP_TO_START、SNAP_TO_CENTER否则抛出UnsupportedOperationException(Only snap to start is implemented for vertical lists)GridRecyclerConfiguration同样限制垂直网格只能使用SNAP_NONE或SNAP_TO_START。此外ListRecyclerConfiguration.Builder还暴露了若干精细调参项deltaJumpThreshold(int)硬滑动时的跳跃阈值默认Integer.MAX_VALUEsnapToStartFlingOffset(int)StartSnapHelper的 fling 偏移默认SNAP_TO_START_DEFAULT_FLING_OFFSET 1snapToStartOffset(int)吸附到起始位置的偏移默认0isStrictMode(boolean)控制轮播式 H-Scroll 行为——开启时硬滑动从卡片 N 跳到 Nxx 为deltaJumpThreshold关闭时跳到 Nx1默认falsesnapHelper(SnapHelper)直接注入自定义SnapHelper优先级高于snapMode。设置横向 RecyclerCollectionComponent 的高度横向滚动的RecyclerCollectionComponent有三种设置高度的方法完整讲解见 Horizontal Scrolling and Measurement固定高度法Fixed height在 H-Scroll 组件上直接设置固定高度。这是性能最优的方法建议尽可能采用。未知高度法Unknown height创建组件时高度未知让 H-Scroll 以第一个 item 的高度作为自身高度。动态高度法Dynamic height让 H-Scroll 动态改变高度以适配最高的 item。这是性能最差的方法。固定高度法——通过heightprop 设置固定高度RecyclerCollectionComponent.create(c) .recyclerConfiguration( ListRecyclerConfiguration.create() .orientation(OrientationHelper.HORIZONTAL) .build()) .section(DataDiffSection.createInt(SectionContext(c)) .data(colors) .renderEventHandler(FixedHeightHscrollComponent.onRender(c)) .build()) .heightDip(150f) .build()子项最多以 H-Scroll 的高度被测量更高的子项会被裁剪更矮的子项则对齐在起始位置未知高度法——不传heightprop而是通过RecyclerBinderConfig的crossAxisWrapMode设置为CrossAxisWrapMode.MatchFirstChild让 H-Scroll 以第一个子项的高度自我测量RecyclerCollectionComponent.create(c) .recyclerConfiguration( ListRecyclerConfiguration.create() .orientation(OrientationHelper.HORIZONTAL) .recyclerBinderConfiguration( RecyclerBinderConfiguration.create() .recyclerBinderConfig( RecyclerBinderConfig( crossAxisWrapMode CrossAxisWrapMode.MatchFirstChild)) .build()) .build()) .section(...) .build()该测量只在 H-Scroll 首次测量时发生之后高度不可改变其他子项高度以 H-Scroll 高度为上限并定位在起始位置⚠️ 如果既不在RecyclerCollectionComponent上设置非零高度、也不修改crossAxisWrapMode其默认值是CrossAxisWrapMode.NoWrapH-Scroll 的高度最终会是0。这一点在 hscrolls.mdx 中有明确警告。动态高度法——性能最差仅在绝对必要时使用尤其应避免用于无限滚动的列表。将crossAxisWrapMode设为CrossAxisWrapMode.Dynamic后H-Scroll 的初始高度由最高的子项决定当某个子项想变得比当前高度更高时H-Scroll 会按新高度重新测量而其他子项不会被重测当最高子项收缩时H-Scroll 会重新测量所有子项来确定新高度。由于每次插入新 item 都可能触发全量重测无限加载场景下成本很高。下拉刷新Pull to RefreshRecyclerCollectionComponent默认启用下拉刷新并向底层的Recycler发送一个事件处理器用于在SectionTree上触发刷新。要禁用该功能把disablePTRprop 设为truefinal Component component RecyclerCollectionComponent.create(c) .section(FooSection.create(new SectionContext(c)).build()) .recyclerConfiguration(recyclerConfiguration) .disablePTR(true) .build();从源码RecyclerCollectionComponentSpec.java看PTR 的实际可用性由两个条件共同决定final boolean canPTR recyclerConfiguration.getOrientation() ! OrientationHelper.HORIZONTAL !disablePTR;即横向列表上默认就没有下拉刷新只有纵向列表在disablePTR false时才启用。触发刷新时会回调refreshContent(c, sectionTree, ignoreLoadingUpdates)最终调用sectionTree.refresh()若设置了ignoreLoadingUpdates且存在外部PTRRefreshEvent处理器则会先尝试派发该事件、未被处理时才回落到sectionTree.refresh()。刷新进度条颜色可通过refreshProgressBarColor默认0XFF4267B2与refreshProgressBarBackgroundColor两个ResType.COLORprop 定制。加载中、空、错误界面Loading / Empty / Error screens在 Sections API 中数据获取可以通过 loading eventsLoadingState 和 services 集成。RecyclerCollectionComponent会监听这些加载事件并做出响应通过三个 prop 指定不同数据获取阶段展示的内容loadingComponent— 数据正在加载且列表中还没有任何内容时显示emptyComponent— 数据加载完成但没有任何内容可展示时显示errorComponent— 数据加载失败且列表中没有任何内容时显示。final Component component RecyclerCollectionComponent.create(c) .section(FooSection.create(new SectionContext(c)).build()) .recyclerConfiguration(recyclerConfiguration) .loadingComponent( Progress.create(c) .build()) .errorComponent( Text.create(c) .text(Data Fetch has failed).build()) .emptyComponent( Text.create(c) .text(No data to show).build()) .build();底层行为见 RecyclerCollectionComponentSpec.java组件内部维护一个LoadingState枚举LOADING/LOADED/EMPTY/ERROR初始状态为LOADINGRecyclerCollectionLoadEventsHandler监听 Section 树冒泡上来的加载事件onLoadStarted→ 更新为LOADING若数据为空或LOADEDonLoadSucceeded→EMPTY或LOADEDonLoadFailed→ERROR或LOADED并通过updateLoadingStateAsync触发状态更新布局时loading/empty/error 组件会被包装在Wrapper中以positionType(ABSOLUTE) 全边距 0 的方式覆盖在 Recycler 之上如果处于EMPTY或ERROR状态但对应的emptyComponent/errorComponent未传入组件会直接返回null什么都不渲染loadingComponent未传入时则仅显示空列表。注意ignoreLoadingUpdates是一个临时性 workaround开启后会直接跳过 loading/empty/error 组件逻辑状态被强制设为LOADED源码注释明确警告它“会破坏 loadingComponent/errorComponent/emptyComponent”不建议常规使用。深入底层组件是如何被组织起来的要理解上述能力可以看一下createInitialStateRecyclerCollectionComponentSpec.java中的组装流程从recyclerConfiguration.getLayoutInfo(c)得到LayoutInfoLinearLayoutInfo或GridLayoutInfo基于RecyclerBinderConfiguration构建RecyclerBinder可注入stickyHeaderControllerFactory、startupLogger、poolScope、PostDispatchDrawListener列表等用SectionBinderTarget把 Binder 包装为 Section 树的 Target并据此创建SectionTree其标签tag默认取section.getSimpleName()可通过sectionTreeTag覆盖创建RecyclerCollectionEventsController内部控制器与RecyclerCollectionLoadEventsHandler并将后者注册为 SectionTree 的LoadEventsHandler注册ViewportInfo.ViewportChanged监听把视口变化首/末可见索引、首/末完全可见索引、状态转发给sectionTree.viewPortChanged(...)为 Working Ranges预取与分页 提供基础。其中RecyclerBinderConfigurationRecyclerBinderConfiguration.java本身也是一个可配置对象支持recyclerBinderConfig(RecyclerBinderConfig)透传给底层 Binder 的完整配置包含wrapContent、crossAxisWrapMode、componentsConfiguration等见上文高度一节的使用方式useBackgroundChangeSets(boolean)是否在后台线程计算 changeset默认来自SectionsConfiguration.useBackgroundChangeSets实验性 APIchangeSetThreadHandler(RunnableHandler)自定义 changeset 计算线程postToFrontOfQueueForFirstChangeset(boolean)首个 changeset 是否插队到队列最前primitiveRecyclerBinderStrategy(...)实验性的 Primitive Recycler Binder 策略。在交互层面RecyclerCollectionComponentSpec还定义了三个 Trigger 事件ScrollEvent通过OnTrigger(ScrollEvent.class)处理内部调用sectionTree.requestFocusOnRoot(position)完成滚动RecyclerDynamicConfigEvent动态调整提交策略CommitPolicyClearRefreshingEvent清除刷新状态。这也印证了文档 communicating-with-the-ui.md 中描述的滚动方式从 Section 内部触发滚动应使用SectionLifecycle.requestFocus(SectionContext, int)系列方法含requestFocusWithOffset与按sectionKey聚焦的变体且目标数据必须先通过OnDataBound就绪requestFocus才能生效。小结与延伸阅读RecyclerCollectionComponent是 Litho Sections API 中连接“声明式 Section 树”与“原生RecyclerView”的桥梁它默认帮你处理 LayoutManager 选择、后台布局、增量挂载、视口变化上报、下拉刷新与加载状态呈现同时通过RecyclerConfiguration及其内部的RecyclerBinderConfiguration保留了高度的自定义空间。如果想继续深入仓库中相关的后续阅读路径包括Sections 基础教程Sections Basics理解DiffSection、SingleComponentSection、GroupSection如何组成列表结构Horizontal Scrolling and Measurement横向列表三种高度方案的完整对比与示例本文高度一节即提炼自该页Sections 与 UI 通信Communicating with the UILoadingState加载事件、requestFocus滚动与refresh刷新Best Practices and Performance数据 diff 的性能最佳实践Services 与 Working Ranges依赖注入、预取与分页等进阶主题。核心实现源码可进一步研读 RecyclerCollectionComponentSpec.java、ListRecyclerConfiguration.java、GridRecyclerConfiguration.java 与 SnapUtil.kt。赞分享移动开发UI组件【免费下载链接】lithoA declarative framework for building efficient UIs on Android.项目地址https://gitcode.com/gh_mirrors/li/litho点击查看免费下载相关推荐Litho Sections框架构建高性能列表的最佳实践Litho Sections框架构建高性能列表的最佳实践 本文深入探讨了Litho Sections框架的核心组件和优化机制包括RecyclerCollec移动开发UI组件MimeKit终极指南.NET平台最强大的MIME创建与解析库MimeKit终极指南.NET平台最强大的MIME创建与解析库 MimeKit是一款功能全面的.NET MIME创建与解析库支持S/MIME、PGP、DKI后端终极指南如何用Android Advanced RecyclerView打造高性能聊天列表终极指南如何用Android Advanced RecyclerView打造高性能聊天列表 Android Advanced RecyclerView是一个功移动开发UI组件上一篇暗黑破坏神2高清补丁D2DX深度实测25帧老游戏如何榨出144帧附完整调优指南下一篇跑通完整流程才算真正验证learn-harness-engineering 中的端到端测试与架构边界工程创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考