如何实现水平滚动UICollectionView插入动画以中心为基准展开?
核心问题根因
- 界面抖动的本质:手动调用的滚动动画与NSDiffableDataSource的内置插入动画,在同一执行周期内同时修改
contentOffset,布局引擎逐帧计算时叠加冲突位移,最终造成界面跳变抖动。 - 默认插入行为的逻辑:UICollectionViewFlowLayout计算cell frame时,插入点右侧的所有单元格会整体累加一个单元格宽度的偏移,直接推动原有中心单元格滑出固定视口,无法实现中心位置不动的效果。
- 之前尝试方案的缺陷:传入旧的indexPath触发居中滚动时,更新流程中indexPath已经发生偏移,计算的目标位置本身就是错的,加上动画叠加自然效果很差。
分步实现方案
- 第一步:维护一个全局属性,记录当前处于视口正中心的单元格的模型唯一标识,不要存临时indexPath——因为快照更新后indexPath会动态变化。在每次用户拖拽滚动、布局完成的回调里,实时更新这个属性的值。
- 第二步:在生成新的diffable快照、调用
apply方法时,不要立刻在同步链路里触发滚动操作:- 带完成回调的系统版本,直接把滚动调整逻辑放在
apply(animatingDifferences:completion:)的完成闭包里执行 - 旧版本系统没有完成回调,用
DispatchQueue.main.async把滚动调整逻辑延后到下一个runloop执行,确保diffable的所有插入、移动动画完全跑完、布局稳定后再操作
- 带完成回调的系统版本,直接把滚动调整逻辑放在
- 第三步:在延后执行的调整逻辑里,关闭滚动动画,直接计算并赋值
contentOffset:- 拿之前记录的中心模型标识,找到它在当前数据源里对应的indexPath
- 取这个indexPath对应cell的frame中心点,结合collectionView视口的中心点(
bounds.midX),算出两者的水平差值 - 把这个差值累加到当前的
contentOffset.x上,直接赋值给collectionView的contentOffset,不需要走scrollToItem的动画流程
- 实际效果匹配:
- 若插入位置在原中心单元格左侧:插入完成后,原中心单元格的位置会相对视口向左偏移一个单元格宽度,计算得到的差值会推动整个collectionView向左平移一个单元格宽度,让更左侧的原有单元格滑出屏幕,最终保持原中心的单元格在视口正中心
- 若插入位置在原中心单元格右侧:插入完成后,原中心单元格的位置相对视口没有偏移,差值为0,不会产生额外位移,右侧新插入的单元格会正常右移,超出视口的原有右侧单元格自然滑出
避坑提示
不要把offset调整逻辑放在通用布局拦截方法(比如
layoutSubviews、targetContentOffsetForProposedContentOffset)里做全局逻辑,否则会和用户拖拽、惯性滚动等正常交互产生冲突,仅在需要固定视口的快照更新流程中触发调整即可。
内容的提问来源于stack exchange,提问作者smocer
相关产品推荐
相关产品推荐

