flutter_flutter/packages/flutter/lib/widgets/fixed_height_scrollable.dart
Hixie 74575775bd Introduce an explicit Key type.
This fixes some theoretical bugs whereby we were using hashCode to try
to get unique keys for objects, but really we wanted object identity.
It also lays the groundwork for a new GlobalKey concept.

I tried to keep the impact on the code minimal, which is why the "Key"
constructor is actually a factory that returns a StringKey. The code
has this class hierarchy:

```
   KeyBase
    |
   Key--------------+---------------+
    |               |               |
   StringKey    ObjectKey       UniqueKey
```

...where the constructors are Key and Key.stringify (StringKey),
Key.fromObjectIdentity (ObjectKey), and Key.unique (UniqueKey).

We could instead of factory methods use regular constructors with the
following hierarchy:

```
   KeyBase
    |
   LocalKey---------+---------------+
    |               |               |
   Key      ObjectIdentityKey   UniqueKey
```

...with constructors Key, Key.stringify, ObjectIdentityKey, and
UniqueKey, but I felt that that was maybe a more confusing hierarchy.
I don't have a strong opinion on this.
2015-07-22 09:14:06 -07:00

107 lines
3.0 KiB
Dart

// Copyright 2015 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 'dart:math' as math;
import 'package:sky/animation/scroll_behavior.dart';
import 'package:sky/widgets/basic.dart';
import 'package:sky/widgets/scrollable.dart';
abstract class FixedHeightScrollable extends Scrollable {
FixedHeightScrollable({ Key key, this.itemHeight, this.padding })
: super(key: key) {
assert(itemHeight != null);
}
EdgeDims padding;
double itemHeight;
/// Subclasses must implement `get itemCount` to tell FixedHeightScrollable
/// how many items there are in the list.
int get itemCount;
int _previousItemCount;
void syncFields(FixedHeightScrollable source) {
padding = source.padding;
itemHeight = source.itemHeight;
super.syncFields(source);
}
ScrollBehavior createScrollBehavior() => new OverscrollBehavior();
OverscrollBehavior get scrollBehavior => super.scrollBehavior;
double _height;
void _handleSizeChanged(Size newSize) {
setState(() {
_height = newSize.height;
scrollBehavior.containerSize = _height;
});
}
void _updateContentsHeight() {
double contentsHeight = itemHeight * itemCount;
if (padding != null)
contentsHeight += padding.top + padding.bottom;
scrollBehavior.contentsSize = contentsHeight;
}
void _updateScrollOffset() {
if (scrollOffset > scrollBehavior.maxScrollOffset)
settleScrollOffset();
}
Widget buildContent() {
if (itemCount != _previousItemCount) {
_previousItemCount = itemCount;
_updateContentsHeight();
_updateScrollOffset();
}
int itemShowIndex = 0;
int itemShowCount = 0;
double offsetY = 0.0;
if (_height != null && _height > 0.0) {
if (scrollOffset < 0.0) {
double visibleHeight = _height + scrollOffset;
itemShowCount = (visibleHeight / itemHeight).round() + 1;
offsetY = scrollOffset;
} else {
itemShowCount = (_height / itemHeight).ceil();
double alignmentDelta = -scrollOffset % itemHeight;
double drawStart;
if (alignmentDelta != 0.0) {
alignmentDelta -= itemHeight;
itemShowCount += 1;
drawStart = scrollOffset + alignmentDelta;
offsetY = -alignmentDelta;
} else {
drawStart = scrollOffset;
}
itemShowIndex = math.max(0, (drawStart / itemHeight).floor());
}
}
List<Widget> items = buildItems(itemShowIndex, itemShowCount);
assert(items.every((item) => item.key != null));
// TODO(ianh): Refactor this so that it does the building in the
// same frame as the size observing, similar to BlockViewport, but
// keeping the fixed-height optimisations.
return new SizeObserver(
callback: _handleSizeChanged,
child: new Viewport(
offset: offsetY,
child: new Container(
padding: padding,
child: new Block(items)
)
)
);
}
List<Widget> buildItems(int start, int count);
}