chore: config insert animation

This commit is contained in:
appflowy 2022-08-06 12:10:10 +08:00
parent 151e735d27
commit c035c9cf93
4 changed files with 142 additions and 102 deletions

View File

@ -1,25 +1,35 @@
import 'package:flutter/material.dart';
import '../transitions.dart';
abstract class DragTargetData {
int get draggingIndex;
}
abstract class ReorderFlexDraggableTargetBuilder {
Widget? build<T extends DragTargetData>(BuildContext context, Widget child, DragTargetOnStarted onDragStarted,
DragTargetOnEnded<T> onDragEnded, DragTargetWillAccpet<T> onWillAccept);
Widget? build<T extends DragTargetData>(
BuildContext context,
Widget child,
DragTargetOnStarted onDragStarted,
DragTargetOnEnded<T> onDragEnded,
DragTargetWillAccpet<T> onWillAccept,
AnimationController insertAnimationController,
AnimationController deleteAnimationController,
);
}
///
typedef DragTargetWillAccpet<T extends DragTargetData> = bool Function(T dragTargetData);
typedef DragTargetWillAccpet<T extends DragTargetData> = bool Function(
T dragTargetData);
///
typedef DragTargetOnStarted = void Function(Widget, int, Size?);
///
typedef DragTargetOnEnded<T extends DragTargetData> = void Function(T dragTargetData);
typedef DragTargetOnEnded<T extends DragTargetData> = void Function(
T dragTargetData);
/// [ReorderDragTarget] is a [DragTarget] that carries the index information of
/// the child.
/// the child. You could check out this link for more information.
///
/// The size of the [ReorderDragTarget] will become zero when it start dragging.
///
@ -52,6 +62,9 @@ class ReorderDragTarget<T extends DragTargetData> extends StatefulWidget {
final ReorderFlexDraggableTargetBuilder? draggableTargetBuilder;
final AnimationController insertAnimationController;
final AnimationController deleteAnimationController;
ReorderDragTarget({
Key? key,
required this.child,
@ -59,6 +72,8 @@ class ReorderDragTarget<T extends DragTargetData> extends StatefulWidget {
required this.onDragStarted,
required this.onDragEnded,
required this.onWillAccept,
required this.insertAnimationController,
required this.deleteAnimationController,
this.onAccept,
this.onLeave,
this.draggableTargetBuilder,
@ -69,7 +84,8 @@ class ReorderDragTarget<T extends DragTargetData> extends StatefulWidget {
State<ReorderDragTarget<T>> createState() => _ReorderDragTargetState<T>();
}
class _ReorderDragTargetState<T extends DragTargetData> extends State<ReorderDragTarget<T>> {
class _ReorderDragTargetState<T extends DragTargetData>
extends State<ReorderDragTarget<T>> {
/// Returns the dragTarget's size
Size? _draggingFeedbackSize = Size.zero;
@ -101,7 +117,8 @@ class _ReorderDragTargetState<T extends DragTargetData> extends State<ReorderDra
List<dynamic> rejectedCandidates,
) {
Widget feedbackBuilder = Builder(builder: (BuildContext context) {
BoxConstraints contentSizeConstraints = BoxConstraints.loose(_draggingFeedbackSize!);
BoxConstraints contentSizeConstraints =
BoxConstraints.loose(_draggingFeedbackSize!);
return _buildDraggableFeedback(
context,
contentSizeConstraints,
@ -115,6 +132,8 @@ class _ReorderDragTargetState<T extends DragTargetData> extends State<ReorderDra
widget.onDragStarted,
widget.onDragEnded,
widget.onWillAccept,
widget.insertAnimationController,
widget.deleteAnimationController,
) ??
LongPressDraggable<DragTargetData>(
maxSimultaneousDrags: 1,
@ -141,14 +160,16 @@ class _ReorderDragTargetState<T extends DragTargetData> extends State<ReorderDra
/// When the drag does not end inside a DragTarget widget, the
/// drag fails, but we still reorder the widget to the last position it
/// had been dragged to.
onDraggableCanceled: (Velocity velocity, Offset offset) => widget.onDragEnded(widget.dragTargetData),
onDraggableCanceled: (Velocity velocity, Offset offset) =>
widget.onDragEnded(widget.dragTargetData),
child: widget.child,
);
return draggableWidget;
}
Widget _buildDraggableFeedback(BuildContext context, BoxConstraints constraints, Widget child) {
Widget _buildDraggableFeedback(
BuildContext context, BoxConstraints constraints, Widget child) {
return Transform(
transform: Matrix4.rotationZ(0),
alignment: FractionalOffset.topLeft,
@ -163,7 +184,7 @@ class _ReorderDragTargetState<T extends DragTargetData> extends State<ReorderDra
}
}
class DragAnimationController {
class DragTargetAnimation {
// How long an animation to reorder an element in the list takes.
final Duration reorderAnimationDuration;
@ -174,17 +195,28 @@ class DragAnimationController {
// where the widget used to be.
late AnimationController phantomController;
DragAnimationController({
late AnimationController insertController;
late AnimationController deleteController;
DragTargetAnimation({
required this.reorderAnimationDuration,
required TickerProvider vsync,
required void Function(AnimationStatus) entranceAnimateStatusChanged,
}) {
entranceController = AnimationController(value: 1.0, vsync: vsync, duration: reorderAnimationDuration);
phantomController = AnimationController(value: 0, vsync: vsync, duration: reorderAnimationDuration);
entranceController = AnimationController(
value: 1.0, vsync: vsync, duration: reorderAnimationDuration);
entranceController.addStatusListener(entranceAnimateStatusChanged);
}
bool get isEntranceAnimationCompleted => entranceController.isCompleted;
phantomController = AnimationController(
value: 0, vsync: vsync, duration: reorderAnimationDuration);
insertController = AnimationController(
value: 0.0, vsync: vsync, duration: reorderAnimationDuration);
deleteController = AnimationController(
value: 0.0, vsync: vsync, duration: reorderAnimationDuration);
}
void startDargging() {
entranceController.value = 1.0;
@ -203,6 +235,8 @@ class DragAnimationController {
void dispose() {
entranceController.dispose();
phantomController.dispose();
insertController.dispose();
deleteController.dispose();
}
}
@ -217,7 +251,9 @@ class IgnorePointerWidget extends StatelessWidget {
@override
Widget build(BuildContext context) {
final sizedChild = useIntrinsicSize ? child : SizedBox(width: 0.0, height: 0.0, child: child);
final sizedChild = useIntrinsicSize
? child
: SizedBox(width: 0.0, height: 0.0, child: child);
return IgnorePointer(
ignoring: true,
child: Opacity(
@ -246,42 +282,7 @@ class PhantomWidget extends StatelessWidget {
}
}
class PhantomAnimateContorller {
// How long an animation to reorder an element in the list takes.
final Duration reorderAnimationDuration;
late AnimationController appearController;
late AnimationController disappearController;
PhantomAnimateContorller({
required TickerProvider vsync,
required this.reorderAnimationDuration,
required void Function(AnimationStatus) appearAnimateStatusChanged,
}) {
appearController = AnimationController(value: 1.0, vsync: vsync, duration: reorderAnimationDuration);
disappearController = AnimationController(value: 0, vsync: vsync, duration: reorderAnimationDuration);
appearController.addStatusListener(appearAnimateStatusChanged);
}
bool get isAppearAnimationCompleted => appearController.isCompleted;
void animateToNext() {
disappearController.reverse(from: 1.0);
appearController.forward(from: 0.0);
}
void performReorderAnimation() {
disappearController.reverse(from: 0.1);
appearController.reverse(from: 0.0);
}
void dispose() {
appearController.dispose();
disappearController.dispose();
}
}
abstract class FakeDragTargetEventTrigger {
void fakeOnDragStarted(VoidCallback callback);
void fakeOnDragEnded(VoidCallback callback);
}
@ -292,12 +293,15 @@ abstract class FakeDragTargetEventData {
}
class FakeDragTarget<T extends DragTargetData> extends StatefulWidget {
final Duration animationDuration;
final FakeDragTargetEventTrigger eventTrigger;
final FakeDragTargetEventData eventData;
final DragTargetOnStarted onDragStarted;
final DragTargetOnEnded<T> onDragEnded;
final DragTargetWillAccpet<T> onWillAccept;
final Widget child;
final AnimationController insertAnimationController;
final AnimationController deleteAnimationController;
const FakeDragTarget({
Key? key,
required this.eventTrigger,
@ -305,34 +309,43 @@ class FakeDragTarget<T extends DragTargetData> extends StatefulWidget {
required this.onDragStarted,
required this.onDragEnded,
required this.onWillAccept,
required this.insertAnimationController,
required this.deleteAnimationController,
required this.child,
this.animationDuration = const Duration(milliseconds: 250),
}) : super(key: key);
@override
State<FakeDragTarget<T>> createState() => _FakeDragTargetState<T>();
}
class _FakeDragTargetState<T extends DragTargetData> extends State<FakeDragTarget<T>> {
bool isDragging = false;
class _FakeDragTargetState<T extends DragTargetData>
extends State<FakeDragTarget<T>>
with TickerProviderStateMixin<FakeDragTarget<T>> {
bool simulateDragging = false;
@override
void initState() {
widget.eventTrigger.fakeOnDragStarted(() {
if (mounted) {
setState(() {
widget.onWillAccept(widget.eventData.dragTargetData as T);
widget.onDragStarted(
widget.child,
widget.eventData.index,
widget.eventData.feedbackSize,
);
isDragging = true;
widget.insertAnimationController.addStatusListener(
(status) {
if (status != AnimationStatus.completed) return;
if (!mounted) return;
WidgetsBinding.instance.addPostFrameCallback((_) {
setState(() {
simulateDragging = true;
widget.deleteAnimationController.reverse(from: 1.0);
widget.onWillAccept(widget.eventData.dragTargetData as T);
widget.onDragStarted(
widget.child,
widget.eventData.index,
widget.eventData.feedbackSize,
);
});
});
}
});
},
);
widget.insertAnimationController.forward(from: 0.0);
widget.eventTrigger.fakeOnDragEnded(() {
if (mounted) {
widget.onDragEnded(widget.eventData.dragTargetData as T);
@ -344,10 +357,17 @@ class _FakeDragTargetState<T extends DragTargetData> extends State<FakeDragTarge
@override
Widget build(BuildContext context) {
if (isDragging) {
return IgnorePointerWidget(child: widget.child);
} else {
return IgnorePointerWidget(useIntrinsicSize: true, child: widget.child);
}
final child = IgnorePointerWidget(
useIntrinsicSize: !simulateDragging, child: widget.child);
final animationController = simulateDragging
? widget.deleteAnimationController
: widget.insertAnimationController;
return SizeTransitionWithIntrinsicSize(
sizeFactor: animationController,
axis: Axis.vertical,
child: child,
);
}
}

View File

@ -127,12 +127,14 @@ class CrossReorderFlexDragTargetInterceptor extends DragTargetInterceptor {
@override
void onAccept(FlexDragTargetData dragTargetData) {
Log.trace('[$CrossReorderFlexDragTargetInterceptor] Column$reorderFlexId on onAccept');
Log.trace(
'[$CrossReorderFlexDragTargetInterceptor] Column$reorderFlexId on onAccept');
}
@override
void onLeave(FlexDragTargetData dragTargetData) {
Log.trace('[$CrossReorderFlexDragTargetInterceptor] Column$reorderFlexId on leave');
Log.trace(
'[$CrossReorderFlexDragTargetInterceptor] Column$reorderFlexId on leave');
}
@override

View File

@ -105,14 +105,14 @@ class ReorderFlexState extends State<ReorderFlex>
/// [dragState] records the dragging state including dragStartIndex, and phantomIndex, etc.
late DraggingState dragState;
/// [_dragAnimationController] controls the dragging animations
late DragAnimationController _dragAnimationController;
/// [_animation] controls the dragging animations
late DragTargetAnimation _animation;
@override
void initState() {
dragState = DraggingState(widget.reorderFlexId);
_dragAnimationController = DragAnimationController(
_animation = DragTargetAnimation(
reorderAnimationDuration: widget.config.reorderAnimationDuration,
entranceAnimateStatusChanged: (status) {
if (status == AnimationStatus.completed) {
@ -174,7 +174,7 @@ class ReorderFlexState extends State<ReorderFlex>
_attachedScrollPosition = null;
}
_dragAnimationController.dispose();
_animation.dispose();
super.dispose();
}
@ -183,7 +183,7 @@ class ReorderFlexState extends State<ReorderFlex>
/// dragging animation is completed. Otherwise, it will get called again
/// when the animation finishs.
if (_dragAnimationController.isEntranceAnimationCompleted) {
if (_animation.entranceController.isCompleted) {
dragState.removePhantom();
if (!isAcceptingNewTarget && dragState.didDragTargetMoveToNext()) {
@ -191,7 +191,7 @@ class ReorderFlexState extends State<ReorderFlex>
}
dragState.moveDragTargetToNext();
_dragAnimationController.animateToNext();
_animation.animateToNext();
}
}
@ -238,7 +238,7 @@ class ReorderFlexState extends State<ReorderFlex>
Widget appearSpace = _makeAppearSpace(dragSpace, feedbackSize);
Widget disappearSpace = _makeDisappearSpace(dragSpace, feedbackSize);
/// When start dragging, the dragTarget, [BoardDragTarget], will
/// When start dragging, the dragTarget, [ReorderDragTarget], will
/// return a [IgnorePointerWidget] which size is zero.
if (dragState.isPhantomAboveDragTarget()) {
//the phantom is moving down, i.e. the tile below the phantom is moving up
@ -337,6 +337,12 @@ class ReorderFlexState extends State<ReorderFlex>
});
},
onWillAccept: (FlexDragTargetData dragTargetData) {
Log.debug('Insert animation: ${_animation.deleteController.status}');
if (_animation.deleteController.isAnimating) {
return false;
}
assert(widget.dataSource.items.length > dragTargetIndex);
if (_interceptDragTarget(
@ -366,6 +372,8 @@ class ReorderFlexState extends State<ReorderFlex>
(interceptor) => interceptor.onLeave(dragTargetData),
);
},
insertAnimationController: _animation.insertController,
deleteAnimationController: _animation.deleteController,
draggableTargetBuilder: widget.interceptor?.draggableTargetBuilder,
child: child,
);
@ -387,7 +395,7 @@ class ReorderFlexState extends State<ReorderFlex>
Widget _makeAppearSpace(Widget child, Size? feedbackSize) {
return makeAppearingWidget(
child,
_dragAnimationController.entranceController,
_animation.entranceController,
feedbackSize,
widget.direction,
);
@ -396,7 +404,7 @@ class ReorderFlexState extends State<ReorderFlex>
Widget _makeDisappearSpace(Widget child, Size? feedbackSize) {
return makeDisappearingWidget(
child,
_dragAnimationController.phantomController,
_animation.phantomController,
feedbackSize,
widget.direction,
);
@ -409,7 +417,7 @@ class ReorderFlexState extends State<ReorderFlex>
) {
setState(() {
dragState.startDragging(draggingWidget, dragIndex, feedbackSize);
_dragAnimationController.startDargging();
_animation.startDargging();
});
}
@ -446,7 +454,7 @@ class ReorderFlexState extends State<ReorderFlex>
widget.onReorder.call(fromIndex, toIndex);
}
_dragAnimationController.reverseAnimation();
_animation.reverseAnimation();
}
Widget _wrapScrollView({required Widget child}) {

View File

@ -6,6 +6,7 @@ import '../flex/drag_target.dart';
import '../flex/drag_target_inteceptor.dart';
import 'phantom_state.dart';
@protected
abstract class BoardPhantomControllerDelegate {
BoardColumnDataController? controller(String columnId);
@ -34,7 +35,9 @@ abstract class BoardPhantomControllerDelegate {
);
}
class BoardPhantomController extends OverlapDragTargetDelegate with CrossReorderFlexDragTargetDelegate {
@protected
class BoardPhantomController extends OverlapDragTargetDelegate
with CrossReorderFlexDragTargetDelegate {
PhantomRecord? phantomRecord;
final BoardPhantomControllerDelegate delegate;
final columnsState = ColumnPhantomStateController();
@ -60,6 +63,7 @@ class BoardPhantomController extends OverlapDragTargetDelegate with CrossReorder
columnsState.setColumnIsDragging(columnId, false);
}
/// Remove the phanton in the column when the column is end dragging.
void columnEndDragging(String columnId) {
columnsState.setColumnIsDragging(columnId, true);
if (phantomRecord != null) {
@ -91,6 +95,8 @@ class BoardPhantomController extends OverlapDragTargetDelegate with CrossReorder
/// Remove the phantom in the column if it contains phantom
void _removePhantom(String columnId) {
// columnsState.notifyDidRemovePhantom(columnId);
// columnsState.removeColumnListener(columnId);
if (delegate.removePhantom(columnId)) {
columnsState.notifyDidRemovePhantom(columnId);
columnsState.removeColumnListener(columnId);
@ -114,12 +120,7 @@ class BoardPhantomController extends OverlapDragTargetDelegate with CrossReorder
PhantomColumnItem(phantomContext),
);
WidgetsBinding.instance.addPostFrameCallback((_) {
Future.delayed(const Duration(milliseconds: 100), () {
Log.debug('[$BoardPhantomController] notify $toColumnId to insert phantom');
columnsState.notifyDidInsertPhantom(toColumnId);
});
});
columnsState.notifyDidInsertPhantom(toColumnId);
}
/// Reset or initial the [PhantomRecord]
@ -160,6 +161,7 @@ class BoardPhantomController extends OverlapDragTargetDelegate with CrossReorder
final isNewDragTarget = phantomRecord!.toColumnId != reorderFlexId;
if (isNewDragTarget) {
/// Remove the phantom when the dragTarget is moved from one column to another column.
_removePhantom(phantomRecord!.toColumnId);
_resetPhantomRecord(reorderFlexId, dragTargetData, dragTargetIndex);
_insertPhantom(reorderFlexId, dragTargetData, dragTargetIndex);
@ -188,6 +190,7 @@ class BoardPhantomController extends OverlapDragTargetDelegate with CrossReorder
return;
}
/// Remove the phantom when the dragTarge is go back to the original column.
_removePhantom(phantomRecord!.toColumnId);
phantomRecord = null;
}
@ -214,6 +217,7 @@ class BoardPhantomController extends OverlapDragTargetDelegate with CrossReorder
/// [toColumnId] the column that the phantom moves into
/// [toColumnIndex] the index of the phantom moves into the column
///
@protected
class PhantomRecord {
final String fromColumnId;
int fromColumnIndex;
@ -232,7 +236,8 @@ class PhantomRecord {
if (fromColumnIndex == index) {
return;
}
Log.debug('[$PhantomRecord] Update Column$fromColumnId remove position to $index');
Log.debug(
'[$PhantomRecord] Update Column$fromColumnId remove position to $index');
fromColumnIndex = index;
}
@ -241,7 +246,8 @@ class PhantomRecord {
return;
}
Log.debug('[$PhantomRecord] Column$toColumnId update position $toColumnIndex -> $index');
Log.debug(
'[$PhantomRecord] Column$toColumnId update position $toColumnIndex -> $index');
toColumnIndex = index;
}
@ -254,7 +260,8 @@ class PhantomRecord {
class PhantomColumnItem extends ColumnItem {
final PassthroughPhantomContext phantomContext;
PhantomColumnItem(PassthroughPhantomContext insertedPhantom) : phantomContext = insertedPhantom;
PhantomColumnItem(PassthroughPhantomContext insertedPhantom)
: phantomContext = insertedPhantom;
@override
bool get isPhantom => true;
@ -264,8 +271,9 @@ class PhantomColumnItem extends ColumnItem {
Size? get feedbackSize => phantomContext.feedbackSize;
Widget get draggingWidget =>
phantomContext.draggingWidget == null ? const SizedBox() : phantomContext.draggingWidget!;
Widget get draggingWidget => phantomContext.draggingWidget == null
? const SizedBox()
: phantomContext.draggingWidget!;
@override
String toString() {
@ -303,13 +311,9 @@ class PassthroughPhantomContext extends FakeDragTargetEventTrigger
void fakeOnDragEnded(VoidCallback callback) {
onDragEnded = callback;
}
@override
void fakeOnDragStarted(VoidCallback callback) {
onInserted = callback;
}
}
@protected
class PassthroughPhantomWidget extends PhantomWidget {
final PassthroughPhantomContext passthroughPhantomContext;
@ -324,7 +328,9 @@ class PassthroughPhantomWidget extends PhantomWidget {
);
}
@protected
class PhantomDraggableBuilder extends ReorderFlexDraggableTargetBuilder {
PhantomDraggableBuilder();
@override
Widget? build<T extends DragTargetData>(
BuildContext context,
@ -332,6 +338,8 @@ class PhantomDraggableBuilder extends ReorderFlexDraggableTargetBuilder {
DragTargetOnStarted onDragStarted,
DragTargetOnEnded<T> onDragEnded,
DragTargetWillAccpet<T> onWillAccept,
AnimationController insertAnimationController,
AnimationController deleteAnimationController,
) {
if (child is PassthroughPhantomWidget) {
return FakeDragTarget<T>(
@ -340,6 +348,8 @@ class PhantomDraggableBuilder extends ReorderFlexDraggableTargetBuilder {
onDragStarted: onDragStarted,
onDragEnded: onDragEnded,
onWillAccept: onWillAccept,
insertAnimationController: insertAnimationController,
deleteAnimationController: deleteAnimationController,
child: child,
);
} else {