blob: 408fdd475a94a5f54c0d8ad01410aff5277f3aaf [file] [log] [blame]
// Copyright 2014 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// @dart = 2.8
import 'package:flutter/foundation.dart';
import 'package:flutter/gestures.dart';
import 'package:flutter/rendering.dart';
import 'framework.dart';
import 'overscroll_indicator.dart';
import 'scroll_physics.dart';
const Color _kDefaultGlowColor = Color(0xFFFFFFFF);
/// Describes how [Scrollable] widgets should behave.
///
/// Used by [ScrollConfiguration] to configure the [Scrollable] widgets in a
/// subtree.
@immutable
class ScrollBehavior {
/// Creates a description of how [Scrollable] widgets should behave.
const ScrollBehavior();
/// The platform whose scroll physics should be implemented.
///
/// Defaults to the current platform.
TargetPlatform getPlatform(BuildContext context) => defaultTargetPlatform;
/// Wraps the given widget, which scrolls in the given [AxisDirection].
///
/// For example, on Android, this method wraps the given widget with a
/// [GlowingOverscrollIndicator] to provide visual feedback when the user
/// overscrolls.
Widget buildViewportChrome(BuildContext context, Widget child, AxisDirection axisDirection) {
// When modifying this function, consider modifying the implementation in
// _MaterialScrollBehavior as well.
switch (getPlatform(context)) {
case TargetPlatform.iOS:
case TargetPlatform.linux:
case TargetPlatform.macOS:
case TargetPlatform.windows:
return child;
case TargetPlatform.android:
case TargetPlatform.fuchsia:
return GlowingOverscrollIndicator(
child: child,
axisDirection: axisDirection,
color: _kDefaultGlowColor,
);
}
return null;
}
/// Specifies the type of velocity tracker to use in the descendant
/// [Scrollable]s' drag gesture recognizers, for estimating the velocity of a
/// drag gesture.
///
/// This can be used to, for example, apply different fling velocity
/// estimation methods on different platforms, in order to match the
/// platform's native behavior.
///
/// Typically, the provided [GestureVelocityTrackerBuilder] should return a
/// fresh velocity tracker. If null is returned, [Scrollable] creates a new
/// [VelocityTracker] to track the newly added pointer that may develop into
/// a drag gesture.
///
/// The default implementation provides a new
/// [IOSScrollViewFlingVelocityTracker] on iOS and macOS for each new pointer,
/// and a new [VelocityTracker] on other platforms for each new pointer.
GestureVelocityTrackerBuilder velocityTrackerBuilder(BuildContext context) {
switch (getPlatform(context)) {
case TargetPlatform.iOS:
case TargetPlatform.macOS:
return (PointerEvent event) => IOSScrollViewFlingVelocityTracker(event.kind);
case TargetPlatform.android:
case TargetPlatform.fuchsia:
case TargetPlatform.linux:
case TargetPlatform.windows:
return (PointerEvent event) => VelocityTracker.withKind(event.kind);
}
assert(false);
return (PointerEvent event) => VelocityTracker(event.kind);
}
static const ScrollPhysics _bouncingPhysics = BouncingScrollPhysics(parent: RangeMaintainingScrollPhysics());
static const ScrollPhysics _clampingPhysics = ClampingScrollPhysics(parent: RangeMaintainingScrollPhysics());
/// The scroll physics to use for the platform given by [getPlatform].
///
/// Defaults to [RangeMaintainingScrollPhysics] mixed with
/// [BouncingScrollPhysics] on iOS and [ClampingScrollPhysics] on
/// Android.
ScrollPhysics getScrollPhysics(BuildContext context) {
switch (getPlatform(context)) {
case TargetPlatform.iOS:
case TargetPlatform.macOS:
return _bouncingPhysics;
case TargetPlatform.android:
case TargetPlatform.fuchsia:
case TargetPlatform.linux:
case TargetPlatform.windows:
return _clampingPhysics;
}
return null;
}
/// Called whenever a [ScrollConfiguration] is rebuilt with a new
/// [ScrollBehavior] of the same [runtimeType].
///
/// If the new instance represents different information than the old
/// instance, then the method should return true, otherwise it should return
/// false.
///
/// If this method returns true, all the widgets that inherit from the
/// [ScrollConfiguration] will rebuild using the new [ScrollBehavior]. If this
/// method returns false, the rebuilds might be optimized away.
bool shouldNotify(covariant ScrollBehavior oldDelegate) => false;
@override
String toString() => objectRuntimeType(this, 'ScrollBehavior');
}
/// Controls how [Scrollable] widgets behave in a subtree.
///
/// The scroll configuration determines the [ScrollPhysics] and viewport
/// decorations used by descendants of [child].
class ScrollConfiguration extends InheritedWidget {
/// Creates a widget that controls how [Scrollable] widgets behave in a subtree.
///
/// The [behavior] and [child] arguments must not be null.
const ScrollConfiguration({
Key key,
@required this.behavior,
@required Widget child,
}) : super(key: key, child: child);
/// How [Scrollable] widgets that are descendants of [child] should behave.
final ScrollBehavior behavior;
/// The [ScrollBehavior] for [Scrollable] widgets in the given [BuildContext].
///
/// If no [ScrollConfiguration] widget is in scope of the given `context`,
/// a default [ScrollBehavior] instance is returned.
static ScrollBehavior of(BuildContext context) {
final ScrollConfiguration configuration = context.dependOnInheritedWidgetOfExactType<ScrollConfiguration>();
return configuration?.behavior ?? const ScrollBehavior();
}
@override
bool updateShouldNotify(ScrollConfiguration oldWidget) {
assert(behavior != null);
return behavior.runtimeType != oldWidget.behavior.runtimeType
|| (behavior != oldWidget.behavior && behavior.shouldNotify(oldWidget.behavior));
}
@override
void debugFillProperties(DiagnosticPropertiesBuilder properties) {
super.debugFillProperties(properties);
properties.add(DiagnosticsProperty<ScrollBehavior>('behavior', behavior));
}
}