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 {
(midHeight - minHeight)).clamp(0.0, 1.0);
double _indicatorOpacity(int index) {
return 1.0 - _selectedIndexDelta(index) * tColumnToRow * 0.5;
return 1.0 - _selectedIndexDelta(index) * 0.5;
}
double _titleOpacity(int index) {
......@@ -369,16 +369,15 @@ class _AllSectionsView extends AnimatedWidget {
// app bar's height is _kAppBarMidHeight and only one section heading is
// visible.
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;
@override
_SnappingScrollPhysics applyTo(ScrollPhysics parent) {
return new _SnappingScrollPhysics(
parent: parent,
midScrollOffset: midScrollOffset
);
_SnappingScrollPhysics applyTo(ScrollPhysics ancestor) {
return new _SnappingScrollPhysics(parent: buildParent(ancestor), midScrollOffset: midScrollOffset);
}
Simulation _toMidScrollOffsetSimulation(double offset, double dragVelocity) {
......@@ -436,6 +435,7 @@ class _AnimationDemoHomeState extends State<AnimationDemoHome> {
final ScrollController _scrollController = new ScrollController();
final PageController _headingPageController = new PageController();
final PageController _detailsPageController = new PageController();
ScrollPhysics _headingScrollPhysics = const NeverScrollableScrollPhysics();
ValueNotifier<double> selectedIndex = new ValueNotifier<double>(0.0);
@override
......@@ -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) {
const Duration duration = const Duration(milliseconds: 400);
const Curve curve = Curves.fastOutSlowIn;
......@@ -532,53 +548,59 @@ class _AnimationDemoHomeState extends State<AnimationDemoHome> {
return new SizedBox.expand(
child: new Stack(
children: <Widget>[
new CustomScrollView(
controller: _scrollController,
physics: new _SnappingScrollPhysics(midScrollOffset: appBarMidScrollOffset),
slivers: <Widget>[
// Start out below the status bar, gradually move to the top of the screen.
new _StatusBarPaddingSliver(
maxHeight: statusBarHeight,
scrollFactor: 7.0,
),
// Section Headings
new SliverPersistentHeader(
pinned: true,
delegate: new _SliverAppBarDelegate(
minHeight: _kAppBarMinHeight,
maxHeight: appBarMaxHeight,
child: new NotificationListener<ScrollNotification>(
onNotification: (ScrollNotification notification) {
return _handlePageNotification(notification, _headingPageController, _detailsPageController);
},
child: new PageView(
controller: _headingPageController,
children: _allHeadingItems(appBarMaxHeight, appBarMidScrollOffset),
new NotificationListener<ScrollNotification>(
onNotification: (ScrollNotification notification) {
return _handleScrollNotification(notification, appBarMidScrollOffset);
},
child: new CustomScrollView(
controller: _scrollController,
physics: new _SnappingScrollPhysics(midScrollOffset: appBarMidScrollOffset),
slivers: <Widget>[
// Start out below the status bar, gradually move to the top of the screen.
new _StatusBarPaddingSliver(
maxHeight: statusBarHeight,
scrollFactor: 7.0,
),
// Section Headings
new SliverPersistentHeader(
pinned: true,
delegate: new _SliverAppBarDelegate(
minHeight: _kAppBarMinHeight,
maxHeight: appBarMaxHeight,
child: new NotificationListener<ScrollNotification>(
onNotification: (ScrollNotification notification) {
return _handlePageNotification(notification, _headingPageController, _detailsPageController);
},
child: new PageView(
physics: _headingScrollPhysics,
controller: _headingPageController,
children: _allHeadingItems(appBarMaxHeight, appBarMidScrollOffset),
),
),
),
),
),
// Details
new SliverToBoxAdapter(
child: new SizedBox(
height: 610.0,
child: new NotificationListener<ScrollNotification>(
onNotification: (ScrollNotification notification) {
return _handlePageNotification(notification, _detailsPageController, _headingPageController);
},
child: new PageView(
controller: _detailsPageController,
children: allSections.map((Section section) {
return new Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: _detailItemsFor(section).toList(),
);
}).toList(),
// Details
new SliverToBoxAdapter(
child: new SizedBox(
height: 610.0,
child: new NotificationListener<ScrollNotification>(
onNotification: (ScrollNotification notification) {
return _handlePageNotification(notification, _detailsPageController, _headingPageController);
},
child: new PageView(
controller: _detailsPageController,
children: allSections.map((Section section) {
return new Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: _detailItemsFor(section).toList(),
);
}).toList(),
),
),
),
),
),
],
],
),
),
new Positioned(
top: statusBarHeight,
......
......@@ -233,7 +233,9 @@ class PageScrollPhysics extends ScrollPhysics {
const PageScrollPhysics({ ScrollPhysics parent }) : super(parent: parent);
@override
PageScrollPhysics applyTo(ScrollPhysics parent) => new PageScrollPhysics(parent: parent);
PageScrollPhysics applyTo(ScrollPhysics ancestor) {
return new PageScrollPhysics(parent: buildParent(ancestor));
}
double _getPage(ScrollPosition position) {
if (position is _PagePosition)
......
......@@ -37,13 +37,35 @@ class ScrollPhysics {
/// [ScrollPhysics] subclasses at runtime.
final ScrollPhysics parent;
/// Return a [ScrollPhysics] with the same [runtimeType] where the [parent]
/// has been replaced with the given [parent].
/// If [parent] is null then return ancestor, otherwise recursively build a
/// 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
/// [ScrollPhysics] instance and some of the behaviors from the given
/// [ScrollPhysics] instance.
ScrollPhysics applyTo(ScrollPhysics parent) => new ScrollPhysics(parent: parent);
/// [ScrollPhysics] instance and some of the behaviors from [ancestor].
///
/// 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
/// convert an offset in logical pixels as provided by the [DragUpdateDetails]
......@@ -198,7 +220,9 @@ class BouncingScrollPhysics extends ScrollPhysics {
const BouncingScrollPhysics({ ScrollPhysics parent }) : super(parent: parent);
@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 edge of the scrollable contents is harder than scrolling the list.
......@@ -277,7 +301,9 @@ class ClampingScrollPhysics extends ScrollPhysics {
const ClampingScrollPhysics({ ScrollPhysics parent }) : super(parent: parent);
@override
ClampingScrollPhysics applyTo(ScrollPhysics parent) => new ClampingScrollPhysics(parent: parent);
ClampingScrollPhysics applyTo(ScrollPhysics ancestor) {
return new ClampingScrollPhysics(parent: buildParent(ancestor));
}
@override
double applyBoundaryConditions(ScrollMetrics position, double value) {
......@@ -359,8 +385,34 @@ class AlwaysScrollableScrollPhysics extends ScrollPhysics {
const AlwaysScrollableScrollPhysics({ ScrollPhysics parent }) : super(parent: parent);
@override
AlwaysScrollableScrollPhysics applyTo(ScrollPhysics parent) => new AlwaysScrollableScrollPhysics(parent: parent);
AlwaysScrollableScrollPhysics applyTo(ScrollPhysics ancestor) {
return new AlwaysScrollableScrollPhysics(parent: buildParent(ancestor));
}
@override
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
}
bool _shouldUpdatePosition(Scrollable oldWidget) {
return widget.physics?.runtimeType != oldWidget.physics?.runtimeType
|| widget.controller?.runtimeType != oldWidget.controller?.runtimeType;
ScrollPhysics newPhysics = widget.physics;
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
......
// 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 {
const TestScrollPhysics({ ScrollPhysics parent }) : super(parent: parent);
@override
TestScrollPhysics applyTo(ScrollPhysics parent) => new TestScrollPhysics(parent: parent);
TestScrollPhysics applyTo(ScrollPhysics ancestor) {
return new TestScrollPhysics(parent: parent?.applyTo(ancestor) ?? ancestor);
}
@override
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