Commit 3681aee5 authored by Hans Muller's avatar Hans Muller Committed by GitHub

Selectively enable page scrolling in the gallery animation demo (#9845)

parent 6337a055
...@@ -313,7 +313,7 @@ class _AllSectionsView extends AnimatedWidget { ...@@ -313,7 +313,7 @@ class _AllSectionsView extends AnimatedWidget {
(midHeight - minHeight)).clamp(0.0, 1.0); (midHeight - minHeight)).clamp(0.0, 1.0);
double _indicatorOpacity(int index) { double _indicatorOpacity(int index) {
return 1.0 - _selectedIndexDelta(index) * tColumnToRow * 0.5; return 1.0 - _selectedIndexDelta(index) * 0.5;
} }
double _titleOpacity(int index) { double _titleOpacity(int index) {
...@@ -369,16 +369,15 @@ class _AllSectionsView extends AnimatedWidget { ...@@ -369,16 +369,15 @@ class _AllSectionsView extends AnimatedWidget {
// app bar's height is _kAppBarMidHeight and only one section heading is // app bar's height is _kAppBarMidHeight and only one section heading is
// visible. // visible.
class _SnappingScrollPhysics extends ClampingScrollPhysics { class _SnappingScrollPhysics extends ClampingScrollPhysics {
_SnappingScrollPhysics({ ScrollPhysics parent, this.midScrollOffset }) : super(parent: parent); _SnappingScrollPhysics({ ScrollPhysics parent, this.midScrollOffset }) : super(parent: parent) {
assert(midScrollOffset != null);
}
final double midScrollOffset; final double midScrollOffset;
@override @override
_SnappingScrollPhysics applyTo(ScrollPhysics parent) { _SnappingScrollPhysics applyTo(ScrollPhysics ancestor) {
return new _SnappingScrollPhysics( return new _SnappingScrollPhysics(parent: buildParent(ancestor), midScrollOffset: midScrollOffset);
parent: parent,
midScrollOffset: midScrollOffset
);
} }
Simulation _toMidScrollOffsetSimulation(double offset, double dragVelocity) { Simulation _toMidScrollOffsetSimulation(double offset, double dragVelocity) {
...@@ -436,6 +435,7 @@ class _AnimationDemoHomeState extends State<AnimationDemoHome> { ...@@ -436,6 +435,7 @@ class _AnimationDemoHomeState extends State<AnimationDemoHome> {
final ScrollController _scrollController = new ScrollController(); final ScrollController _scrollController = new ScrollController();
final PageController _headingPageController = new PageController(); final PageController _headingPageController = new PageController();
final PageController _detailsPageController = new PageController(); final PageController _detailsPageController = new PageController();
ScrollPhysics _headingScrollPhysics = const NeverScrollableScrollPhysics();
ValueNotifier<double> selectedIndex = new ValueNotifier<double>(0.0); ValueNotifier<double> selectedIndex = new ValueNotifier<double>(0.0);
@override @override
...@@ -449,6 +449,22 @@ class _AnimationDemoHomeState extends State<AnimationDemoHome> { ...@@ -449,6 +449,22 @@ class _AnimationDemoHomeState extends State<AnimationDemoHome> {
); );
} }
// Only enable paging for the heading when the user has scrolled to midScrollOffset.
// Paging is enabled/disabled by setting the heading's PageView scroll physics.
bool _handleScrollNotification(ScrollNotification notification, double midScrollOffset) {
if (notification.depth == 0 && notification is ScrollUpdateNotification) {
final ScrollPhysics physics = _scrollController.position.pixels >= midScrollOffset
? const PageScrollPhysics()
: const NeverScrollableScrollPhysics();
if (physics != _headingScrollPhysics) {
setState(() {
_headingScrollPhysics = physics;
});
}
}
return false;
}
void _maybeScroll(double midScrollOffset, int pageIndex, double xOffset) { void _maybeScroll(double midScrollOffset, int pageIndex, double xOffset) {
const Duration duration = const Duration(milliseconds: 400); const Duration duration = const Duration(milliseconds: 400);
const Curve curve = Curves.fastOutSlowIn; const Curve curve = Curves.fastOutSlowIn;
...@@ -532,7 +548,11 @@ class _AnimationDemoHomeState extends State<AnimationDemoHome> { ...@@ -532,7 +548,11 @@ class _AnimationDemoHomeState extends State<AnimationDemoHome> {
return new SizedBox.expand( return new SizedBox.expand(
child: new Stack( child: new Stack(
children: <Widget>[ children: <Widget>[
new CustomScrollView( new NotificationListener<ScrollNotification>(
onNotification: (ScrollNotification notification) {
return _handleScrollNotification(notification, appBarMidScrollOffset);
},
child: new CustomScrollView(
controller: _scrollController, controller: _scrollController,
physics: new _SnappingScrollPhysics(midScrollOffset: appBarMidScrollOffset), physics: new _SnappingScrollPhysics(midScrollOffset: appBarMidScrollOffset),
slivers: <Widget>[ slivers: <Widget>[
...@@ -552,6 +572,7 @@ class _AnimationDemoHomeState extends State<AnimationDemoHome> { ...@@ -552,6 +572,7 @@ class _AnimationDemoHomeState extends State<AnimationDemoHome> {
return _handlePageNotification(notification, _headingPageController, _detailsPageController); return _handlePageNotification(notification, _headingPageController, _detailsPageController);
}, },
child: new PageView( child: new PageView(
physics: _headingScrollPhysics,
controller: _headingPageController, controller: _headingPageController,
children: _allHeadingItems(appBarMaxHeight, appBarMidScrollOffset), children: _allHeadingItems(appBarMaxHeight, appBarMidScrollOffset),
), ),
...@@ -580,6 +601,7 @@ class _AnimationDemoHomeState extends State<AnimationDemoHome> { ...@@ -580,6 +601,7 @@ class _AnimationDemoHomeState extends State<AnimationDemoHome> {
), ),
], ],
), ),
),
new Positioned( new Positioned(
top: statusBarHeight, top: statusBarHeight,
left: 0.0, left: 0.0,
......
...@@ -233,7 +233,9 @@ class PageScrollPhysics extends ScrollPhysics { ...@@ -233,7 +233,9 @@ class PageScrollPhysics extends ScrollPhysics {
const PageScrollPhysics({ ScrollPhysics parent }) : super(parent: parent); const PageScrollPhysics({ ScrollPhysics parent }) : super(parent: parent);
@override @override
PageScrollPhysics applyTo(ScrollPhysics parent) => new PageScrollPhysics(parent: parent); PageScrollPhysics applyTo(ScrollPhysics ancestor) {
return new PageScrollPhysics(parent: buildParent(ancestor));
}
double _getPage(ScrollPosition position) { double _getPage(ScrollPosition position) {
if (position is _PagePosition) if (position is _PagePosition)
......
...@@ -37,13 +37,35 @@ class ScrollPhysics { ...@@ -37,13 +37,35 @@ class ScrollPhysics {
/// [ScrollPhysics] subclasses at runtime. /// [ScrollPhysics] subclasses at runtime.
final ScrollPhysics parent; final ScrollPhysics parent;
/// Return a [ScrollPhysics] with the same [runtimeType] where the [parent] /// If [parent] is null then return ancestor, otherwise recursively build a
/// has been replaced with the given [parent]. /// ScrollPhysics that has [ancestor] as its parent.
///
/// This method is typically used to define [applyTo] methods like:
/// ```dart
/// FooScrollPhysics applyTo(ScrollPhysics ancestor) {
/// return new FooScrollPhysics(parent: buildParent(ancestor));
/// }
/// ```
@protected
ScrollPhysics buildParent(ScrollPhysics ancestor) => parent?.applyTo(ancestor) ?? ancestor;
/// If [parent] is null then return a [ScrollPhysics] with the same
/// [runtimeType] where the [parent] has been replaced with the [ancestor].
///
/// If this scroll physics object already has a parent, then this method
/// is applied recursively and ancestor will appear at the end of the
/// existing chain of parents.
/// ///
/// The returned object will combine some of the behaviors from this /// The returned object will combine some of the behaviors from this
/// [ScrollPhysics] instance and some of the behaviors from the given /// [ScrollPhysics] instance and some of the behaviors from [ancestor].
/// [ScrollPhysics] instance. ///
ScrollPhysics applyTo(ScrollPhysics parent) => new ScrollPhysics(parent: parent); /// See also:
///
/// * [buildParent], a utility method that's often used to define [applyTo]
/// methods for ScrollPhysics subclasses.
ScrollPhysics applyTo(ScrollPhysics ancestor) {
return new ScrollPhysics(parent: buildParent(ancestor));
}
/// Used by [DragScrollActivity] and other user-driven activities to /// Used by [DragScrollActivity] and other user-driven activities to
/// convert an offset in logical pixels as provided by the [DragUpdateDetails] /// convert an offset in logical pixels as provided by the [DragUpdateDetails]
...@@ -198,7 +220,9 @@ class BouncingScrollPhysics extends ScrollPhysics { ...@@ -198,7 +220,9 @@ class BouncingScrollPhysics extends ScrollPhysics {
const BouncingScrollPhysics({ ScrollPhysics parent }) : super(parent: parent); const BouncingScrollPhysics({ ScrollPhysics parent }) : super(parent: parent);
@override @override
BouncingScrollPhysics applyTo(ScrollPhysics parent) => new BouncingScrollPhysics(parent: parent); BouncingScrollPhysics applyTo(ScrollPhysics ancestor) {
return new BouncingScrollPhysics(parent: buildParent(ancestor));
}
/// The multiple applied to overscroll to make it appear that scrolling past /// The multiple applied to overscroll to make it appear that scrolling past
/// the edge of the scrollable contents is harder than scrolling the list. /// the edge of the scrollable contents is harder than scrolling the list.
...@@ -277,7 +301,9 @@ class ClampingScrollPhysics extends ScrollPhysics { ...@@ -277,7 +301,9 @@ class ClampingScrollPhysics extends ScrollPhysics {
const ClampingScrollPhysics({ ScrollPhysics parent }) : super(parent: parent); const ClampingScrollPhysics({ ScrollPhysics parent }) : super(parent: parent);
@override @override
ClampingScrollPhysics applyTo(ScrollPhysics parent) => new ClampingScrollPhysics(parent: parent); ClampingScrollPhysics applyTo(ScrollPhysics ancestor) {
return new ClampingScrollPhysics(parent: buildParent(ancestor));
}
@override @override
double applyBoundaryConditions(ScrollMetrics position, double value) { double applyBoundaryConditions(ScrollMetrics position, double value) {
...@@ -359,8 +385,34 @@ class AlwaysScrollableScrollPhysics extends ScrollPhysics { ...@@ -359,8 +385,34 @@ class AlwaysScrollableScrollPhysics extends ScrollPhysics {
const AlwaysScrollableScrollPhysics({ ScrollPhysics parent }) : super(parent: parent); const AlwaysScrollableScrollPhysics({ ScrollPhysics parent }) : super(parent: parent);
@override @override
AlwaysScrollableScrollPhysics applyTo(ScrollPhysics parent) => new AlwaysScrollableScrollPhysics(parent: parent); AlwaysScrollableScrollPhysics applyTo(ScrollPhysics ancestor) {
return new AlwaysScrollableScrollPhysics(parent: buildParent(ancestor));
}
@override @override
bool shouldAcceptUserOffset(ScrollMetrics position) => true; bool shouldAcceptUserOffset(ScrollMetrics position) => true;
} }
/// Scroll physics that does not allow the user to scroll.
///
/// See also:
///
/// * [ScrollPhysics], which can be used instead of this class when the default
/// behavior is desired instead.
/// * [BouncingScrollPhysics], which provides the bouncing overscroll behavior
/// found on iOS.
/// * [ClampingScrollPhysics], which provides the clamping overscroll behavior
/// found on Android.
class NeverScrollableScrollPhysics extends ScrollPhysics {
/// Creates scroll physics that does not let the user scroll.
const NeverScrollableScrollPhysics({ ScrollPhysics parent }) : super(parent: parent);
@override
NeverScrollableScrollPhysics applyTo(ScrollPhysics ancestor) {
return new NeverScrollableScrollPhysics(parent: buildParent(ancestor));
}
@override
bool shouldAcceptUserOffset(ScrollMetrics position) => false;
}
...@@ -271,8 +271,16 @@ class ScrollableState extends State<Scrollable> with TickerProviderStateMixin ...@@ -271,8 +271,16 @@ class ScrollableState extends State<Scrollable> with TickerProviderStateMixin
} }
bool _shouldUpdatePosition(Scrollable oldWidget) { bool _shouldUpdatePosition(Scrollable oldWidget) {
return widget.physics?.runtimeType != oldWidget.physics?.runtimeType ScrollPhysics newPhysics = widget.physics;
|| widget.controller?.runtimeType != oldWidget.controller?.runtimeType; ScrollPhysics oldPhysics = oldWidget.physics;
do {
if (newPhysics?.runtimeType != oldPhysics?.runtimeType)
return true;
newPhysics = newPhysics?.parent;
oldPhysics = oldPhysics?.parent;
} while (newPhysics != null || oldPhysics != null);
return widget.controller?.runtimeType != oldWidget.controller?.runtimeType;
} }
@override @override
......
// Copyright 2017 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'package:flutter/widgets.dart';
import 'package:flutter_test/flutter_test.dart';
class TestScrollPhysics extends ScrollPhysics {
const TestScrollPhysics({ this.name, ScrollPhysics parent }) : super(parent: parent);
final String name;
@override
TestScrollPhysics applyTo(ScrollPhysics ancestor) {
return new TestScrollPhysics(name: name, parent: parent?.applyTo(ancestor) ?? ancestor);
}
TestScrollPhysics get namedParent => parent;
String get names => parent == null ? name : '$name ${namedParent.names}';
@override
String toString() {
if (parent == null)
return '$runtimeType($name)';
return '$runtimeType($name) -> $parent';
}
}
void main() {
test('ScrollPhysics applyTo()', () {
const ScrollPhysics a = const TestScrollPhysics(name: 'a');
const ScrollPhysics b = const TestScrollPhysics(name: 'b');
const ScrollPhysics c = const TestScrollPhysics(name: 'c');
const ScrollPhysics d = const TestScrollPhysics(name: 'd');
const ScrollPhysics e = const TestScrollPhysics(name: 'e');
expect(a.parent, null);
expect(b.parent, null);
expect(c.parent, null);
final TestScrollPhysics ab = a.applyTo(b);
expect(ab.names, 'a b');
final TestScrollPhysics abc = ab.applyTo(c);
expect(abc.names, 'a b c');
final TestScrollPhysics de = d.applyTo(e);
expect(de.names, 'd e');
final TestScrollPhysics abcde = abc.applyTo(de);
expect(abcde.names, 'a b c d e');
});
test('ScrollPhysics subclasses applyTo()', () {
const ScrollPhysics bounce = const BouncingScrollPhysics();
const ScrollPhysics clamp = const ClampingScrollPhysics();
const ScrollPhysics never = const NeverScrollableScrollPhysics();
const ScrollPhysics always = const AlwaysScrollableScrollPhysics();
const ScrollPhysics page = const PageScrollPhysics();
String types(ScrollPhysics s) => s.parent == null ? '${s.runtimeType}' : '${s.runtimeType} ${types(s.parent)}';
expect(types(bounce.applyTo(clamp.applyTo(never.applyTo(always.applyTo(page))))),
'BouncingScrollPhysics ClampingScrollPhysics NeverScrollableScrollPhysics AlwaysScrollableScrollPhysics PageScrollPhysics');
expect(types(clamp.applyTo(never.applyTo(always.applyTo(page.applyTo(bounce))))),
'ClampingScrollPhysics NeverScrollableScrollPhysics AlwaysScrollableScrollPhysics PageScrollPhysics BouncingScrollPhysics');
expect(types(never.applyTo(always.applyTo(page.applyTo(bounce.applyTo(clamp))))),
'NeverScrollableScrollPhysics AlwaysScrollableScrollPhysics PageScrollPhysics BouncingScrollPhysics ClampingScrollPhysics');
expect(types(always.applyTo(page.applyTo(bounce.applyTo(clamp.applyTo(never))))),
'AlwaysScrollableScrollPhysics PageScrollPhysics BouncingScrollPhysics ClampingScrollPhysics NeverScrollableScrollPhysics');
expect(types(page.applyTo(bounce.applyTo(clamp.applyTo(never.applyTo(always))))),
'PageScrollPhysics BouncingScrollPhysics ClampingScrollPhysics NeverScrollableScrollPhysics AlwaysScrollableScrollPhysics');
});
}
...@@ -47,7 +47,9 @@ class TestScrollPhysics extends ClampingScrollPhysics { ...@@ -47,7 +47,9 @@ class TestScrollPhysics extends ClampingScrollPhysics {
const TestScrollPhysics({ ScrollPhysics parent }) : super(parent: parent); const TestScrollPhysics({ ScrollPhysics parent }) : super(parent: parent);
@override @override
TestScrollPhysics applyTo(ScrollPhysics parent) => new TestScrollPhysics(parent: parent); TestScrollPhysics applyTo(ScrollPhysics ancestor) {
return new TestScrollPhysics(parent: parent?.applyTo(ancestor) ?? ancestor);
}
@override @override
Tolerance get tolerance => const Tolerance(velocity: 20.0, distance: 1.0); Tolerance get tolerance => const Tolerance(velocity: 20.0, distance: 1.0);
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment