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
|
||||
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;
|
||||
}
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user