Commit 3f047018 authored by Ian Hickson's avatar Ian Hickson

Merge pull request #1826 from Hixie/size-obs-1

SizeObserver crusade: snap alignment
parents cdbc9427 fd7be69d
......@@ -28,6 +28,8 @@ class CardCollectionState extends State<CardCollection> {
// TODO(hansmuller): need a local image asset
static const _sunshineURL = "http://www.walltor.com/images/wallpaper/good-morning-sunshine-58540.jpg";
static const kCardMargins = 8.0;
final TextStyle backgroundTextStyle =
Typography.white.title.copyWith(textAlign: TextAlign.center);
......@@ -41,7 +43,6 @@ class CardCollectionState extends State<CardCollection> {
bool _sunshine = false;
bool _varyFontSizes = false;
InvalidatorCallback _invalidator;
Size _cardCollectionSize = new Size(200.0, 200.0);
void _initVariableSizedCardModels() {
List<double> cardHeights = <double>[
......@@ -78,15 +79,14 @@ class CardCollectionState extends State<CardCollection> {
double _variableSizeToSnapOffset(double scrollOffset) {
double cumulativeHeight = 0.0;
double margins = 8.0;
List<double> cumulativeHeights = _cardModels.map((CardModel card) {
cumulativeHeight += card.height + margins;
cumulativeHeight += card.height + kCardMargins;
return cumulativeHeight;
})
.toList();
double offsetForIndex(int i) {
return 12.0 + (margins + _cardModels[i].height) / 2.0 + ((i == 0) ? 0.0 : cumulativeHeights[i - 1]);
return (kCardMargins + _cardModels[i].height) / 2.0 + ((i == 0) ? 0.0 : cumulativeHeights[i - 1]);
}
for (int i = 0; i < cumulativeHeights.length; i++) {
......@@ -99,11 +99,14 @@ class CardCollectionState extends State<CardCollection> {
double _fixedSizeToSnapOffset(double scrollOffset) {
double cardHeight = _cardModels[0].height;
int cardIndex = (scrollOffset.clamp(0.0, cardHeight * (_cardModels.length - 1)) / cardHeight).floor();
return 12.0 + cardIndex * cardHeight + cardHeight * 0.5;
return cardIndex * cardHeight + cardHeight * 0.5;
}
double _toSnapOffset(double scrollOffset) {
return _fixedSizeCards ? _fixedSizeToSnapOffset(scrollOffset) : _variableSizeToSnapOffset(scrollOffset);
double _toSnapOffset(double scrollOffset, Size containerSize) {
double halfHeight = containerSize.height / 2.0;
scrollOffset += halfHeight;
double result = _fixedSizeCards ? _fixedSizeToSnapOffset(scrollOffset) : _variableSizeToSnapOffset(scrollOffset);
return result - halfHeight;
}
void dismissCard(CardModel card) {
......@@ -300,7 +303,7 @@ class CardCollectionState extends State<CardCollection> {
color: Theme.of(context).primarySwatch[cardModel.color],
child: new Container(
height: cardModel.height,
padding: const EdgeDims.all(8.0),
padding: const EdgeDims.all(kCardMargins),
child: _editable ?
new Center(
child: new Input(
......@@ -387,12 +390,6 @@ class CardCollectionState extends State<CardCollection> {
);
}
void _updateCardCollectionSize(Size newSize) {
setState(() {
_cardCollectionSize = newSize;
});
}
Shader _createShader(Rect bounds) {
return new LinearGradient(
begin: bounds.topLeft,
......@@ -408,7 +405,6 @@ class CardCollectionState extends State<CardCollection> {
if (_fixedSizeCards) {
cardCollection = new ScrollableList (
snapOffsetCallback: _snapToCenter ? _toSnapOffset : null,
snapAlignmentOffset: _cardCollectionSize.height / 2.0,
itemExtent: _cardModels[0].height,
children: _cardModels.map((CardModel card) => _buildCard(context, card.value))
);
......@@ -417,7 +413,6 @@ class CardCollectionState extends State<CardCollection> {
builder: _buildCard,
token: _cardModels.length,
snapOffsetCallback: _snapToCenter ? _toSnapOffset : null,
snapAlignmentOffset: _cardCollectionSize.height / 2.0,
onInvalidatorAvailable: (InvalidatorCallback callback) { _invalidator = callback; }
);
}
......@@ -431,13 +426,10 @@ class CardCollectionState extends State<CardCollection> {
);
}
Widget body = new SizeObserver(
onSizeChanged: _updateCardCollectionSize,
child: new Container(
padding: const EdgeDims.symmetric(vertical: 12.0, horizontal: 8.0),
decoration: new BoxDecoration(backgroundColor: Theme.of(context).primarySwatch[50]),
child: cardCollection
)
Widget body = new Container(
padding: const EdgeDims.symmetric(vertical: 12.0, horizontal: 8.0),
decoration: new BoxDecoration(backgroundColor: Theme.of(context).primarySwatch[50]),
child: cardCollection
);
if (_snapToCenter) {
......
......@@ -28,7 +28,6 @@ class PageableList extends Scrollable {
ScrollListener onScroll,
ScrollListener onScrollEnd,
SnapOffsetCallback snapOffsetCallback,
double snapAlignmentOffset: 0.0,
this.itemsWrap: false,
this.itemsSnapAlignment: ItemsSnapAlignment.adjacentItem,
this.onPageChanged,
......@@ -44,8 +43,7 @@ class PageableList extends Scrollable {
onScrollStart: onScrollStart,
onScroll: onScroll,
onScrollEnd: onScrollEnd,
snapOffsetCallback: snapOffsetCallback,
snapAlignmentOffset: snapAlignmentOffset
snapOffsetCallback: snapOffsetCallback
);
final bool itemsWrap;
......
......@@ -25,7 +25,7 @@ final Tolerance kPixelScrollTolerance = new Tolerance(
);
typedef void ScrollListener(double scrollOffset);
typedef double SnapOffsetCallback(double scrollOffset);
typedef double SnapOffsetCallback(double scrollOffset, Size containerSize);
/// A base class for scrollable widgets.
///
......@@ -43,12 +43,10 @@ abstract class Scrollable extends StatefulComponent {
this.onScrollStart,
this.onScroll,
this.onScrollEnd,
this.snapOffsetCallback,
this.snapAlignmentOffset: 0.0
this.snapOffsetCallback
}) : super(key: key) {
assert(scrollDirection == Axis.vertical || scrollDirection == Axis.horizontal);
assert(scrollAnchor == ViewportAnchor.start || scrollAnchor == ViewportAnchor.end);
assert(snapAlignmentOffset != null);
}
/// The scroll offset this widget should use when first created.
......@@ -68,11 +66,21 @@ abstract class Scrollable extends StatefulComponent {
/// Called whenever this widget stops scrolling.
final ScrollListener onScrollEnd;
/// Called to determine the offset to which scrolling should snap.
/// Called to determine the offset to which scrolling should snap,
/// when handling a fling.
///
/// This callback, if set, will be called with the offset that the
/// Scrollable would have scrolled to in the absence of this
/// callback, and a Size describing the size of the Scrollable
/// itself.
///
/// The callback's return value is used as the new scroll offset to
/// aim for.
///
/// If the callback simply returns its first argument (the offset),
/// then it is as if the callback was null.
final SnapOffsetCallback snapOffsetCallback;
final double snapAlignmentOffset; // What does this do?
/// The state from the closest instance of this class that encloses the given context.
static ScrollableState of(BuildContext context) {
return context.ancestorStateOfType(const TypeMatcher<ScrollableState>());
......@@ -294,7 +302,8 @@ abstract class ScrollableState<T extends Scrollable> extends State<T> {
/// Returns the snapped offset closest to the given scroll offset.
double snapScrollOffset(double scrollOffset) {
return config.snapOffsetCallback == null ? scrollOffset : config.snapOffsetCallback(scrollOffset);
RenderBox box = context.findRenderObject();
return config.snapOffsetCallback == null ? scrollOffset : config.snapOffsetCallback(scrollOffset, box.size);
}
/// Whether this scrollable should attempt to snap scroll offsets.
......@@ -312,20 +321,20 @@ abstract class ScrollableState<T extends Scrollable> extends State<T> {
if (endScrollOffset.isNaN)
return null;
final double snappedScrollOffset = snapScrollOffset(endScrollOffset + config.snapAlignmentOffset);
final double alignedScrollOffset = snappedScrollOffset - config.snapAlignmentOffset;
if (!_scrollOffsetIsInBounds(alignedScrollOffset))
final double snappedScrollOffset = snapScrollOffset(endScrollOffset);
if (!_scrollOffsetIsInBounds(snappedScrollOffset))
return null;
final double snapVelocity = scrollVelocity.abs() * (alignedScrollOffset - scrollOffset).sign;
final double snapVelocity = scrollVelocity.abs() * (snappedScrollOffset - scrollOffset).sign;
final double endVelocity = pixelOffsetToScrollOffset(kPixelScrollTolerance.velocity).abs() * scrollVelocity.sign;
Simulation toSnapSimulation =
scrollBehavior.createSnapScrollSimulation(scrollOffset, alignedScrollOffset, snapVelocity, endVelocity);
Simulation toSnapSimulation = scrollBehavior.createSnapScrollSimulation(
scrollOffset, snappedScrollOffset, snapVelocity, endVelocity
);
if (toSnapSimulation == null)
return null;
final double scrollOffsetMin = math.min(scrollOffset, alignedScrollOffset);
final double scrollOffsetMax = math.max(scrollOffset, alignedScrollOffset);
final double scrollOffsetMin = math.min(scrollOffset, snappedScrollOffset);
final double scrollOffsetMax = math.max(scrollOffset, snappedScrollOffset);
return new ClampedSimulation(toSnapSimulation, xMin: scrollOffsetMin, xMax: scrollOffsetMax);
}
......@@ -631,7 +640,6 @@ class ScrollableMixedWidgetList extends Scrollable {
double initialScrollOffset,
ScrollListener onScroll,
SnapOffsetCallback snapOffsetCallback,
double snapAlignmentOffset: 0.0,
this.builder,
this.token,
this.onInvalidatorAvailable
......@@ -639,8 +647,7 @@ class ScrollableMixedWidgetList extends Scrollable {
key: key,
initialScrollOffset: initialScrollOffset,
onScroll: onScroll,
snapOffsetCallback: snapOffsetCallback,
snapAlignmentOffset: snapAlignmentOffset
snapOffsetCallback: snapOffsetCallback
);
final IndexedBuilder builder;
......
......@@ -20,7 +20,6 @@ class ScrollableGrid extends Scrollable {
double initialScrollOffset,
ScrollListener onScroll,
SnapOffsetCallback snapOffsetCallback,
double snapAlignmentOffset: 0.0,
this.delegate,
this.children
}) : super(
......@@ -31,8 +30,7 @@ class ScrollableGrid extends Scrollable {
// delegate that places children in column-major order.
scrollDirection: Axis.vertical,
onScroll: onScroll,
snapOffsetCallback: snapOffsetCallback,
snapAlignmentOffset: snapAlignmentOffset
snapOffsetCallback: snapOffsetCallback
);
final GridDelegate delegate;
......
......@@ -19,7 +19,6 @@ class ScrollableList extends Scrollable {
ViewportAnchor scrollAnchor: ViewportAnchor.start,
ScrollListener onScroll,
SnapOffsetCallback snapOffsetCallback,
double snapAlignmentOffset: 0.0,
this.itemExtent,
this.itemsWrap: false,
this.padding,
......@@ -31,8 +30,7 @@ class ScrollableList extends Scrollable {
scrollDirection: scrollDirection,
scrollAnchor: scrollAnchor,
onScroll: onScroll,
snapOffsetCallback: snapOffsetCallback,
snapAlignmentOffset: snapAlignmentOffset
snapOffsetCallback: snapOffsetCallback
) {
assert(itemExtent != null);
}
......@@ -269,7 +267,6 @@ class ScrollableLazyList extends Scrollable {
ViewportAnchor scrollAnchor: ViewportAnchor.start,
ScrollListener onScroll,
SnapOffsetCallback snapOffsetCallback,
double snapAlignmentOffset: 0.0,
this.itemExtent,
this.itemCount,
this.itemBuilder,
......@@ -281,8 +278,7 @@ class ScrollableLazyList extends Scrollable {
scrollDirection: scrollDirection,
scrollAnchor: scrollAnchor,
onScroll: onScroll,
snapOffsetCallback: snapOffsetCallback,
snapAlignmentOffset: snapAlignmentOffset
snapOffsetCallback: snapOffsetCallback
) {
assert(itemExtent != null);
assert(itemBuilder != null);
......
......@@ -20,7 +20,7 @@ Widget buildItem(int item) {
);
}
double snapOffsetCallback(double offset) {
double snapOffsetCallback(double offset, Size size) {
return (offset / itemExtent).floor() * itemExtent;
}
......
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