因为NestedScrollView自定义下拉刷新组件有问题,上自身的一些问题,不能达到目前的业务要求,所以参考NestedScrollView 自己实现了一个.来实现scrollView内ListView的滚动联动和下拉刷新自定义的一个组件. 先看效果
主要实现思路共大家参考,大家也可以直接下载来使用
添加滚动协调类customScrollCoordinator,包含主视图、子视图scrollController,和主视图Position的引用. 在滚动时自定义Position跟据自身状态来调用协调类applyUserOffset方法,来实现主页面滚动和子页面滚动的响应者切换. 具体实现方法: 自定义customScrollController类,加入协调类引用,
class CustomScrollController extends ScrollController {
CustomScrollController(
this.coordinator, {
double initialScrollOffset = 0.0,
/// 每次滚动完成时,请使用 [PageStorage] 保存当前滚动 [offset] ,如果重新创建了此
/// 控制器的可滚动内容,则将其还原。
///
/// 如果将此属性设置为false,则永远不会保存滚动偏移量,
/// 并且始终使用 [initialScrollOffset] 来初始化滚动偏移量。如果为 true(默认值),
/// 则第一次创建控制器的可滚动对象时将使用初始滚动偏移量,因为尚无要还原的滚动偏移量。
/// 随后,将恢复保存的偏移,并且忽略[initialScrollOffset]。
///
/// 也可以看看:
/// * [PageStorageKey],当同一路径中出现多个滚动条时,应使用 [PageStorageKey]
/// 来区分用于保存滚动偏移量的 [PageStorage] 位置。
bool keepScrollOffset = true,
/// [toString] 输出中使用的标签。帮助在调试输出中标识滚动控制器实例。
String debugLabel,
}) : assert(initialScrollOffset != null),
assert(keepScrollOffset != null),
_initialScrollOffset = initialScrollOffset,
super(keepScrollOffset: keepScrollOffset, debugLabel: debugLabel);
final CustomScrollCoordinator coordinator;
主要就是为了添加coordinator引用 然后将方法createScrollPosition方法重写,改为返回自定义的customScrollPosition类,并传入协调类
@override
CustomScrollPosition createScrollPosition(
ScrollPhysics physics,
ScrollContext context,
ScrollPosition oldPosition,
) {
return CustomScrollPosition(
coordinator: coordinator,
physics: physics,
context: context,
initialPixels: initialScrollOffset,
keepScrollOffset: keepScrollOffset,
oldPosition: oldPosition,
debugLabel: debugLabel,
);
}
自定义scrollController完成,该类主要就是为了关联协调类和自定义position 第二部: 自定义协调类 customScrollCoordinator 该类管理scrollController,主要进行滚动的切换和滑动惯性的延续
///获取主页面滑动控制器
CustomScrollController mainScrollController([double initialOffset = 0.0]) {
assert(initialOffset != null, initialOffset >= 0);
_customScrollController = CustomScrollController(this, debugLabel: debugLabel, initialScrollOffset: initialOffset);
return _customScrollController;
}
///创建并获取子滑动控制器
CustomScrollController newChildScrollController([String debugLabel = 'innerSliding']) {
return CustomScrollController(this, debugLabel: debugLabel);
}
切换手势的响应方法,进行滚动响应的切换
/// 子部件滑动数据协调
/// [delta]滑动距离
/// [userScrollDirection]用户滑动方向
/// [position]被滑动的子部件的位置信息
void applyUserOffset(double delta,
[ScrollDirection userScrollDirection, CustomScrollPosition position]) {
if (userScrollDirection == ScrollDirection.reverse) {
/// 当用户滑动方向是向上滑动
updateUserScrollDirection(_customScrollPosition, userScrollDirection);
final innerDelta = _customScrollPosition.applyClampedDragUpdate(delta);
if (innerDelta != 0.0) {
updateUserScrollDirection(position, userScrollDirection);
position.applyFullDragUpdate(innerDelta);
}
} else {
/// 当用户滑动方向是向下滑动
// print('用户下拉 position${position.pixels}');
// print('_customScrollController.position.maxScrollExtent :${_customScrollController.position.maxScrollExtent}');
// print('_customScrollController.position.pixels :${_customScrollController.position.pixels}');
bool listviewAtTop = position.pixels == 0;
if (listviewAtTop) {
///listview到顶了,滚动main
updateUserScrollDirection(position, userScrollDirection);
final outerDelta = position.applyClampedDragUpdate(delta);
if (outerDelta != 0.0) {
updateUserScrollDirection(_customScrollPosition, userScrollDirection);
_customScrollPosition.applyFullDragUpdate(outerDelta);
}
} else {
///滚动listview
}
}
}
惯性的处理:
/// 以特定的速度开始一个物理驱动的模拟,该模拟确定 [pixels] 位置。
///
/// 此方法遵从 [ScrollPhysics.createBallisticSimulation],通常在当前位置超出范围时
/// 提供滑动模拟,而在当前位置超出范围但具有非零速度时提供摩擦模拟。
///
/// 速度应以 逻辑像素/秒 为单位。
void goBallistic(double velocity,{bool scrollInner = false}){
if (scrollInner) {
//需要滚动内部
// print('内部 收到滚动:$velocity 子scroll:${currentScrollController.position}');
currentScrollController.position.goBallistic(velocity);
} else {
//需要滚动外部
// print('外部 收到滚动:$velocity 子scroll:${currentScrollController.position}');
_customScrollPosition.goBallistic(velocity);
}
}
至此,协调类的主要方法完成 3,接下来实现自定义Position类 该类为主要数据获得的类,主滚动和子滚动的滚动事件都有该类处理,然后交给协调类进行滚动事件的切换. 首先需要把协调类引用进来
class CustomScrollPosition extends ScrollPosition
implements ScrollActivityDelegate {
CustomScrollPosition({
@required ScrollPhysics physics,
@required ScrollContext context,
double initialPixels = 0.0,
bool keepScrollOffset = true,
ScrollPosition oldPosition,
String debugLabel,
@required this.coordinator,
}
然后处理滑动的滚动事件,是交给协调类切换还是自身滚动由此方法处理
/// 当手指滑动时,该方法会获取到滑动距离。
///
/// [delta] 滑动距离,正增量表示下滑,负增量向上滑。
///
/// 我们需要把子部件的滑动数据交给协调器处理,主部件无干扰。
@override
void applyUserOffset(double delta) {
coordinator.coordinatorScrolling();
final ScrollDirection userScrollDirection = delta > 0.0 ? ScrollDirection.forward : ScrollDirection.reverse;
bool listviewAtTop = pixels == 0;
if (debugLabel != coordinator.debugLabel) {
//内部滚动
if(userScrollDirection == ScrollDirection.reverse){
//用户向上滑动
return coordinator.applyUserOffset(delta, userScrollDirection, this);
}else{
//用户向下滑动
if(listviewAtTop){
return coordinator.applyUserOffset(delta, userScrollDirection, this);
}
}
}
updateUserScrollDirection(userScrollDirection);
setPixels(pixels - physics.applyPhysicsToUserOffset(this, delta));
}
还有一个滚动惯性的处理,该方法控制滚动的惯性滚动事件由谁处理
/// 以特定的速度开始一个物理驱动的模拟,该模拟确定 [pixels] 位置。
/// 此方法遵从 [ScrollPhysics.createBallisticSimulation],该方法通常在当前位置超出
/// 范围时提供滑动模拟,而在当前位置超出范围但具有非零速度时提供摩擦模拟。
/// 速度应以逻辑像素/秒为单位。
@override
void goBallistic(double velocity, [bool fromCoordinator = false]) {
bool mainAtTop = coordinator.mainScrollViewAtTop();
bool listviewAtTop = pixels == 0;
if (debugLabel == coordinator.debugLabel) {
//主拖动
} else {
//子拖动
if (velocity >= 0) {
//上拉
if (!mainAtTop) {
print('1');
goIdle();
return coordinator.goBallistic(velocity);
}
} else {
//下拉
if (listviewAtTop) {
print('2');
goIdle();
return coordinator.goBallistic(velocity);
}
}
}
assert(pixels != null);
final Simulation simulation = physics.createBallisticSimulation(this, velocity);
if (simulation != null) {
beginActivity(BallisticScrollActivity(this, simulation, context.vsync));
} else {
goIdle();
}
}
对应的有个处理惯性速度的方法,比如速度很快的到边界了,接下来的滚动由谁继承和响应
@override
void goIdle() {
double velocity = 0;
if(activity != null) {
if (debugLabel == coordinator.debugLabel) {
//主滚动结束,但是有剩余速度
if (activity.velocity != 0) {
velocity = activity.velocity;
}
} else {
if (activity.velocity != 0) {
velocity = activity.velocity;
}
}
}
beginActivity(IdleScrollActivity(this));
if(velocity != 0){
if(debugLabel == coordinator.debugLabel){
// print('主list滚动结束-剩余转子list滚动:$velocity');
coordinator.goBallistic(velocity,scrollInner: true);
}else{
// print('子list滚动结束-剩余转主list滚动');
coordinator.goBallistic(velocity);
}
}
}
主要实现的思路和方法已经完成. 该方式可以完美的配合
EasyRefresh
的下拉刷新等操作 配合
CustomScrollView
实现滚动联动等多种效果 使用方法:
CustomScrollCoordinator _scrollCoordinator = CustomScrollCoordinator();
CustomScrollController _mainScrollController;
_mainScrollController = _scrollCoordinator.mainScrollController();
_scrollCoordinator.coordinatorScrolling = (){
floatingBallKey.currentState.scrollEvent();
};
CustomScrollView(
controller: _mainScrollController,
physics: physics,
slivers: []
如果有多个子滚动需要在listView切换时指定当前滚动的controller
_scrollCoordinator.currentScrollController = currentInnerScrollController;
具体实现详情可下载 download.csdn.net/download/an…