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