Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Submit feedback
Sign in
Toggle navigation
F
Front-End
Project
Project
Details
Activity
Releases
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
abdullh.alsoleman
Front-End
Commits
5f9cb4aa
Commit
5f9cb4aa
authored
Sep 25, 2015
by
Hixie
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Port ScrollableMixedWidgetListState to fn3.
parent
06ffa759
Changes
4
Hide whitespace changes
Inline
Side-by-side
Showing
4 changed files
with
179 additions
and
42 deletions
+179
-42
card_collection.dart
examples/widgets/card_collection.dart
+16
-12
framework.dart
packages/flutter/lib/src/fn3/framework.dart
+21
-9
mixed_viewport.dart
packages/flutter/lib/src/fn3/mixed_viewport.dart
+60
-19
scrollable.dart
packages/flutter/lib/src/fn3/scrollable.dart
+82
-2
No files found.
examples/widgets/card_collection.dart
View file @
5f9cb4aa
...
...
@@ -4,7 +4,8 @@
import
'package:sky/animation.dart'
;
import
'package:sky/material.dart'
;
import
'package:sky/widgets.dart'
;
import
'package:sky/painting.dart'
;
import
'package:sky/src/fn3.dart'
;
class
CardModel
{
CardModel
(
this
.
value
,
this
.
height
,
this
.
color
);
...
...
@@ -15,7 +16,11 @@ class CardModel {
Key
get
key
=>
new
ObjectKey
(
this
);
}
class
CardCollectionApp
extends
App
{
class
CardCollectionApp
extends
StatefulComponent
{
CardCollectionAppState
createState
()
=>
new
CardCollectionAppState
();
}
class
CardCollectionAppState
extends
State
<
CardCollectionApp
>
{
static
const
TextStyle
cardLabelStyle
=
const
TextStyle
(
color:
Colors
.
white
,
fontSize:
18.0
,
fontWeight:
bold
);
...
...
@@ -23,14 +28,14 @@ class CardCollectionApp extends App {
final
TextStyle
backgroundTextStyle
=
Typography
.
white
.
title
.
copyWith
(
textAlign:
TextAlign
.
center
);
MixedViewportLayoutState
_layoutState
=
new
MixedViewportLayoutState
();
List
<
CardModel
>
_cardModels
;
DismissDirection
_dismissDirection
=
DismissDirection
.
horizontal
;
bool
_drawerShowing
=
false
;
AnimationStatus
_drawerStatus
=
AnimationStatus
.
dismissed
;
InvalidatorCallback
_invalidator
;
void
initState
()
{
void
initState
(
BuildContext
context
)
{
super
.
initState
(
context
);
List
<
double
>
cardHeights
=
<
double
>[
48.0
,
63.0
,
82.0
,
146.0
,
60.0
,
55.0
,
84.0
,
96.0
,
50.0
,
48.0
,
63.0
,
82.0
,
146.0
,
60.0
,
55.0
,
84.0
,
96.0
,
50.0
,
...
...
@@ -40,7 +45,6 @@ class CardCollectionApp extends App {
Color
color
=
Color
.
lerp
(
Colors
.
red
[
300
],
Colors
.
blue
[
900
],
i
/
cardHeights
.
length
);
return
new
CardModel
(
i
,
cardHeights
[
i
],
color
);
});
super
.
initState
();
}
void
dismissCard
(
CardModel
card
)
{
...
...
@@ -121,14 +125,14 @@ class CardCollectionApp extends App {
);
}
Widget
buildCard
(
int
index
)
{
Widget
buildCard
(
BuildContext
context
,
int
index
)
{
if
(
index
>=
_cardModels
.
length
)
return
null
;
CardModel
cardModel
=
_cardModels
[
index
];
Widget
card
=
new
Dismissable
(
direction:
_dismissDirection
,
onResized:
()
{
_
layoutState
.
invalidate
([
index
]);
},
onResized:
()
{
_
invalidator
([
index
]);
},
onDismissed:
()
{
dismissCard
(
cardModel
);
},
child:
new
Card
(
color:
cardModel
.
color
,
...
...
@@ -178,7 +182,7 @@ class CardCollectionApp extends App {
child:
new
Viewport
(
child:
new
Container
(
height:
cardModel
.
height
,
decoration:
new
BoxDecoration
(
backgroundColor:
Theme
.
of
(
this
).
primaryColor
),
decoration:
new
BoxDecoration
(
backgroundColor:
Theme
.
of
(
context
).
primaryColor
),
child:
new
Row
([
leftArrowIcon
,
new
Flexible
(
child:
new
Text
(
backgroundMessage
,
style:
backgroundTextStyle
)),
...
...
@@ -196,14 +200,14 @@ class CardCollectionApp extends App {
);
}
Widget
build
()
{
Widget
build
(
BuildContext
context
)
{
Widget
cardCollection
=
new
Container
(
padding:
const
EdgeDims
.
symmetric
(
vertical:
12.0
,
horizontal:
8.0
),
decoration:
new
BoxDecoration
(
backgroundColor:
Theme
.
of
(
this
).
primarySwatch
[
50
]),
decoration:
new
BoxDecoration
(
backgroundColor:
Theme
.
of
(
context
).
primarySwatch
[
50
]),
child:
new
ScrollableMixedWidgetList
(
builder:
buildCard
,
token:
_cardModels
.
length
,
layoutState:
_layoutState
onInvalidatorAvailable:
(
InvalidatorCallback
callback
)
{
_invalidator
=
callback
;
}
)
);
...
...
packages/flutter/lib/src/fn3/framework.dart
View file @
5f9cb4aa
...
...
@@ -315,9 +315,14 @@ abstract class State<T extends StatefulComponent> {
/// component will not be scheduled for rebuilding, meaning that its rendering
/// will not be updated.
void
setState
(
void
fn
())
{
assert
(
_debugLifecycleState
==
_StateLifecycle
.
ready
);
assert
(
_debugLifecycleState
!=
_StateLifecycle
.
defunct
);
fn
();
_element
.
markNeedsBuild
();
if
(
_element
.
_builder
!=
null
)
{
// _element._builder is set after initState(). We verify that we're past
// that before calling markNeedsBuild() so that setState()s triggered
// during initState() during lockState() don't cause any trouble.
_element
.
markNeedsBuild
();
}
}
/// Called when this object is removed from the tree. Override this to clean
...
...
@@ -509,8 +514,10 @@ abstract class Element<T extends Widget> implements BuildContext {
_parent
=
parent
;
_slot
=
newSlot
;
_depth
=
_parent
!=
null
?
_parent
.
depth
+
1
:
1
;
if
(
widget
.
key
is
GlobalKey
)
widget
.
key
.
_register
(
this
);
if
(
widget
.
key
is
GlobalKey
)
{
final
GlobalKey
key
=
widget
.
key
;
key
.
_register
(
this
);
}
assert
(()
{
_debugLifecycleState
=
_ElementLifecycle
.
mounted
;
return
true
;
});
}
...
...
@@ -576,8 +583,10 @@ abstract class Element<T extends Widget> implements BuildContext {
assert
(
_debugLifecycleState
==
_ElementLifecycle
.
mounted
);
assert
(
widget
!=
null
);
assert
(
depth
!=
null
);
if
(
widget
.
key
is
GlobalKey
)
widget
.
key
.
_unregister
(
this
);
if
(
widget
.
key
is
GlobalKey
)
{
final
GlobalKey
key
=
widget
.
key
;
key
.
_unregister
(
this
);
}
assert
(()
{
_debugLifecycleState
=
_ElementLifecycle
.
defunct
;
return
true
;
});
}
...
...
@@ -631,6 +640,7 @@ abstract class BuildableElement<T extends Widget> extends Element<T> {
/// binding when scheduleBuild() has been called to mark this element dirty,
/// and by update() when the Widget has changed.
void
rebuild
()
{
assert
(
_debugLifecycleState
!=
_ElementLifecycle
.
initial
);
if
(!
_dirty
)
return
;
assert
(
_debugLifecycleState
==
_ElementLifecycle
.
mounted
);
...
...
@@ -738,6 +748,9 @@ class StatefulComponentElement extends BuildableElement<StatefulComponent> {
return
false
;
});
assert
(()
{
_state
.
_debugLifecycleState
=
_StateLifecycle
.
ready
;
return
true
;
});
assert
(
_builder
==
null
);
// see State.setState() for why it's important that _builder be set after
// initState() is called.
_builder
=
_state
.
build
;
}
...
...
@@ -1184,9 +1197,8 @@ typedef void WidgetsExceptionHandler(String context, dynamic exception, StackTra
/// the exception occurred, and may include additional details such as
/// descriptions of the objects involved. The 'exception' argument contains the
/// object that was thrown, and the 'stack' argument contains the stack trace.
/// The callback is invoked after the information is printed to the console, and
/// could be used to print additional information, such as from
/// [debugDumpApp()].
/// If no callback is set, then a default behaviour consisting of dumping the
/// context, exception, and stack trace to the console is used instead.
WidgetsExceptionHandler
debugWidgetsExceptionHandler
;
void
_debugReportException
(
String
context
,
dynamic
exception
,
StackTrace
stack
)
{
if
(
debugWidgetsExceptionHandler
!=
null
)
{
...
...
packages/flutter/lib/src/fn3/mixed_viewport.dart
View file @
5f9cb4aa
...
...
@@ -163,14 +163,20 @@ class MixedViewportElement extends RenderObjectElement<MixedViewport> {
assert
(
renderObject
!=
null
);
final
int
startIndex
=
_firstVisibleChildIndex
;
int
lastIndex
=
startIndex
+
_childrenByKey
.
length
-
1
;
for
(
int
index
=
startIndex
;
index
<=
lastIndex
;
index
+=
1
)
{
Element
nextSibling
=
null
;
for
(
int
index
=
lastIndex
;
index
>
startIndex
;
index
-=
1
)
{
final
Widget
newWidget
=
_buildWidgetAt
(
index
);
final
_ChildKey
key
=
new
_ChildKey
.
fromWidget
(
newWidget
);
final
Element
oldElement
=
_childrenByKey
[
key
];
assert
(
oldElement
!=
null
);
final
Element
newElement
=
updateChild
(
oldElement
,
newWidget
,
renderObject
.
childAfter
(
oldElement
.
renderObject
)
);
final
Element
newElement
=
updateChild
(
oldElement
,
newWidget
,
nextSibling
);
assert
(
newElement
!=
null
);
_childrenByKey
[
key
]
=
newElement
;
// Verify that it hasn't changed size.
// If this assertion fires, it means you didn't call "invalidate"
// before changing the size of one of your items.
assert
(
_debugIsSameSize
(
newElement
,
index
,
_lastLayoutConstraints
));
nextSibling
=
newElement
;
}
}
}
...
...
@@ -245,6 +251,16 @@ class MixedViewportElement extends RenderObjectElement<MixedViewport> {
return
newElement
;
}
// Build the widget at index.
Element
_maybeGetElement
(
int
index
,
BoxConstraints
innerConstraints
)
{
assert
(
index
<=
_childOffsets
.
length
-
1
);
final
Widget
newWidget
=
_maybeBuildWidgetAt
(
index
);
if
(
newWidget
==
null
)
return
null
;
final
Element
newElement
=
_inflateOrUpdateWidget
(
newWidget
);
return
newElement
;
}
// Build the widget at index, handling the case where there is no such widget.
// Update the offset for that widget.
Element
_getElementAtLastKnownOffset
(
int
index
,
BoxConstraints
innerConstraints
)
{
...
...
@@ -257,14 +273,14 @@ class MixedViewportElement extends RenderObjectElement<MixedViewport> {
final
Element
newElement
=
_inflateOrUpdateWidget
(
newWidget
);
// Update the offsets based on the newElement's dimensions.
final
double
newOffset
=
_get
Offse
t
(
newElement
,
innerConstraints
);
final
double
newOffset
=
_get
ElementExten
t
(
newElement
,
innerConstraints
);
_childOffsets
.
add
(
_childOffsets
[
index
]
+
newOffset
);
return
newElement
;
}
/// Returns the intrinsic size of the given element in the scroll direction
double
_get
Offse
t
(
Element
element
,
BoxConstraints
innerConstraints
)
{
double
_get
ElementExten
t
(
Element
element
,
BoxConstraints
innerConstraints
)
{
final
RenderBox
childRenderObject
=
element
.
renderObject
;
switch
(
widget
.
direction
)
{
case
ScrollDirection
.
vertical
:
...
...
@@ -277,6 +293,38 @@ class MixedViewportElement extends RenderObjectElement<MixedViewport> {
}
}
BoxConstraints
_getInnerConstraints
(
BoxConstraints
constraints
)
{
switch
(
widget
.
direction
)
{
case
ScrollDirection
.
vertical
:
return
new
BoxConstraints
.
tightFor
(
width:
constraints
.
constrainWidth
());
case
ScrollDirection
.
horizontal
:
return
new
BoxConstraints
.
tightFor
(
height:
constraints
.
constrainHeight
());
case
ScrollDirection
.
both
:
assert
(
false
);
// we don't support ScrollDirection.both, see issue 888
return
null
;
}
}
/// This compares the offsets we had for an element with its current
/// intrinsic dimensions.
bool
_debugIsSameSize
(
Element
element
,
int
index
,
BoxConstraints
constraints
)
{
BoxConstraints
innerConstraints
=
_getInnerConstraints
(
constraints
);
// We multiple both sides by 32 and then round to avoid floating
// point errors. (You have to round, not truncate, because otherwise
// if the error is on either side of an integer, you'll magnify it
// rather than hiding it.)
// This is an issue because we don't actually record the raw data
// (the intrinsic dimensions), we record the offsets.
// The offsets therefore accumulate floating point errors. The
// errors are far too small to make the slightest diffference, but
// they're big enough to trip the assertion if we don't do this.
// We multiply by 32 so that we notice errors up to 1/32nd of a
// logical pixel. I'm assuming 32x resolution displays aren't going
// to happen. When I'm invariably proved wrong, just bump this up to
// a higher power of two.
return
((
_childOffsets
[
index
+
1
]
-
_childOffsets
[
index
])
*
32.0
).
round
()
==
(
_getElementExtent
(
element
,
innerConstraints
)
*
32.0
).
round
();
}
/// This is the core lazy-build algorithm. It builds widgets incrementally
/// from index 0 until it has built enough widgets to cover itself, and
/// discards any widgets that are not displayed.
...
...
@@ -306,16 +354,7 @@ class MixedViewportElement extends RenderObjectElement<MixedViewport> {
final
double
endOffset
=
widget
.
startOffset
+
extent
;
// Create the constraints that we will use to measure the children.
BoxConstraints
innerConstraints
;
switch
(
widget
.
direction
)
{
case
ScrollDirection
.
vertical
:
innerConstraints
=
new
BoxConstraints
.
tightFor
(
width:
constraints
.
constrainWidth
());
break
;
case
ScrollDirection
.
horizontal
:
innerConstraints
=
new
BoxConstraints
.
tightFor
(
height:
constraints
.
constrainHeight
());
break
;
case
ScrollDirection
.
both
:
assert
(
false
);
// we don't support ScrollDirection.both, see issue 888
}
final
BoxConstraints
innerConstraints
=
_getInnerConstraints
(
constraints
);
// Before doing the actual layout, fix the offsets for the widgets whose
// size or type has changed.
...
...
@@ -323,7 +362,7 @@ class MixedViewportElement extends RenderObjectElement<MixedViewport> {
assert
(
_childOffsets
.
length
>
0
);
List
<
int
>
invalidIndices
=
_invalidIndices
.
toList
();
invalidIndices
.
sort
();
for
(
int
i
=
0
;
i
<
invalidIndices
.
length
-
1
;
i
+=
1
)
{
for
(
int
i
=
0
;
i
<
invalidIndices
.
length
;
i
+=
1
)
{
// Determine the indices for this pass.
final
int
widgetIndex
=
invalidIndices
[
i
];
...
...
@@ -346,7 +385,7 @@ class MixedViewportElement extends RenderObjectElement<MixedViewport> {
final
Element
newElement
=
_getElement
(
widgetIndex
,
innerConstraints
);
// Update the offsets based on the newElement's dimensions.
final
double
newOffset
=
_get
Offse
t
(
newElement
,
innerConstraints
);
final
double
newOffset
=
_get
ElementExten
t
(
newElement
,
innerConstraints
);
final
double
oldOffset
=
_childOffsets
[
widgetIndex
+
1
]
-
_childOffsets
[
widgetIndex
];
final
double
offsetDelta
=
newOffset
-
oldOffset
;
for
(
int
j
=
widgetIndex
+
1
;
j
<=
endIndex
;
j
++)
...
...
@@ -465,18 +504,20 @@ class MixedViewportElement extends RenderObjectElement<MixedViewport> {
// Build all the widgets we still need.
while
(
_childOffsets
[
index
]
<
endOffset
)
{
if
(!
builtChildren
.
containsKey
(
index
))
{
Element
element
=
_
g
etElement
(
index
,
innerConstraints
);
Element
element
=
_
maybeG
etElement
(
index
,
innerConstraints
);
if
(
element
==
null
)
{
_didReachLastChild
=
true
;
break
;
}
if
(
index
==
_childOffsets
.
length
-
1
)
{
// Remember this element's offset.
final
double
newOffset
=
_get
Offse
t
(
element
,
innerConstraints
);
final
double
newOffset
=
_get
ElementExten
t
(
element
,
innerConstraints
);
_childOffsets
.
add
(
_childOffsets
[
index
]
+
newOffset
);
}
else
{
// Verify that it hasn't changed size.
assert
(
_childOffsets
[
index
]
-
_childOffsets
[
index
-
1
]
==
_getOffset
(
element
,
innerConstraints
));
// If this assertion fires, it means you didn't call "invalidate"
// before changing the size of one of your items.
assert
(
_debugIsSameSize
(
element
,
index
,
constraints
));
}
// Remember the element for when we place the children.
final
_ChildKey
key
=
new
_ChildKey
.
fromWidget
(
element
.
widget
);
...
...
packages/flutter/lib/src/fn3/scrollable.dart
View file @
5f9cb4aa
...
...
@@ -14,6 +14,7 @@ import 'package:sky/src/fn3/basic.dart';
import
'package:sky/src/fn3/framework.dart'
;
import
'package:sky/src/fn3/gesture_detector.dart'
;
import
'package:sky/src/fn3/homogeneous_viewport.dart'
;
import
'package:sky/src/fn3/mixed_viewport.dart'
;
// The gesture velocity properties are pixels/second, config min,max limits are pixels/ms
const
double
_kMillisecondsPerSecond
=
1000.0
;
...
...
@@ -386,7 +387,7 @@ abstract class ScrollableWidgetListState<T extends ScrollableWidgetList> extends
}
void
_updateScrollBehavior
()
{
// if you don't call this from build()
or syncConstructorArguments()
, you must call it from setState().
// if you don't call this from build(), you must call it from setState().
double
contentExtent
=
config
.
itemExtent
*
itemCount
;
if
(
config
.
padding
!=
null
)
contentExtent
+=
_leadingPadding
+
_trailingPadding
;
...
...
@@ -536,4 +537,83 @@ class PageableListState<T> extends ScrollableListState<T, PageableList<T>> {
}
}
// TODO(abarth): ScrollableMixedWidgetList
/// A general scrollable list for a large number of children that might not all
/// have the same height. Prefer [ScrollableWidgetList] when all the children
/// have the same height because it can use that property to be more efficient.
/// Prefer [ScrollableViewport] with a single child.
class
ScrollableMixedWidgetList
extends
Scrollable
{
ScrollableMixedWidgetList
({
Key
key
,
double
initialScrollOffset
,
this
.
builder
,
this
.
token
,
this
.
onInvalidatorAvailable
})
:
super
(
key:
key
,
initialScrollOffset:
initialScrollOffset
);
final
IndexedBuilder
builder
;
final
Object
token
;
final
InvalidatorAvailableCallback
onInvalidatorAvailable
;
ScrollableMixedWidgetListState
createState
()
=>
new
ScrollableMixedWidgetListState
();
}
class
ScrollableMixedWidgetListState
extends
ScrollableState
<
ScrollableMixedWidgetList
>
{
void
initState
(
BuildContext
context
)
{
super
.
initState
(
context
);
scrollBehavior
.
updateExtents
(
contentExtent:
double
.
INFINITY
);
}
ScrollBehavior
createScrollBehavior
()
=>
new
OverscrollBehavior
();
OverscrollBehavior
get
scrollBehavior
=>
super
.
scrollBehavior
;
void
_handleSizeChanged
(
Size
newSize
)
{
setState
(()
{
scrollBy
(
scrollBehavior
.
updateExtents
(
containerExtent:
newSize
.
height
,
scrollOffset:
scrollOffset
));
});
}
bool
_contentChanged
=
false
;
void
didUpdateConfig
(
ScrollableMixedWidgetList
oldConfig
)
{
super
.
didUpdateConfig
(
oldConfig
);
if
(
config
.
token
!=
oldConfig
.
token
)
{
// When the token changes the scrollable's contents may have changed.
// Remember as much so that after the new contents have been laid out we
// can adjust the scrollOffset so that the last page of content is still
// visible.
_contentChanged
=
true
;
}
}
void
_handleExtentsUpdate
(
double
newExtents
)
{
double
newScrollOffset
;
setState
(()
{
newScrollOffset
=
scrollBehavior
.
updateExtents
(
contentExtent:
newExtents
??
double
.
INFINITY
,
scrollOffset:
scrollOffset
);
});
if
(
_contentChanged
)
{
_contentChanged
=
false
;
scrollTo
(
newScrollOffset
);
}
}
Widget
buildContent
(
BuildContext
context
)
{
return
new
SizeObserver
(
callback:
_handleSizeChanged
,
child:
new
MixedViewport
(
startOffset:
scrollOffset
,
builder:
config
.
builder
,
token:
config
.
token
,
onInvalidatorAvailable:
config
.
onInvalidatorAvailable
,
onExtentsUpdate:
_handleExtentsUpdate
)
);
}
}
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment