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
0e70a97e
Unverified
Commit
0e70a97e
authored
Nov 02, 2022
by
Kate Lovett
Committed by
GitHub
Nov 02, 2022
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Refactor Animated[List, Grid, SliverList, SliverGrid] to share common code (#113793)
parent
1cfdac4b
Changes
4
Show whitespace changes
Inline
Side-by-side
Showing
4 changed files
with
672 additions
and
983 deletions
+672
-983
sliver_grid.dart
packages/flutter/lib/src/rendering/sliver_grid.dart
+3
-3
animated_list.dart
packages/flutter/lib/src/widgets/animated_list.dart
+0
-700
animated_scroll_view.dart
packages/flutter/lib/src/widgets/animated_scroll_view.dart
+668
-278
widgets.dart
packages/flutter/lib/widgets.dart
+1
-2
No files found.
packages/flutter/lib/src/rendering/sliver_grid.dart
View file @
0e70a97e
...
...
@@ -462,9 +462,9 @@ class SliverGridDelegateWithMaxCrossAxisExtent extends SliverGridDelegate {
SliverGridLayout
getLayout
(
SliverConstraints
constraints
)
{
assert
(
_debugAssertIsValid
(
constraints
.
crossAxisExtent
));
int
crossAxisCount
=
(
constraints
.
crossAxisExtent
/
(
maxCrossAxisExtent
+
crossAxisSpacing
)).
ceil
();
//
TODO(gspencergoog): Figure out why we need this in release mode (and only
//
in release mode). https://github.com/flutter/flutter/issues/113109
crossAxisCount
=
crossAxisCount
<
1
?
1
:
crossAxisCount
;
//
Ensure a minimum count of 1, can be zero and result in an infinite extent
//
below when the window size is 0.
crossAxisCount
=
math
.
max
(
1
,
crossAxisCount
)
;
final
double
usableCrossAxisExtent
=
math
.
max
(
0.0
,
constraints
.
crossAxisExtent
-
crossAxisSpacing
*
(
crossAxisCount
-
1
),
...
...
packages/flutter/lib/src/widgets/animated_list.dart
deleted
100644 → 0
View file @
1cfdac4b
// Copyright 2014 The Flutter 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
'package:flutter/foundation.dart'
;
import
'animated_grid.dart'
;
import
'basic.dart'
;
import
'framework.dart'
;
import
'scroll_controller.dart'
;
import
'scroll_physics.dart'
;
import
'scroll_view.dart'
;
import
'sliver.dart'
;
import
'ticker_provider.dart'
;
/// Signature for the builder callback used by [AnimatedList].
///
/// This is deprecated, use the identical [AnimatedItemBuilder] instead.
@Deprecated
(
'Use AnimatedItemBuilder instead. '
'This feature was deprecated after v3.5.0-4.0.pre.'
,
)
typedef
AnimatedListItemBuilder
=
Widget
Function
(
BuildContext
context
,
int
index
,
Animation
<
double
>
animation
);
/// Signature for the builder callback used by [AnimatedListState.removeItem].
///
/// This is deprecated, use the identical [AnimatedRemovedItemBuilder]
/// instead.
@Deprecated
(
'Use AnimatedRemovedItemBuilder instead. '
'This feature was deprecated after v3.5.0-4.0.pre.'
,
)
typedef
AnimatedListRemovedItemBuilder
=
Widget
Function
(
BuildContext
context
,
Animation
<
double
>
animation
);
// The default insert/remove animation duration.
const
Duration
_kDuration
=
Duration
(
milliseconds:
300
);
// Incoming and outgoing AnimatedList items.
class
_ActiveItem
implements
Comparable
<
_ActiveItem
>
{
_ActiveItem
.
incoming
(
this
.
controller
,
this
.
itemIndex
)
:
removedItemBuilder
=
null
;
_ActiveItem
.
outgoing
(
this
.
controller
,
this
.
itemIndex
,
this
.
removedItemBuilder
);
_ActiveItem
.
index
(
this
.
itemIndex
)
:
controller
=
null
,
removedItemBuilder
=
null
;
final
AnimationController
?
controller
;
final
AnimatedRemovedItemBuilder
?
removedItemBuilder
;
int
itemIndex
;
@override
int
compareTo
(
_ActiveItem
other
)
=>
itemIndex
-
other
.
itemIndex
;
}
/// A scrolling container that animates items when they are inserted or removed.
///
/// This widget's [AnimatedListState] can be used to dynamically insert or
/// remove items. To refer to the [AnimatedListState] either provide a
/// [GlobalKey] or use the static [of] method from an item's input callback.
///
/// This widget is similar to one created by [ListView.builder].
///
/// {@youtube 560 315 https://www.youtube.com/watch?v=ZtfItHwFlZ8}
///
/// {@tool dartpad}
/// This sample application uses an [AnimatedList] to create an effect when
/// items are removed or added to the list.
///
/// ** See code in examples/api/lib/widgets/animated_list/animated_list.0.dart **
/// {@end-tool}
///
/// See also:
///
/// * [SliverAnimatedList], a sliver that animates items when they are inserted
/// or removed from a list.
/// * [SliverAnimatedGrid], a sliver which animates items when they are
/// inserted or removed from a grid.
/// * [AnimatedGrid], a non-sliver scrolling container that animates items when
/// they are inserted or removed in a grid.
class
AnimatedList
extends
StatefulWidget
{
/// Creates a scrolling container that animates items when they are inserted
/// or removed.
const
AnimatedList
({
super
.
key
,
required
this
.
itemBuilder
,
this
.
initialItemCount
=
0
,
this
.
scrollDirection
=
Axis
.
vertical
,
this
.
reverse
=
false
,
this
.
controller
,
this
.
primary
,
this
.
physics
,
this
.
shrinkWrap
=
false
,
this
.
padding
,
this
.
clipBehavior
=
Clip
.
hardEdge
,
})
:
assert
(
itemBuilder
!=
null
),
assert
(
initialItemCount
!=
null
&&
initialItemCount
>=
0
);
/// Called, as needed, to build list item widgets.
///
/// List items are only built when they're scrolled into view.
///
/// The [AnimatedItemBuilder] index parameter indicates the item's
/// position in the list. The value of the index parameter will be between 0
/// and [initialItemCount] plus the total number of items that have been
/// inserted with [AnimatedListState.insertItem] and less the total number of
/// items that have been removed with [AnimatedListState.removeItem].
///
/// Implementations of this callback should assume that
/// [AnimatedListState.removeItem] removes an item immediately.
final
AnimatedItemBuilder
itemBuilder
;
/// {@template flutter.widgets.animatedList.initialItemCount}
/// The number of items the list will start with.
///
/// The appearance of the initial items is not animated. They
/// are created, as needed, by [itemBuilder] with an animation parameter
/// of [kAlwaysCompleteAnimation].
/// {@endtemplate}
final
int
initialItemCount
;
/// The axis along which the scroll view scrolls.
///
/// Defaults to [Axis.vertical].
final
Axis
scrollDirection
;
/// Whether the scroll view scrolls in the reading direction.
///
/// For example, if the reading direction is left-to-right and
/// [scrollDirection] is [Axis.horizontal], then the scroll view scrolls from
/// left to right when [reverse] is false and from right to left when
/// [reverse] is true.
///
/// Similarly, if [scrollDirection] is [Axis.vertical], then the scroll view
/// scrolls from top to bottom when [reverse] is false and from bottom to top
/// when [reverse] is true.
///
/// Defaults to false.
final
bool
reverse
;
/// An object that can be used to control the position to which this scroll
/// view is scrolled.
///
/// Must be null if [primary] is true.
///
/// A [ScrollController] serves several purposes. It can be used to control
/// the initial scroll position (see [ScrollController.initialScrollOffset]).
/// It can be used to control whether the scroll view should automatically
/// save and restore its scroll position in the [PageStorage] (see
/// [ScrollController.keepScrollOffset]). It can be used to read the current
/// scroll position (see [ScrollController.offset]), or change it (see
/// [ScrollController.animateTo]).
final
ScrollController
?
controller
;
/// Whether this is the primary scroll view associated with the parent
/// [PrimaryScrollController].
///
/// On iOS, this identifies the scroll view that will scroll to top in
/// response to a tap in the status bar.
///
/// Defaults to true when [scrollDirection] is [Axis.vertical] and
/// [controller] is null.
final
bool
?
primary
;
/// How the scroll view should respond to user input.
///
/// For example, determines how the scroll view continues to animate after the
/// user stops dragging the scroll view.
///
/// Defaults to matching platform conventions.
final
ScrollPhysics
?
physics
;
/// Whether the extent of the scroll view in the [scrollDirection] should be
/// determined by the contents being viewed.
///
/// If the scroll view does not shrink wrap, then the scroll view will expand
/// to the maximum allowed size in the [scrollDirection]. If the scroll view
/// has unbounded constraints in the [scrollDirection], then [shrinkWrap] must
/// be true.
///
/// Shrink wrapping the content of the scroll view is significantly more
/// expensive than expanding to the maximum allowed size because the content
/// can expand and contract during scrolling, which means the size of the
/// scroll view needs to be recomputed whenever the scroll position changes.
///
/// Defaults to false.
final
bool
shrinkWrap
;
/// The amount of space by which to inset the children.
final
EdgeInsetsGeometry
?
padding
;
/// {@macro flutter.material.Material.clipBehavior}
///
/// Defaults to [Clip.hardEdge].
final
Clip
clipBehavior
;
/// The state from the closest instance of this class that encloses the given
/// context.
///
/// This method is typically used by [AnimatedList] item widgets that insert
/// or remove items in response to user input.
///
/// If no [AnimatedList] surrounds the context given, then this function will
/// assert in debug mode and throw an exception in release mode.
///
/// This method can be expensive (it walks the element tree).
///
/// See also:
///
/// * [maybeOf], a similar function that will return null if no
/// [AnimatedList] ancestor is found.
static
AnimatedListState
of
(
BuildContext
context
)
{
assert
(
context
!=
null
);
final
AnimatedListState
?
result
=
context
.
findAncestorStateOfType
<
AnimatedListState
>();
assert
(()
{
if
(
result
==
null
)
{
throw
FlutterError
.
fromParts
(<
DiagnosticsNode
>[
ErrorSummary
(
'AnimatedList.of() called with a context that does not contain an AnimatedList.'
),
ErrorDescription
(
'No AnimatedList ancestor could be found starting from the context that was passed to AnimatedList.of().'
,
),
ErrorHint
(
'This can happen when the context provided is from the same StatefulWidget that '
'built the AnimatedList. Please see the AnimatedList documentation for examples '
'of how to refer to an AnimatedListState object:
\n
'
' https://api.flutter.dev/flutter/widgets/AnimatedListState-class.html'
,
),
context
.
describeElement
(
'The context used was'
),
]);
}
return
true
;
}());
return
result
!;
}
/// The state from the closest instance of this class that encloses the given
/// context.
///
/// This method is typically used by [AnimatedList] item widgets that insert
/// or remove items in response to user input.
///
/// If no [AnimatedList] surrounds the context given, then this function will
/// return null.
///
/// This method can be expensive (it walks the element tree).
///
/// See also:
///
/// * [of], a similar function that will throw if no [AnimatedList] ancestor
/// is found.
static
AnimatedListState
?
maybeOf
(
BuildContext
context
)
{
assert
(
context
!=
null
);
return
context
.
findAncestorStateOfType
<
AnimatedListState
>();
}
@override
AnimatedListState
createState
()
=>
AnimatedListState
();
}
/// The state for a scrolling container that animates items when they are
/// inserted or removed.
///
/// When an item is inserted with [insertItem] an animation begins running. The
/// animation is passed to [AnimatedList.itemBuilder] whenever the item's widget
/// is needed.
///
/// When an item is removed with [removeItem] its animation is reversed.
/// The removed item's animation is passed to the [removeItem] builder
/// parameter.
///
/// An app that needs to insert or remove items in response to an event
/// can refer to the [AnimatedList]'s state with a global key:
///
/// ```dart
/// // (e.g. in a stateful widget)
/// GlobalKey<AnimatedListState> listKey = GlobalKey<AnimatedListState>();
///
/// // ...
///
/// @override
/// Widget build(BuildContext context) {
/// return AnimatedList(
/// key: listKey,
/// itemBuilder: (BuildContext context, int index, Animation<double> animation) {
/// return const Placeholder();
/// },
/// );
/// }
///
/// // ...
///
/// void _updateList() {
/// // adds "123" to the AnimatedList
/// listKey.currentState!.insertItem(123);
/// }
/// ```
///
/// [AnimatedList] item input handlers can also refer to their [AnimatedListState]
/// with the static [AnimatedList.of] method.
class
AnimatedListState
extends
State
<
AnimatedList
>
with
TickerProviderStateMixin
<
AnimatedList
>
{
final
GlobalKey
<
SliverAnimatedListState
>
_sliverAnimatedListKey
=
GlobalKey
();
/// Insert an item at [index] and start an animation that will be passed
/// to [AnimatedList.itemBuilder] when the item is visible.
///
/// This method's semantics are the same as Dart's [List.insert] method:
/// it increases the length of the list by one and shifts all items at or
/// after [index] towards the end of the list.
void
insertItem
(
int
index
,
{
Duration
duration
=
_kDuration
})
{
_sliverAnimatedListKey
.
currentState
!.
insertItem
(
index
,
duration:
duration
);
}
/// Remove the item at [index] and start an animation that will be passed
/// to [builder] when the item is visible.
///
/// Items are removed immediately. After an item has been removed, its index
/// will no longer be passed to the [AnimatedList.itemBuilder]. However the
/// item will still appear in the list for [duration] and during that time
/// [builder] must construct its widget as needed.
///
/// This method's semantics are the same as Dart's [List.remove] method:
/// it decreases the length of the list by one and shifts all items at or
/// before [index] towards the beginning of the list.
void
removeItem
(
int
index
,
AnimatedRemovedItemBuilder
builder
,
{
Duration
duration
=
_kDuration
})
{
_sliverAnimatedListKey
.
currentState
!.
removeItem
(
index
,
builder
,
duration:
duration
);
}
@override
Widget
build
(
BuildContext
context
)
{
return
CustomScrollView
(
scrollDirection:
widget
.
scrollDirection
,
reverse:
widget
.
reverse
,
controller:
widget
.
controller
,
primary:
widget
.
primary
,
physics:
widget
.
physics
,
shrinkWrap:
widget
.
shrinkWrap
,
clipBehavior:
widget
.
clipBehavior
,
slivers:
<
Widget
>[
SliverPadding
(
padding:
widget
.
padding
??
EdgeInsets
.
zero
,
sliver:
SliverAnimatedList
(
key:
_sliverAnimatedListKey
,
itemBuilder:
widget
.
itemBuilder
,
initialItemCount:
widget
.
initialItemCount
,
),
),
],
);
}
}
/// A sliver that animates items when they are inserted or removed.
///
/// This widget's [SliverAnimatedListState] can be used to dynamically insert or
/// remove items. To refer to the [SliverAnimatedListState] either provide a
/// [GlobalKey] or use the static [SliverAnimatedList.of] method from an item's
/// input callback.
///
/// {@tool dartpad}
/// This sample application uses a [SliverAnimatedList] to create an animated
/// effect when items are removed or added to the list.
///
/// ** See code in examples/api/lib/widgets/animated_list/sliver_animated_list.0.dart **
/// {@end-tool}
///
/// See also:
///
/// * [SliverList], which does not animate items when they are inserted or
/// removed.
/// * [AnimatedList], a non-sliver scrolling container that animates items when
/// they are inserted or removed.
/// * [SliverAnimatedGrid], a sliver which animates items when they are
/// inserted into or removed from a grid.
/// * [AnimatedGrid], a non-sliver scrolling container that animates items when
/// they are inserted into or removed from a grid.
class
SliverAnimatedList
extends
StatefulWidget
{
/// Creates a sliver that animates items when they are inserted or removed.
const
SliverAnimatedList
({
super
.
key
,
required
this
.
itemBuilder
,
this
.
findChildIndexCallback
,
this
.
initialItemCount
=
0
,
})
:
assert
(
itemBuilder
!=
null
),
assert
(
initialItemCount
!=
null
&&
initialItemCount
>=
0
);
/// Called, as needed, to build list item widgets.
///
/// List items are only built when they're scrolled into view.
///
/// The [AnimatedItemBuilder] index parameter indicates the item's
/// position in the list. The value of the index parameter will be between 0
/// and [initialItemCount] plus the total number of items that have been
/// inserted with [SliverAnimatedListState.insertItem] and less the total
/// number of items that have been removed with
/// [SliverAnimatedListState.removeItem].
///
/// Implementations of this callback should assume that
/// [SliverAnimatedListState.removeItem] removes an item immediately.
final
AnimatedItemBuilder
itemBuilder
;
/// {@macro flutter.widgets.SliverChildBuilderDelegate.findChildIndexCallback}
final
ChildIndexGetter
?
findChildIndexCallback
;
/// {@macro flutter.widgets.animatedList.initialItemCount}
final
int
initialItemCount
;
@override
SliverAnimatedListState
createState
()
=>
SliverAnimatedListState
();
/// The state from the closest instance of this class that encloses the given
/// context.
///
/// This method is typically used by [SliverAnimatedList] item widgets that
/// insert or remove items in response to user input.
///
/// If no [SliverAnimatedList] surrounds the context given, then this function
/// will assert in debug mode and throw an exception in release mode.
///
/// This method can be expensive (it walks the element tree).
///
/// See also:
///
/// * [maybeOf], a similar function that will return null if no
/// [SliverAnimatedList] ancestor is found.
static
SliverAnimatedListState
of
(
BuildContext
context
)
{
assert
(
context
!=
null
);
final
SliverAnimatedListState
?
result
=
context
.
findAncestorStateOfType
<
SliverAnimatedListState
>();
assert
(()
{
if
(
result
==
null
)
{
throw
FlutterError
(
'SliverAnimatedList.of() called with a context that does not contain a SliverAnimatedList.
\n
'
'No SliverAnimatedListState ancestor could be found starting from the '
'context that was passed to SliverAnimatedListState.of(). This can '
'happen when the context provided is from the same StatefulWidget that '
'built the AnimatedList. Please see the SliverAnimatedList documentation '
'for examples of how to refer to an AnimatedListState object: '
'https://api.flutter.dev/flutter/widgets/SliverAnimatedListState-class.html
\n
'
'The context used was:
\n
'
'
$context
'
,
);
}
return
true
;
}());
return
result
!;
}
/// The state from the closest instance of this class that encloses the given
/// context.
///
/// This method is typically used by [SliverAnimatedList] item widgets that
/// insert or remove items in response to user input.
///
/// If no [SliverAnimatedList] surrounds the context given, then this function
/// will return null.
///
/// This method can be expensive (it walks the element tree).
///
/// See also:
///
/// * [of], a similar function that will throw if no [SliverAnimatedList]
/// ancestor is found.
static
SliverAnimatedListState
?
maybeOf
(
BuildContext
context
)
{
assert
(
context
!=
null
);
return
context
.
findAncestorStateOfType
<
SliverAnimatedListState
>();
}
}
/// The state for a sliver that animates items when they are
/// inserted or removed.
///
/// When an item is inserted with [insertItem] an animation begins running. The
/// animation is passed to [SliverAnimatedList.itemBuilder] whenever the item's
/// widget is needed.
///
/// When an item is removed with [removeItem] its animation is reversed.
/// The removed item's animation is passed to the [removeItem] builder
/// parameter.
///
/// An app that needs to insert or remove items in response to an event
/// can refer to the [SliverAnimatedList]'s state with a global key:
///
/// ```dart
/// // (e.g. in a stateful widget)
/// GlobalKey<AnimatedListState> listKey = GlobalKey<AnimatedListState>();
///
/// // ...
///
/// @override
/// Widget build(BuildContext context) {
/// return AnimatedList(
/// key: listKey,
/// itemBuilder: (BuildContext context, int index, Animation<double> animation) {
/// return const Placeholder();
/// },
/// );
/// }
///
/// // ...
///
/// void _updateList() {
/// // adds "123" to the AnimatedList
/// listKey.currentState!.insertItem(123);
/// }
/// ```
///
/// [SliverAnimatedList] item input handlers can also refer to their
/// [SliverAnimatedListState] with the static [SliverAnimatedList.of] method.
class
SliverAnimatedListState
extends
State
<
SliverAnimatedList
>
with
TickerProviderStateMixin
{
final
List
<
_ActiveItem
>
_incomingItems
=
<
_ActiveItem
>[];
final
List
<
_ActiveItem
>
_outgoingItems
=
<
_ActiveItem
>[];
int
_itemsCount
=
0
;
@override
void
initState
()
{
super
.
initState
();
_itemsCount
=
widget
.
initialItemCount
;
}
@override
void
dispose
()
{
for
(
final
_ActiveItem
item
in
_incomingItems
.
followedBy
(
_outgoingItems
))
{
item
.
controller
!.
dispose
();
}
super
.
dispose
();
}
_ActiveItem
?
_removeActiveItemAt
(
List
<
_ActiveItem
>
items
,
int
itemIndex
)
{
final
int
i
=
binarySearch
(
items
,
_ActiveItem
.
index
(
itemIndex
));
return
i
==
-
1
?
null
:
items
.
removeAt
(
i
);
}
_ActiveItem
?
_activeItemAt
(
List
<
_ActiveItem
>
items
,
int
itemIndex
)
{
final
int
i
=
binarySearch
(
items
,
_ActiveItem
.
index
(
itemIndex
));
return
i
==
-
1
?
null
:
items
[
i
];
}
// The insertItem() and removeItem() index parameters are defined as if the
// removeItem() operation removed the corresponding list entry immediately.
// The entry is only actually removed from the ListView when the remove animation
// finishes. The entry is added to _outgoingItems when removeItem is called
// and removed from _outgoingItems when the remove animation finishes.
int
_indexToItemIndex
(
int
index
)
{
int
itemIndex
=
index
;
for
(
final
_ActiveItem
item
in
_outgoingItems
)
{
if
(
item
.
itemIndex
<=
itemIndex
)
{
itemIndex
+=
1
;
}
else
{
break
;
}
}
return
itemIndex
;
}
int
_itemIndexToIndex
(
int
itemIndex
)
{
int
index
=
itemIndex
;
for
(
final
_ActiveItem
item
in
_outgoingItems
)
{
assert
(
item
.
itemIndex
!=
itemIndex
);
if
(
item
.
itemIndex
<
itemIndex
)
{
index
-=
1
;
}
else
{
break
;
}
}
return
index
;
}
SliverChildDelegate
_createDelegate
()
{
return
SliverChildBuilderDelegate
(
_itemBuilder
,
childCount:
_itemsCount
,
findChildIndexCallback:
widget
.
findChildIndexCallback
==
null
?
null
:
(
Key
key
)
{
final
int
?
index
=
widget
.
findChildIndexCallback
!(
key
);
return
index
!=
null
?
_indexToItemIndex
(
index
)
:
null
;
},
);
}
/// Insert an item at [index] and start an animation that will be passed to
/// [SliverAnimatedList.itemBuilder] when the item is visible.
///
/// This method's semantics are the same as Dart's [List.insert] method:
/// it increases the length of the list by one and shifts all items at or
/// after [index] towards the end of the list.
void
insertItem
(
int
index
,
{
Duration
duration
=
_kDuration
})
{
assert
(
index
!=
null
&&
index
>=
0
);
assert
(
duration
!=
null
);
final
int
itemIndex
=
_indexToItemIndex
(
index
);
assert
(
itemIndex
>=
0
&&
itemIndex
<=
_itemsCount
);
// Increment the incoming and outgoing item indices to account
// for the insertion.
for
(
final
_ActiveItem
item
in
_incomingItems
)
{
if
(
item
.
itemIndex
>=
itemIndex
)
{
item
.
itemIndex
+=
1
;
}
}
for
(
final
_ActiveItem
item
in
_outgoingItems
)
{
if
(
item
.
itemIndex
>=
itemIndex
)
{
item
.
itemIndex
+=
1
;
}
}
final
AnimationController
controller
=
AnimationController
(
duration:
duration
,
vsync:
this
,
);
final
_ActiveItem
incomingItem
=
_ActiveItem
.
incoming
(
controller
,
itemIndex
,
);
setState
(()
{
_incomingItems
..
add
(
incomingItem
)
..
sort
();
_itemsCount
+=
1
;
});
controller
.
forward
().
then
<
void
>((
_
)
{
_removeActiveItemAt
(
_incomingItems
,
incomingItem
.
itemIndex
)!.
controller
!.
dispose
();
});
}
/// Remove the item at [index] and start an animation that will be passed
/// to [builder] when the item is visible.
///
/// Items are removed immediately. After an item has been removed, its index
/// will no longer be passed to the [SliverAnimatedList.itemBuilder]. However
/// the item will still appear in the list for [duration] and during that time
/// [builder] must construct its widget as needed.
///
/// This method's semantics are the same as Dart's [List.remove] method:
/// it decreases the length of the list by one and shifts all items at or
/// before [index] towards the beginning of the list.
void
removeItem
(
int
index
,
AnimatedRemovedItemBuilder
builder
,
{
Duration
duration
=
_kDuration
})
{
assert
(
index
!=
null
&&
index
>=
0
);
assert
(
builder
!=
null
);
assert
(
duration
!=
null
);
final
int
itemIndex
=
_indexToItemIndex
(
index
);
assert
(
itemIndex
>=
0
&&
itemIndex
<
_itemsCount
);
assert
(
_activeItemAt
(
_outgoingItems
,
itemIndex
)
==
null
);
final
_ActiveItem
?
incomingItem
=
_removeActiveItemAt
(
_incomingItems
,
itemIndex
);
final
AnimationController
controller
=
incomingItem
?.
controller
??
AnimationController
(
duration:
duration
,
value:
1.0
,
vsync:
this
);
final
_ActiveItem
outgoingItem
=
_ActiveItem
.
outgoing
(
controller
,
itemIndex
,
builder
);
setState
(()
{
_outgoingItems
..
add
(
outgoingItem
)
..
sort
();
});
controller
.
reverse
().
then
<
void
>((
void
value
)
{
_removeActiveItemAt
(
_outgoingItems
,
outgoingItem
.
itemIndex
)!.
controller
!.
dispose
();
// Decrement the incoming and outgoing item indices to account
// for the removal.
for
(
final
_ActiveItem
item
in
_incomingItems
)
{
if
(
item
.
itemIndex
>
outgoingItem
.
itemIndex
)
{
item
.
itemIndex
-=
1
;
}
}
for
(
final
_ActiveItem
item
in
_outgoingItems
)
{
if
(
item
.
itemIndex
>
outgoingItem
.
itemIndex
)
{
item
.
itemIndex
-=
1
;
}
}
setState
(()
=>
_itemsCount
-=
1
);
});
}
Widget
_itemBuilder
(
BuildContext
context
,
int
itemIndex
)
{
final
_ActiveItem
?
outgoingItem
=
_activeItemAt
(
_outgoingItems
,
itemIndex
);
if
(
outgoingItem
!=
null
)
{
return
outgoingItem
.
removedItemBuilder
!(
context
,
outgoingItem
.
controller
!.
view
,
);
}
final
_ActiveItem
?
incomingItem
=
_activeItemAt
(
_incomingItems
,
itemIndex
);
final
Animation
<
double
>
animation
=
incomingItem
?.
controller
?.
view
??
kAlwaysCompleteAnimation
;
return
widget
.
itemBuilder
(
context
,
_itemIndexToIndex
(
itemIndex
),
animation
,
);
}
@override
Widget
build
(
BuildContext
context
)
{
return
SliverList
(
delegate:
_createDelegate
(),
);
}
}
packages/flutter/lib/src/widgets/animated_
grid
.dart
→
packages/flutter/lib/src/widgets/animated_
scroll_view
.dart
View file @
0e70a97e
...
...
@@ -12,54 +12,173 @@ import 'scroll_view.dart';
import
'sliver.dart'
;
import
'ticker_provider.dart'
;
/// Signature for the builder callback used by widgets like [AnimatedGrid] to
/// build their animated children.
/// A scrolling container that animates items when they are inserted or removed.
///
/// The `context` argument is the build context where the widget will be
/// created, the `index` is the index of the item to be built, and the
/// `animation` is an [Animation] that should be used to animate an entry
/// transition for the widget that is built.
/// This widget's [AnimatedListState] can be used to dynamically insert or
/// remove items. To refer to the [AnimatedListState] either provide a
/// [GlobalKey] or use the static [of] method from an item's input callback.
///
///
See also:
///
This widget is similar to one created by [ListView.builder].
///
/// * [AnimatedRemovedItemBuilder], a builder that is for removing items with
/// animations instead of adding them.
typedef
AnimatedItemBuilder
=
Widget
Function
(
BuildContext
context
,
int
index
,
Animation
<
double
>
animation
);
/// Signature for the builder callback used by widgets like [AnimatedGrid] (in
/// [AnimatedGridState.removeItem]) to animated their children after they have
/// been removed.
/// {@youtube 560 315 https://www.youtube.com/watch?v=ZtfItHwFlZ8}
///
/// The `context` argument is the build context where the widget will be
/// created, and the `animation` is an [Animation] that should be used to
/// animate an exit transition for the widget that is built.
/// {@tool dartpad}
/// This sample application uses an [AnimatedList] to create an effect when
/// items are removed or added to the list.
///
/// ** See code in examples/api/lib/widgets/animated_list/animated_list.0.dart **
/// {@end-tool}
///
/// See also:
///
/// * [AnimatedItemBuilder], a builder that is for adding items with animations
/// instead of removing them.
typedef
AnimatedRemovedItemBuilder
=
Widget
Function
(
BuildContext
context
,
Animation
<
double
>
animation
);
/// * [SliverAnimatedList], a sliver that animates items when they are inserted
/// or removed from a list.
/// * [SliverAnimatedGrid], a sliver which animates items when they are
/// inserted or removed from a grid.
/// * [AnimatedGrid], a non-sliver scrolling container that animates items when
/// they are inserted or removed in a grid.
class
AnimatedList
extends
_AnimatedScrollView
{
/// Creates a scrolling container that animates items when they are inserted
/// or removed.
const
AnimatedList
({
super
.
key
,
required
super
.
itemBuilder
,
super
.
initialItemCount
=
0
,
super
.
scrollDirection
=
Axis
.
vertical
,
super
.
reverse
=
false
,
super
.
controller
,
super
.
primary
,
super
.
physics
,
super
.
shrinkWrap
=
false
,
super
.
padding
,
super
.
clipBehavior
=
Clip
.
hardEdge
,
})
:
assert
(
itemBuilder
!=
null
),
assert
(
initialItemCount
!=
null
&&
initialItemCount
>=
0
);
// The default insert/remove animation duration.
const
Duration
_kDuration
=
Duration
(
milliseconds:
300
);
/// The state from the closest instance of this class that encloses the given
/// context.
///
/// This method is typically used by [AnimatedList] item widgets that insert
/// or remove items in response to user input.
///
/// If no [AnimatedList] surrounds the context given, then this function will
/// assert in debug mode and throw an exception in release mode.
///
/// This method can be expensive (it walks the element tree).
///
/// This method does not create a dependency, and so will not cause rebuilding
/// when the state changes.
///
/// See also:
///
/// * [maybeOf], a similar function that will return null if no
/// [AnimatedList] ancestor is found.
static
AnimatedListState
of
(
BuildContext
context
)
{
assert
(
context
!=
null
);
final
AnimatedListState
?
result
=
AnimatedList
.
maybeOf
(
context
);
assert
(()
{
if
(
result
==
null
)
{
throw
FlutterError
.
fromParts
(<
DiagnosticsNode
>[
ErrorSummary
(
'AnimatedList.of() called with a context that does not contain an AnimatedList.'
),
ErrorDescription
(
'No AnimatedList ancestor could be found starting from the context that was passed to AnimatedList.of().'
,
),
ErrorHint
(
'This can happen when the context provided is from the same StatefulWidget that '
'built the AnimatedList. Please see the AnimatedList documentation for examples '
'of how to refer to an AnimatedListState object:
\n
'
' https://api.flutter.dev/flutter/widgets/AnimatedListState-class.html'
,
),
context
.
describeElement
(
'The context used was'
),
]);
}
return
true
;
}());
return
result
!;
}
// Incoming and outgoing AnimatedGrid items.
class
_ActiveItem
implements
Comparable
<
_ActiveItem
>
{
_ActiveItem
.
incoming
(
this
.
controller
,
this
.
itemIndex
)
:
removedItemBuilder
=
null
;
_ActiveItem
.
outgoing
(
this
.
controller
,
this
.
itemIndex
,
this
.
removedItemBuilder
);
_ActiveItem
.
index
(
this
.
itemIndex
)
:
controller
=
null
,
removedItemBuilder
=
null
;
/// The [AnimatedListState] from the closest instance of [AnimatedList] that encloses the given
/// context.
///
/// This method is typically used by [AnimatedList] item widgets that insert
/// or remove items in response to user input.
///
/// If no [AnimatedList] surrounds the context given, then this function will
/// return null.
///
/// This method can be expensive (it walks the element tree).
///
/// This method does not create a dependency, and so will not cause rebuilding
/// when the state changes.
///
/// See also:
///
/// * [of], a similar function that will throw if no [AnimatedList] ancestor
/// is found.
static
AnimatedListState
?
maybeOf
(
BuildContext
context
)
{
assert
(
context
!=
null
);
return
context
.
findAncestorStateOfType
<
AnimatedListState
>();
}
final
AnimationController
?
controller
;
final
AnimatedRemovedItemBuilder
?
removedItemBuilder
;
int
itemIndex
;
@override
AnimatedListState
createState
()
=>
AnimatedListState
();
}
/// The [AnimatedListState] for [AnimatedList], a scrolling list container that animates items when they are
/// inserted or removed.
///
/// When an item is inserted with [insertItem] an animation begins running. The
/// animation is passed to [AnimatedList.itemBuilder] whenever the item's widget
/// is needed.
///
/// When an item is removed with [removeItem] its animation is reversed.
/// The removed item's animation is passed to the [removeItem] builder
/// parameter.
///
/// An app that needs to insert or remove items in response to an event
/// can refer to the [AnimatedList]'s state with a global key:
///
/// ```dart
/// // (e.g. in a stateful widget)
/// GlobalKey<AnimatedListState> listKey = GlobalKey<AnimatedListState>();
///
/// // ...
///
/// @override
/// Widget build(BuildContext context) {
/// return AnimatedList(
/// key: listKey,
/// itemBuilder: (BuildContext context, int index, Animation<double> animation) {
/// return const Placeholder();
/// },
/// );
/// }
///
/// // ...
///
/// void _updateList() {
/// // adds "123" to the AnimatedList
/// listKey.currentState!.insertItem(123);
/// }
/// ```
///
/// [AnimatedList] item input handlers can also refer to their [AnimatedListState]
/// with the static [AnimatedList.of] method.
class
AnimatedListState
extends
_AnimatedScrollViewState
<
AnimatedList
>
{
@override
int
compareTo
(
_ActiveItem
other
)
=>
itemIndex
-
other
.
itemIndex
;
Widget
build
(
BuildContext
context
)
{
return
_wrap
(
SliverAnimatedList
(
key:
_sliverAnimatedMultiBoxKey
,
itemBuilder:
widget
.
itemBuilder
,
initialItemCount:
widget
.
initialItemCount
,
),
);
}
}
/// A scrolling container that animates items when they are inserted
or removed
/// A scrolling container that animates items when they are inserted
into or removed from a grid.
/// in a grid.
///
/// This widget's [AnimatedGridState] can be used to dynamically insert or
...
...
@@ -83,51 +202,200 @@ class _ActiveItem implements Comparable<_ActiveItem> {
/// a list instead of a grid.
/// * [AnimatedList], which animates items added and removed from a list instead
/// of a grid.
class
AnimatedGrid
extends
StatefulWidget
{
class
AnimatedGrid
extends
_AnimatedScrollView
{
/// Creates a scrolling container that animates items when they are inserted
/// or removed.
const
AnimatedGrid
({
super
.
key
,
required
this
.
itemBuilder
,
required
super
.
itemBuilder
,
required
this
.
gridDelegate
,
super
.
initialItemCount
=
0
,
super
.
scrollDirection
=
Axis
.
vertical
,
super
.
reverse
=
false
,
super
.
controller
,
super
.
primary
,
super
.
physics
,
super
.
padding
,
super
.
clipBehavior
=
Clip
.
hardEdge
,
})
:
assert
(
itemBuilder
!=
null
),
assert
(
initialItemCount
!=
null
&&
initialItemCount
>=
0
);
/// {@template flutter.widgets.AnimatedGrid.gridDelegate}
/// A delegate that controls the layout of the children within the
/// [AnimatedGrid].
///
/// See also:
///
/// * [SliverGridDelegateWithFixedCrossAxisCount], which creates a layout with
/// a fixed number of tiles in the cross axis.
/// * [SliverGridDelegateWithMaxCrossAxisExtent], which creates a layout with
/// tiles that have a maximum cross-axis extent.
/// {@endtemplate}
final
SliverGridDelegate
gridDelegate
;
/// The state from the closest instance of this class that encloses the given
/// context.
///
/// This method is typically used by [AnimatedGrid] item widgets that insert
/// or remove items in response to user input.
///
/// If no [AnimatedGrid] surrounds the context given, then this function will
/// assert in debug mode and throw an exception in release mode.
///
/// This method can be expensive (it walks the element tree).
///
/// This method does not create a dependency, and so will not cause rebuilding
/// when the state changes.
///
/// See also:
///
/// * [maybeOf], a similar function that will return null if no
/// [AnimatedGrid] ancestor is found.
static
AnimatedGridState
of
(
BuildContext
context
)
{
assert
(
context
!=
null
);
final
AnimatedGridState
?
result
=
AnimatedGrid
.
maybeOf
(
context
);
assert
(()
{
if
(
result
==
null
)
{
throw
FlutterError
.
fromParts
(<
DiagnosticsNode
>[
ErrorSummary
(
'AnimatedGrid.of() called with a context that does not contain an AnimatedGrid.'
),
ErrorDescription
(
'No AnimatedGrid ancestor could be found starting from the context that was passed to AnimatedGrid.of().'
,
),
ErrorHint
(
'This can happen when the context provided is from the same StatefulWidget that '
'built the AnimatedGrid. Please see the AnimatedGrid documentation for examples '
'of how to refer to an AnimatedGridState object:
\n
'
' https://api.flutter.dev/flutter/widgets/AnimatedGridState-class.html'
,
),
context
.
describeElement
(
'The context used was'
),
]);
}
return
true
;
}());
return
result
!;
}
/// The state from the closest instance of this class that encloses the given
/// context.
///
/// This method is typically used by [AnimatedGrid] item widgets that insert
/// or remove items in response to user input.
///
/// If no [AnimatedGrid] surrounds the context given, then this function will
/// return null.
///
/// This method can be expensive (it walks the element tree).
///
/// This method does not create a dependency, and so will not cause rebuilding
/// when the state changes.
///
/// See also:
///
/// * [of], a similar function that will throw if no [AnimatedGrid] ancestor
/// is found.
static
AnimatedGridState
?
maybeOf
(
BuildContext
context
)
{
assert
(
context
!=
null
);
return
context
.
findAncestorStateOfType
<
AnimatedGridState
>();
}
@override
AnimatedGridState
createState
()
=>
AnimatedGridState
();
}
/// The [State] for an [AnimatedGrid] that animates items when they are
/// inserted or removed.
///
/// When an item is inserted with [insertItem] an animation begins running. The
/// animation is passed to [AnimatedGrid.itemBuilder] whenever the item's widget
/// is needed.
///
/// When an item is removed with [removeItem] its animation is reversed.
/// The removed item's animation is passed to the [removeItem] builder
/// parameter.
///
/// An app that needs to insert or remove items in response to an event
/// can refer to the [AnimatedGrid]'s state with a global key:
///
/// ```dart
/// // (e.g. in a stateful widget)
/// GlobalKey<AnimatedGridState> gridKey = GlobalKey<AnimatedGridState>();
///
/// // ...
///
/// @override
/// Widget build(BuildContext context) {
/// return AnimatedGrid(
/// key: gridKey,
/// itemBuilder: (BuildContext context, int index, Animation<double> animation) {
/// return const Placeholder();
/// },
/// gridDelegate: const SliverGridDelegateWithMaxCrossAxisExtent(maxCrossAxisExtent: 100.0),
/// );
/// }
///
/// // ...
///
/// void _updateGrid() {
/// // adds "123" to the AnimatedGrid
/// gridKey.currentState!.insertItem(123);
/// }
/// ```
///
/// [AnimatedGrid] item input handlers can also refer to their [AnimatedGridState]
/// with the static [AnimatedGrid.of] method.
class
AnimatedGridState
extends
_AnimatedScrollViewState
<
AnimatedGrid
>
{
@override
Widget
build
(
BuildContext
context
)
{
return
_wrap
(
SliverAnimatedGrid
(
key:
_sliverAnimatedMultiBoxKey
,
gridDelegate:
widget
.
gridDelegate
,
itemBuilder:
widget
.
itemBuilder
,
initialItemCount:
widget
.
initialItemCount
,
),
);
}
}
abstract
class
_AnimatedScrollView
extends
StatefulWidget
{
/// Creates a scrolling container that animates items when they are inserted
/// or removed.
const
_AnimatedScrollView
({
super
.
key
,
required
this
.
itemBuilder
,
this
.
initialItemCount
=
0
,
this
.
scrollDirection
=
Axis
.
vertical
,
this
.
reverse
=
false
,
this
.
controller
,
this
.
primary
,
this
.
physics
,
this
.
shrinkWrap
=
false
,
this
.
padding
,
this
.
clipBehavior
=
Clip
.
hardEdge
,
})
:
assert
(
itemBuilder
!=
null
),
assert
(
initialItemCount
!=
null
&&
initialItemCount
>=
0
);
/// Called, as needed, to build grid item widgets.
/// {@template flutter.widgets.AnimatedScrollView.itemBuilder}
/// Called, as needed, to build children widgets.
///
///
Grid items
are only built when they're scrolled into view.
///
Children
are only built when they're scrolled into view.
///
/// The [AnimatedItemBuilder] index parameter indicates the item's position in
/// the grid. The value of the index parameter will be between 0 and
/// [initialItemCount] plus the total number of items that have been inserted
/// with [AnimatedGridState.insertItem] and less the total number of items
/// that have been removed with [AnimatedGridState.removeItem].
/// The [AnimatedItemBuilder] index parameter indicates the item's
/// position in the scroll view. The value of the index parameter will be
/// between 0 and [initialItemCount] plus the total number of items that have
/// been inserted with [AnimatedListState.insertItem] or
/// [AnimatedGridState.insertItem] and less the total number of items that
/// have been removed with [AnimatedListState.removeItem] or
/// [AnimatedGridState.removeItem].
///
/// Implementations of this callback should assume that
/// [AnimatedGridState.removeItem] removes an item immediately.
/// `removeItem` removes an item immediately.
/// {@endtemplate}
final
AnimatedItemBuilder
itemBuilder
;
/// A delegate that controls the layout of the children within the
/// [AnimatedGrid].
///
/// See also:
///
/// * [SliverGridDelegateWithFixedCrossAxisCount], which creates a layout with
/// a fixed number of tiles in the cross axis.
/// * [SliverGridDelegateWithMaxCrossAxisExtent], which creates a layout with
/// tiles that have a maximum cross-axis extent.
final
SliverGridDelegate
gridDelegate
;
/// {@template flutter.widgets.AnimatedGrid.initialItemCount}
/// The number of items the grid will start with.
/// {@template flutter.widgets.AnimatedScrollView.initialItemCount}
/// The number of items the [AnimatedList] or [AnimatedGrid] will start with.
///
/// The appearance of the initial items is not animated. They
/// are created, as needed, by [itemBuilder] with an animation parameter
...
...
@@ -168,196 +436,320 @@ class AnimatedGrid extends StatefulWidget {
/// [ScrollController.animateTo]).
final
ScrollController
?
controller
;
/// Whether this is the primary scroll view associated with the parent
/// [PrimaryScrollController].
///
/// On iOS, this identifies the scroll view that will scroll to top in
/// response to a tap in the status bar.
///
/// Defaults to true when [scrollDirection] is [Axis.vertical] and
/// [controller] is null.
final
bool
?
primary
;
/// Whether this is the primary scroll view associated with the parent
/// [PrimaryScrollController].
///
/// On iOS, this identifies the scroll view that will scroll to top in
/// response to a tap in the status bar.
///
/// Defaults to true when [scrollDirection] is [Axis.vertical] and
/// [controller] is null.
final
bool
?
primary
;
/// How the scroll view should respond to user input.
///
/// For example, this determines how the scroll view continues to animate after the
/// user stops dragging the scroll view.
///
/// Defaults to matching platform conventions.
final
ScrollPhysics
?
physics
;
/// Whether the extent of the scroll view in the [scrollDirection] should be
/// determined by the contents being viewed.
///
/// If the scroll view does not shrink wrap, then the scroll view will expand
/// to the maximum allowed size in the [scrollDirection]. If the scroll view
/// has unbounded constraints in the [scrollDirection], then [shrinkWrap] must
/// be true.
///
/// Shrink wrapping the content of the scroll view is significantly more
/// expensive than expanding to the maximum allowed size because the content
/// can expand and contract during scrolling, which means the size of the
/// scroll view needs to be recomputed whenever the scroll position changes.
///
/// Defaults to false.
final
bool
shrinkWrap
;
/// The amount of space by which to inset the children.
final
EdgeInsetsGeometry
?
padding
;
/// {@macro flutter.material.Material.clipBehavior}
///
/// Defaults to [Clip.hardEdge].
final
Clip
clipBehavior
;
}
abstract
class
_AnimatedScrollViewState
<
T
extends
_AnimatedScrollView
>
extends
State
<
T
>
with
TickerProviderStateMixin
{
final
GlobalKey
<
_SliverAnimatedMultiBoxAdaptorState
<
_SliverAnimatedMultiBoxAdaptor
>>
_sliverAnimatedMultiBoxKey
=
GlobalKey
();
/// Insert an item at [index] and start an animation that will be passed
/// to [AnimatedGrid.itemBuilder] or [AnimatedList.itemBuilder] when the item
/// is visible.
///
/// This method's semantics are the same as Dart's [List.insert] method: it
/// increases the length of the list of items by one and shifts
/// all items at or after [index] towards the end of the list of items.
void
insertItem
(
int
index
,
{
Duration
duration
=
_kDuration
})
{
_sliverAnimatedMultiBoxKey
.
currentState
!.
insertItem
(
index
,
duration:
duration
);
}
/// Remove the item at `index` and start an animation that will be passed to
/// `builder` when the item is visible.
///
/// Items are removed immediately. After an item has been removed, its index
/// will no longer be passed to the `itemBuilder`. However, the
/// item will still appear for `duration` and during that time
/// `builder` must construct its widget as needed.
///
/// This method's semantics are the same as Dart's [List.remove] method: it
/// decreases the length of items by one and shifts all items at or before
/// `index` towards the beginning of the list of items.
///
/// See also:
///
/// * [AnimatedRemovedItemBuilder], which describes the arguments to the
/// `builder` argument.
void
removeItem
(
int
index
,
AnimatedRemovedItemBuilder
builder
,
{
Duration
duration
=
_kDuration
})
{
_sliverAnimatedMultiBoxKey
.
currentState
!.
removeItem
(
index
,
builder
,
duration:
duration
);
}
Widget
_wrap
(
Widget
sliver
)
{
return
CustomScrollView
(
scrollDirection:
widget
.
scrollDirection
,
reverse:
widget
.
reverse
,
controller:
widget
.
controller
,
primary:
widget
.
primary
,
physics:
widget
.
physics
,
clipBehavior:
widget
.
clipBehavior
,
slivers:
<
Widget
>[
SliverPadding
(
padding:
widget
.
padding
??
EdgeInsets
.
zero
,
sliver:
sliver
,
),
],
);
}
}
/// Signature for the builder callback used by [AnimatedList].
///
/// This is deprecated, use the identical [AnimatedItemBuilder] instead.
@Deprecated
(
'Use AnimatedItemBuilder instead. '
'This feature was deprecated after v3.5.0-4.0.pre.'
,
)
typedef
AnimatedListItemBuilder
=
Widget
Function
(
BuildContext
context
,
int
index
,
Animation
<
double
>
animation
);
/// Signature for the builder callback used by [AnimatedList] & [AnimatedGrid] to
/// build their animated children.
///
/// The `context` argument is the build context where the widget will be
/// created, the `index` is the index of the item to be built, and the
/// `animation` is an [Animation] that should be used to animate an entry
/// transition for the widget that is built.
///
/// See also:
///
/// * [AnimatedRemovedItemBuilder], a builder that is for removing items with
/// animations instead of adding them.
typedef
AnimatedItemBuilder
=
Widget
Function
(
BuildContext
context
,
int
index
,
Animation
<
double
>
animation
);
/// Signature for the builder callback used by [AnimatedListState.removeItem].
///
/// This is deprecated, use the identical [AnimatedRemovedItemBuilder]
/// instead.
@Deprecated
(
'Use AnimatedRemovedItemBuilder instead. '
'This feature was deprecated after v3.5.0-4.0.pre.'
,
)
typedef
AnimatedListRemovedItemBuilder
=
Widget
Function
(
BuildContext
context
,
Animation
<
double
>
animation
);
/// Signature for the builder callback used in [AnimatedListState.removeItem] and
/// [AnimatedGridState.removeItem] to animate their children after they have
/// been removed.
///
/// The `context` argument is the build context where the widget will be
/// created, and the `animation` is an [Animation] that should be used to
/// animate an exit transition for the widget that is built.
///
/// See also:
///
/// * [AnimatedItemBuilder], a builder that is for adding items with animations
/// instead of removing them.
typedef
AnimatedRemovedItemBuilder
=
Widget
Function
(
BuildContext
context
,
Animation
<
double
>
animation
);
// The default insert/remove animation duration.
const
Duration
_kDuration
=
Duration
(
milliseconds:
300
);
/// How the scroll view should respond to user input
.
///
/// For example, determines how the scroll view continues to animate after the
/// user stops dragging the scroll view.
///
/// Defaults to matching platform conventions.
final
ScrollPhysics
?
physics
;
// Incoming and outgoing animated items
.
class
_ActiveItem
implements
Comparable
<
_ActiveItem
>
{
_ActiveItem
.
incoming
(
this
.
controller
,
this
.
itemIndex
)
:
removedItemBuilder
=
null
;
_ActiveItem
.
outgoing
(
this
.
controller
,
this
.
itemIndex
,
this
.
removedItemBuilder
);
_ActiveItem
.
index
(
this
.
itemIndex
)
:
controller
=
null
,
removedItemBuilder
=
null
;
/// The amount of space by which to inset the children.
final
EdgeInsetsGeometry
?
padding
;
final
AnimationController
?
controller
;
final
AnimatedRemovedItemBuilder
?
removedItemBuilder
;
int
itemIndex
;
/// {@macro flutter.material.Material.clipBehavior}
///
/// Defaults to [Clip.hardEdge].
final
Clip
clipBehavior
;
@override
int
compareTo
(
_ActiveItem
other
)
=>
itemIndex
-
other
.
itemIndex
;
}
/// The state from the closest instance of this class that encloses the given
/// A [SliverList] that animates items when they are inserted or removed.
///
/// This widget's [SliverAnimatedListState] can be used to dynamically insert or
/// remove items. To refer to the [SliverAnimatedListState] either provide a
/// [GlobalKey] or use the static [SliverAnimatedList.of] method from a list item's
/// input callback.
///
/// {@tool dartpad}
/// This sample application uses a [SliverAnimatedList] to create an animated
/// effect when items are removed or added to the list.
///
/// ** See code in examples/api/lib/widgets/animated_list/sliver_animated_list.0.dart **
/// {@end-tool}
///
/// See also:
///
/// * [SliverList], which does not animate items when they are inserted or
/// removed.
/// * [AnimatedList], a non-sliver scrolling container that animates items when
/// they are inserted or removed.
/// * [SliverAnimatedGrid], a sliver which animates items when they are
/// inserted into or removed from a grid.
/// * [AnimatedGrid], a non-sliver scrolling container that animates items when
/// they are inserted into or removed from a grid.
class
SliverAnimatedList
extends
_SliverAnimatedMultiBoxAdaptor
{
/// Creates a [SliverList] that animates items when they are inserted or
/// removed.
const
SliverAnimatedList
({
super
.
key
,
required
super
.
itemBuilder
,
super
.
findChildIndexCallback
,
super
.
initialItemCount
=
0
,
})
:
assert
(
itemBuilder
!=
null
),
assert
(
initialItemCount
!=
null
&&
initialItemCount
>=
0
);
@override
SliverAnimatedListState
createState
()
=>
SliverAnimatedListState
();
/// The [SliverAnimatedListState] from the closest instance of this class that encloses the given
/// context.
///
/// This method is typically used by [
AnimatedGrid] item widgets that inser
t
/// or remove items in response to user input.
/// This method is typically used by [
SliverAnimatedList] item widgets tha
t
///
insert
or remove items in response to user input.
///
/// If no [
AnimatedGrid] surrounds the context given, then this function will
/// assert in debug mode and throw an exception in release mode.
/// If no [
SliverAnimatedList] surrounds the context given, then this function
///
will
assert in debug mode and throw an exception in release mode.
///
/// This method can be expensive (it walks the element tree).
///
/// This method does not create a dependency, and so will not cause rebuilding
/// when the state changes.
///
/// See also:
///
/// * [maybeOf], a similar function that will return null if no
/// [
AnimatedGrid
] ancestor is found.
static
AnimatedGrid
State
of
(
BuildContext
context
)
{
/// [
SliverAnimatedList
] ancestor is found.
static
SliverAnimatedList
State
of
(
BuildContext
context
)
{
assert
(
context
!=
null
);
final
AnimatedGridState
?
result
=
context
.
findAncestorStateOfType
<
AnimatedGridState
>(
);
final
SliverAnimatedListState
?
result
=
SliverAnimatedList
.
maybeOf
(
context
);
assert
(()
{
if
(
result
==
null
)
{
throw
FlutterError
.
fromParts
(<
DiagnosticsNode
>[
ErrorSummary
(
'AnimatedGrid.of() called with a context that does not contain an AnimatedGrid.'
),
ErrorDescription
(
'No AnimatedGrid ancestor could be found starting from the context that was passed to AnimatedGrid.of().'
,
),
ErrorHint
(
'This can happen when the context provided is from the same StatefulWidget that '
'built the AnimatedGrid. Please see the AnimatedGrid documentation for examples '
'of how to refer to an AnimatedGridState object:
\n
'
' https://api.flutter.dev/flutter/widgets/AnimatedGridState-class.html'
,
),
context
.
describeElement
(
'The context used was'
),
]);
throw
FlutterError
(
'SliverAnimatedList.of() called with a context that does not contain a SliverAnimatedList.
\n
'
'No SliverAnimatedListState ancestor could be found starting from the '
'context that was passed to SliverAnimatedListState.of(). This can '
'happen when the context provided is from the same StatefulWidget that '
'built the AnimatedList. Please see the SliverAnimatedList documentation '
'for examples of how to refer to an AnimatedListState object: '
'https://api.flutter.dev/flutter/widgets/SliverAnimatedListState-class.html
\n
'
'The context used was:
\n
'
'
$context
'
,
);
}
return
true
;
}());
return
result
!;
}
/// The
state
from the closest instance of this class that encloses the given
/// The
[SliverAnimatedListState]
from the closest instance of this class that encloses the given
/// context.
///
/// This method is typically used by [
AnimatedGrid] item widgets that inser
t
/// or remove items in response to user input.
/// This method is typically used by [
SliverAnimatedList] item widgets tha
t
///
insert
or remove items in response to user input.
///
/// If no [
AnimatedGrid] surrounds the context given, then this function will
/// return null.
/// If no [
SliverAnimatedList] surrounds the context given, then this function
///
will
return null.
///
/// This method can be expensive (it walks the element tree).
///
/// This method does not create a dependency, and so will not cause rebuilding
/// when the state changes.
///
/// See also:
///
/// * [of], a similar function that will throw if no [
AnimatedGrid] ancestor
/// is found.
static
AnimatedGrid
State
?
maybeOf
(
BuildContext
context
)
{
/// * [of], a similar function that will throw if no [
SliverAnimatedList]
///
ancestor
is found.
static
SliverAnimatedList
State
?
maybeOf
(
BuildContext
context
)
{
assert
(
context
!=
null
);
return
context
.
findAncestorStateOfType
<
AnimatedGrid
State
>();
return
context
.
findAncestorStateOfType
<
SliverAnimatedList
State
>();
}
@override
AnimatedGridState
createState
()
=>
AnimatedGridState
();
}
/// The state for a
scrolling container
that animates items when they are
/// The state for a
[SliverAnimatedList]
that animates items when they are
/// inserted or removed.
///
/// When an item is inserted with [insertItem] an animation begins running. The
/// animation is passed to [
AnimatedGrid.itemBuilder] whenever the item's widget
/// is needed.
/// animation is passed to [
SliverAnimatedList.itemBuilder] whenever the item's
///
widget
is needed.
///
/// When an item is removed with [removeItem] its animation is reversed.
/// The removed item's animation is passed to the [removeItem] builder
/// parameter.
///
/// An app that needs to insert or remove items in response to an event
/// can refer to the [
AnimatedGrid
]'s state with a global key:
/// can refer to the [
SliverAnimatedList
]'s state with a global key:
///
/// ```dart
/// // (e.g. in a stateful widget)
/// GlobalKey<Animated
GridState> gridKey = GlobalKey<AnimatedGrid
State>();
/// GlobalKey<Animated
ListState> listKey = GlobalKey<AnimatedList
State>();
///
/// // ...
///
/// @override
/// Widget build(BuildContext context) {
/// return Animated
Grid
(
/// key:
grid
Key,
/// return Animated
List
(
/// key:
list
Key,
/// itemBuilder: (BuildContext context, int index, Animation<double> animation) {
/// return const Placeholder();
/// },
/// gridDelegate: const SliverGridDelegateWithMaxCrossAxisExtent(maxCrossAxisExtent: 100.0),
/// );
/// }
///
/// // ...
///
/// void _update
Grid
() {
/// // adds "123" to the Animated
Grid
///
grid
Key.currentState!.insertItem(123);
/// void _update
List
() {
/// // adds "123" to the Animated
List
///
list
Key.currentState!.insertItem(123);
/// }
/// ```
///
/// [AnimatedGrid] item input handlers can also refer to their [AnimatedGridState]
/// with the static [AnimatedGrid.of] method.
class
AnimatedGridState
extends
State
<
AnimatedGrid
>
with
TickerProviderStateMixin
<
AnimatedGrid
>
{
final
GlobalKey
<
SliverAnimatedGridState
>
_sliverAnimatedGridKey
=
GlobalKey
();
/// Insert an item at [index] and start an animation that will be passed
/// to [AnimatedGrid.itemBuilder] when the item is visible.
///
/// This method's semantics are the same as Dart's [List.insert] method: it
/// increases the length of the list of items in the grid by one and shifts
/// all items at or after [index] towards the end of the list of items in the
/// grid.
void
insertItem
(
int
index
,
{
Duration
duration
=
_kDuration
})
{
_sliverAnimatedGridKey
.
currentState
!.
insertItem
(
index
,
duration:
duration
);
}
/// Remove the item at `index` and start an animation that will be passed to
/// `builder` when the item is visible.
///
/// Items are removed immediately. After an item has been removed, its index
/// will no longer be passed to the [AnimatedGrid.itemBuilder]. However, the
/// item will still appear in the grid for `duration` and during that time
/// `builder` must construct its widget as needed.
///
/// This method's semantics are the same as Dart's [List.remove] method: it
/// decreases the length of the list of items in the grid by one and shifts
/// all items at or before `index` towards the beginning of the list of items
/// in the grid.
///
/// See also:
///
/// - [AnimatedRemovedItemBuilder], which describes the arguments to the
/// `builder` argument.
void
removeItem
(
int
index
,
AnimatedRemovedItemBuilder
builder
,
{
Duration
duration
=
_kDuration
})
{
_sliverAnimatedGridKey
.
currentState
!.
removeItem
(
index
,
builder
,
duration:
duration
);
}
/// [SliverAnimatedList] item input handlers can also refer to their
/// [SliverAnimatedListState] with the static [SliverAnimatedList.of] method.
class
SliverAnimatedListState
extends
_SliverAnimatedMultiBoxAdaptorState
<
SliverAnimatedList
>
{
@override
Widget
build
(
BuildContext
context
)
{
return
CustomScrollView
(
scrollDirection:
widget
.
scrollDirection
,
reverse:
widget
.
reverse
,
controller:
widget
.
controller
,
primary:
widget
.
primary
,
physics:
widget
.
physics
,
clipBehavior:
widget
.
clipBehavior
,
slivers:
<
Widget
>[
SliverPadding
(
padding:
widget
.
padding
??
EdgeInsets
.
zero
,
sliver:
SliverAnimatedGrid
(
key:
_sliverAnimatedGridKey
,
gridDelegate:
widget
.
gridDelegate
,
itemBuilder:
widget
.
itemBuilder
,
initialItemCount:
widget
.
initialItemCount
,
),
),
],
return
SliverList
(
delegate:
_createDelegate
(),
);
}
}
/// A
sliver that animates items when they are inserted or removed in a gri
d.
/// A
[SliverGrid] that animates items when they are inserted or remove
d.
///
/// This widget's [SliverAnimatedGridState] can be used to dynamically insert or
/// remove items. To refer to the [SliverAnimatedGridState] either provide a
...
...
@@ -380,51 +772,24 @@ class AnimatedGridState extends State<AnimatedGrid> with TickerProviderStateMixi
/// * [SliverList], which displays a non-animated list of items.
/// * [SliverAnimatedList], which animates items added and removed from a list
/// instead of a grid.
class
SliverAnimatedGrid
extends
StatefulWidget
{
/// Creates a sliver that animates items when they are inserted or removed.
class
SliverAnimatedGrid
extends
_SliverAnimatedMultiBoxAdaptor
{
/// Creates a [SliverGrid] that animates items when they are inserted or
/// removed.
const
SliverAnimatedGrid
({
super
.
key
,
required
this
.
itemBuilder
,
required
super
.
itemBuilder
,
required
this
.
gridDelegate
,
this
.
findChildIndexCallback
,
this
.
initialItemCount
=
0
,
super
.
findChildIndexCallback
,
super
.
initialItemCount
=
0
,
})
:
assert
(
itemBuilder
!=
null
),
assert
(
initialItemCount
!=
null
&&
initialItemCount
>=
0
);
/// Called, as needed, to build grid item widgets.
///
/// Grid items are only built when they're scrolled into view.
///
/// The [AnimatedItemBuilder] index parameter indicates the item's position in
/// the grid. The value of the index parameter will be between 0 and
/// [initialItemCount] plus the total number of items that have been inserted
/// with [SliverAnimatedGridState.insertItem] and less the total number of
/// items that have been removed with [SliverAnimatedGridState.removeItem].
///
/// Implementations of this callback should assume that
/// [SliverAnimatedGridState.removeItem] removes an item immediately.
final
AnimatedItemBuilder
itemBuilder
;
/// A delegate that controls the layout of the children within the
/// [SliverAnimatedGrid].
///
/// See also:
///
/// * [SliverGridDelegateWithFixedCrossAxisCount], which creates a layout with
/// a fixed number of tiles in the cross axis.
/// * [SliverGridDelegateWithMaxCrossAxisExtent], which creates a layout with
/// tiles that have a maximum cross-axis extent.
final
SliverGridDelegate
gridDelegate
;
/// {@macro flutter.widgets.SliverChildBuilderDelegate.findChildIndexCallback}
final
ChildIndexGetter
?
findChildIndexCallback
;
/// {@macro flutter.widgets.AnimatedGrid.initialItemCount}
final
int
initialItemCount
;
@override
SliverAnimatedGridState
createState
()
=>
SliverAnimatedGridState
();
/// {@macro flutter.widgets.AnimatedGrid.gridDelegate}
final
SliverGridDelegate
gridDelegate
;
/// The state from the closest instance of this class that encloses the given
/// context.
///
...
...
@@ -483,7 +848,7 @@ class SliverAnimatedGrid extends StatefulWidget {
}
}
/// The state for a
sliver
that animates items when they are
/// The state for a
[SliverAnimatedGrid]
that animates items when they are
/// inserted or removed.
///
/// When an item is inserted with [insertItem] an animation begins running. The
...
...
@@ -524,10 +889,38 @@ class SliverAnimatedGrid extends StatefulWidget {
///
/// [SliverAnimatedGrid] item input handlers can also refer to their
/// [SliverAnimatedGridState] with the static [SliverAnimatedGrid.of] method.
class
SliverAnimatedGridState
extends
State
<
SliverAnimatedGrid
>
with
TickerProviderStateMixin
{
final
List
<
_ActiveItem
>
_incomingItems
=
<
_ActiveItem
>[];
final
List
<
_ActiveItem
>
_outgoingItems
=
<
_ActiveItem
>[];
int
_itemsCount
=
0
;
class
SliverAnimatedGridState
extends
_SliverAnimatedMultiBoxAdaptorState
<
SliverAnimatedGrid
>
{
@override
Widget
build
(
BuildContext
context
)
{
return
SliverGrid
(
gridDelegate:
widget
.
gridDelegate
,
delegate:
_createDelegate
(),
);
}
}
abstract
class
_SliverAnimatedMultiBoxAdaptor
extends
StatefulWidget
{
/// Creates a sliver that animates items when they are inserted or removed.
const
_SliverAnimatedMultiBoxAdaptor
({
super
.
key
,
required
this
.
itemBuilder
,
this
.
findChildIndexCallback
,
this
.
initialItemCount
=
0
,
})
:
assert
(
itemBuilder
!=
null
),
assert
(
initialItemCount
!=
null
&&
initialItemCount
>=
0
);
/// {@macro flutter.widgets.AnimatedScrollView.itemBuilder}
final
AnimatedItemBuilder
itemBuilder
;
/// {@macro flutter.widgets.SliverChildBuilderDelegate.findChildIndexCallback}
final
ChildIndexGetter
?
findChildIndexCallback
;
/// {@macro flutter.widgets.AnimatedScrollView.initialItemCount}
final
int
initialItemCount
;
}
abstract
class
_SliverAnimatedMultiBoxAdaptorState
<
T
extends
_SliverAnimatedMultiBoxAdaptor
>
extends
State
<
T
>
with
TickerProviderStateMixin
{
@override
void
initState
()
{
...
...
@@ -543,6 +936,10 @@ class SliverAnimatedGridState extends State<SliverAnimatedGrid> with TickerProvi
super
.
dispose
();
}
final
List
<
_ActiveItem
>
_incomingItems
=
<
_ActiveItem
>[];
final
List
<
_ActiveItem
>
_outgoingItems
=
<
_ActiveItem
>[];
int
_itemsCount
=
0
;
_ActiveItem
?
_removeActiveItemAt
(
List
<
_ActiveItem
>
items
,
int
itemIndex
)
{
final
int
i
=
binarySearch
(
items
,
_ActiveItem
.
index
(
itemIndex
));
return
i
==
-
1
?
null
:
items
.
removeAt
(
i
);
...
...
@@ -554,10 +951,11 @@ class SliverAnimatedGridState extends State<SliverAnimatedGrid> with TickerProvi
}
// The insertItem() and removeItem() index parameters are defined as if the
// removeItem() operation removed the corresponding grid entry immediately.
// The entry is only actually removed from the grid when the remove animation
// finishes. The entry is added to _outgoingItems when removeItem is called
// and removed from _outgoingItems when the remove animation finishes.
// removeItem() operation removed the corresponding list/grid entry
// immediately. The entry is only actually removed from the
// ListView/GridView when the remove animation finishes. The entry is added
// to _outgoingItems when removeItem is called and removed from
// _outgoingItems when the remove animation finishes.
int
_indexToItemIndex
(
int
index
)
{
int
itemIndex
=
index
;
...
...
@@ -597,14 +995,32 @@ class SliverAnimatedGridState extends State<SliverAnimatedGrid> with TickerProvi
);
}
Widget
_itemBuilder
(
BuildContext
context
,
int
itemIndex
)
{
final
_ActiveItem
?
outgoingItem
=
_activeItemAt
(
_outgoingItems
,
itemIndex
);
if
(
outgoingItem
!=
null
)
{
return
outgoingItem
.
removedItemBuilder
!(
context
,
outgoingItem
.
controller
!.
view
,
);
}
final
_ActiveItem
?
incomingItem
=
_activeItemAt
(
_incomingItems
,
itemIndex
);
final
Animation
<
double
>
animation
=
incomingItem
?.
controller
?.
view
??
kAlwaysCompleteAnimation
;
return
widget
.
itemBuilder
(
context
,
_itemIndexToIndex
(
itemIndex
),
animation
,
);
}
/// Insert an item at [index] and start an animation that will be passed to
/// [SliverAnimatedGrid.itemBuilder] when the item is visible.
/// [SliverAnimatedGrid.itemBuilder] or [SliverAnimatedList.itemBuilder] when
/// the item is visible.
///
/// This method's semantics are the same as Dart's [List.insert] method: it
/// increases the length of the list of items in the grid by one and shifts
/// all items at or after [index] towards the end of the list of items in the
/// grid.
void
insertItem
(
int
index
,
{
Duration
duration
=
_kDuration
})
{
/// increases the length of the list of items by one and shifts
/// all items at or after [index] towards the end of the list of items.
void
insertItem
(
int
index
,
{
Duration
duration
=
_kDuration
})
{
assert
(
index
!=
null
&&
index
>=
0
);
assert
(
duration
!=
null
);
...
...
@@ -648,15 +1064,15 @@ class SliverAnimatedGridState extends State<SliverAnimatedGrid> with TickerProvi
/// to [builder] when the item is visible.
///
/// Items are removed immediately. After an item has been removed, its index
/// will no longer be passed to the [SliverAnimatedGrid.itemBuilder]. However
/// the item will still appear in the grid for [duration] and during that time
/// [builder] must construct its widget as needed.
/// will no longer be passed to the subclass' [SliverAnimatedGrid.itemBuilder]
/// or [SliverAnimatedList.itemBuilder]. However the item will still appear
/// for [duration], and during that time [builder] must construct its widget
/// as needed.
///
/// This method's semantics are the same as Dart's [List.remove] method: it
/// decreases the length of the list of items in the grid by one and shifts
/// all items at or before [index] towards the beginning of the list of items
/// in the grid.
void
removeItem
(
int
index
,
AnimatedRemovedItemBuilder
builder
,
{
Duration
duration
=
_kDuration
})
{
/// decreases the length of items by one and shifts
/// all items at or before [index] towards the beginning of the list of items.
void
removeItem
(
int
index
,
AnimatedRemovedItemBuilder
builder
,
{
Duration
duration
=
_kDuration
})
{
assert
(
index
!=
null
&&
index
>=
0
);
assert
(
builder
!=
null
);
assert
(
duration
!=
null
);
...
...
@@ -694,30 +1110,4 @@ class SliverAnimatedGridState extends State<SliverAnimatedGrid> with TickerProvi
setState
(()
=>
_itemsCount
-=
1
);
});
}
Widget
_itemBuilder
(
BuildContext
context
,
int
itemIndex
)
{
final
_ActiveItem
?
outgoingItem
=
_activeItemAt
(
_outgoingItems
,
itemIndex
);
if
(
outgoingItem
!=
null
)
{
return
outgoingItem
.
removedItemBuilder
!(
context
,
outgoingItem
.
controller
!.
view
,
);
}
final
_ActiveItem
?
incomingItem
=
_activeItemAt
(
_incomingItems
,
itemIndex
);
final
Animation
<
double
>
animation
=
incomingItem
?.
controller
?.
view
??
kAlwaysCompleteAnimation
;
return
widget
.
itemBuilder
(
context
,
_itemIndexToIndex
(
itemIndex
),
animation
,
);
}
@override
Widget
build
(
BuildContext
context
)
{
return
SliverGrid
(
gridDelegate:
widget
.
gridDelegate
,
delegate:
_createDelegate
(),
);
}
}
packages/flutter/lib/widgets.dart
View file @
0e70a97e
...
...
@@ -19,8 +19,7 @@ export 'foundation.dart' show UniqueKey;
export
'rendering.dart'
show
TextSelectionHandleType
;
export
'src/widgets/actions.dart'
;
export
'src/widgets/animated_cross_fade.dart'
;
export
'src/widgets/animated_grid.dart'
;
export
'src/widgets/animated_list.dart'
;
export
'src/widgets/animated_scroll_view.dart'
;
export
'src/widgets/animated_size.dart'
;
export
'src/widgets/animated_switcher.dart'
;
export
'src/widgets/annotated_region.dart'
;
...
...
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