mirror of
https://github.com/HackPlan/AsyncDisplayKit.git
synced 2026-05-19 19:10:05 +08:00
Revert "[Layout Transition API] Simplify applying layout transition (#1886)"
This reverts commit 678df37017.
This commit is contained in:
@@ -642,24 +642,31 @@ static ASDisplayNodeMethodOverrides GetASDisplayNodeMethodOverrides(Class c)
|
||||
{
|
||||
ASDN::MutexLocker l(__instanceLock__);
|
||||
if (! [self shouldMeasureWithSizeRange:constrainedSize]) {
|
||||
ASDisplayNodeAssertNotNil(_calculatedLayout, @"-[ASDisplayNode measureWithSizeRange:] _layout should not be nil! %@", self);
|
||||
return _calculatedLayout ? : [ASLayout layoutWithLayoutableObject:self constrainedSizeRange:constrainedSize size:CGSizeZero];
|
||||
ASDisplayNodeAssertNotNil(_layout, @"-[ASDisplayNode measureWithSizeRange:] _layout should not be nil! %@", self);
|
||||
return _layout ? : [ASLayout layoutWithLayoutableObject:self constrainedSizeRange:constrainedSize size:CGSizeZero];
|
||||
}
|
||||
|
||||
[self cancelLayoutTransitionsInProgress];
|
||||
|
||||
ASLayout *previousLayout = _calculatedLayout;
|
||||
ASLayout *previousLayout = _layout;
|
||||
ASLayout *newLayout = [self calculateLayoutThatFits:constrainedSize];
|
||||
|
||||
_pendingLayoutTransition = [[ASLayoutTransition alloc] initWithNode:self
|
||||
pendingLayout:newLayout
|
||||
previousLayout:previousLayout];
|
||||
|
||||
if (ASHierarchyStateIncludesLayoutPending(_hierarchyState) == NO) {
|
||||
// Complete the pending layout transition immediately
|
||||
[self _completePendingLayoutTransition];
|
||||
if (ASHierarchyStateIncludesLayoutPending(_hierarchyState)) {
|
||||
_pendingLayoutTransition = [[ASLayoutTransition alloc] initWithNode:self
|
||||
pendingLayout:newLayout
|
||||
previousLayout:previousLayout];
|
||||
} else {
|
||||
ASLayoutTransition *layoutTransition = nil;
|
||||
if (self.usesImplicitHierarchyManagement) {
|
||||
layoutTransition = [[ASLayoutTransition alloc] initWithNode:self
|
||||
pendingLayout:newLayout
|
||||
previousLayout:previousLayout];
|
||||
}
|
||||
|
||||
[self _applyLayout:newLayout layoutTransition:layoutTransition];
|
||||
[self _completeLayoutCalculation];
|
||||
}
|
||||
|
||||
|
||||
ASDisplayNodeAssertNotNil(newLayout, @"-[ASDisplayNode measureWithSizeRange:] newLayout should not be nil! %@", self);
|
||||
return newLayout;
|
||||
}
|
||||
@@ -681,12 +688,12 @@ static ASDisplayNodeMethodOverrides GetASDisplayNodeMethodOverrides(Class c)
|
||||
// Only generate a new layout if:
|
||||
// - The current layout is dirty
|
||||
// - The passed constrained size is different than the layout's constrained size
|
||||
return ([self _hasDirtyLayout] || !ASSizeRangeEqualToSizeRange(constrainedSize, _calculatedLayout.constrainedSizeRange));
|
||||
return ([self _hasDirtyLayout] || !ASSizeRangeEqualToSizeRange(constrainedSize, _layout.constrainedSizeRange));
|
||||
}
|
||||
|
||||
- (BOOL)_hasDirtyLayout
|
||||
{
|
||||
return _calculatedLayout == nil || _calculatedLayout.isDirty;
|
||||
return _layout == nil || _layout.isDirty;
|
||||
}
|
||||
|
||||
- (ASLayoutableType)layoutableType
|
||||
@@ -705,14 +712,13 @@ static ASDisplayNodeMethodOverrides GetASDisplayNodeMethodOverrides(Class c)
|
||||
shouldMeasureAsync:(BOOL)shouldMeasureAsync
|
||||
measurementCompletion:(void(^)())completion
|
||||
{
|
||||
if (_calculatedLayout == nil) {
|
||||
if (_layout == nil) {
|
||||
// constrainedSizeRange returns a struct and is invalid to call on nil.
|
||||
// Defaulting to CGSizeZero can cause negative values in client layout code.
|
||||
return;
|
||||
}
|
||||
|
||||
[self invalidateCalculatedLayout];
|
||||
[self transitionLayoutWithSizeRange:_calculatedLayout.constrainedSizeRange
|
||||
[self transitionLayoutWithSizeRange:_layout.constrainedSizeRange
|
||||
animated:animated
|
||||
shouldMeasureAsync:shouldMeasureAsync
|
||||
measurementCompletion:completion];
|
||||
@@ -774,11 +780,12 @@ static ASDisplayNodeMethodOverrides GetASDisplayNodeMethodOverrides(Class c)
|
||||
return;
|
||||
}
|
||||
|
||||
ASLayout *previousLayout = _calculatedLayout;
|
||||
[self setCalculatedLayout:newLayout];
|
||||
ASLayout *previousLayout = _layout;
|
||||
[self _applyLayout:newLayout layoutTransition:nil];
|
||||
|
||||
ASDisplayNodePerformBlockOnEverySubnode(self, ^(ASDisplayNode * _Nonnull node) {
|
||||
[node _completePendingLayoutTransition];
|
||||
[node _applyPendingLayoutContext];
|
||||
[node _completeLayoutCalculation];
|
||||
node.hierarchyState &= (~ASHierarchyStateLayoutPending);
|
||||
});
|
||||
|
||||
@@ -788,26 +795,48 @@ static ASDisplayNodeMethodOverrides GetASDisplayNodeMethodOverrides(Class c)
|
||||
completion();
|
||||
}
|
||||
|
||||
// Setup pending layout transition for animation
|
||||
_pendingLayoutTransition = [[ASLayoutTransition alloc] initWithNode:self
|
||||
pendingLayout:newLayout
|
||||
previousLayout:previousLayout];
|
||||
// Setup context for pending layout transition. we need to hold a strong reference to the context
|
||||
_pendingLayoutTransitionContext = [[_ASTransitionContext alloc] initWithAnimation:animated
|
||||
layoutDelegate:_pendingLayoutTransition
|
||||
completionDelegate:self];
|
||||
|
||||
// Apply the subnode insertion immediately to be able to animate the nodes
|
||||
previousLayout:previousLayout];
|
||||
[_pendingLayoutTransition applySubnodeInsertions];
|
||||
|
||||
// Kick off animating the layout transition
|
||||
[self animateLayoutTransition:_pendingLayoutTransitionContext];
|
||||
|
||||
_transitionContext = [[_ASTransitionContext alloc] initWithAnimation:animated
|
||||
layoutDelegate:_pendingLayoutTransition
|
||||
completionDelegate:self];
|
||||
[self animateLayoutTransition:_transitionContext];
|
||||
});
|
||||
};
|
||||
|
||||
ASPerformBlockOnBackgroundThread(transitionBlock);
|
||||
}
|
||||
|
||||
- (void)_completeLayoutCalculation
|
||||
{
|
||||
ASDN::MutexLocker l(__instanceLock__);
|
||||
[self calculatedLayoutDidChange];
|
||||
|
||||
// We generate placeholders at measureWithSizeRange: time so that a node is guaranteed to have a placeholder ready to go.
|
||||
// This is also because measurement is usually asynchronous, but placeholders need to be set up synchronously.
|
||||
// First measurement is guaranteed to be before the node is onscreen, so we can create the image async. but still have it appear sync.
|
||||
if (_placeholderEnabled && [self _displaysAsynchronously] && self.contents == nil) {
|
||||
|
||||
// Zero-sized nodes do not require a placeholder.
|
||||
CGSize layoutSize = (_layout ? _layout.size : CGSizeZero);
|
||||
if (CGSizeEqualToSize(layoutSize, CGSizeZero)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!_placeholderImage) {
|
||||
_placeholderImage = [self placeholderImage];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
- (void)calculatedLayoutDidChange
|
||||
{
|
||||
// subclass override
|
||||
}
|
||||
|
||||
- (void)cancelLayoutTransitionsInProgress
|
||||
{
|
||||
ASDN::MutexLocker l(__instanceLock__);
|
||||
@@ -861,108 +890,25 @@ static ASDisplayNodeMethodOverrides GetASDisplayNodeMethodOverrides(Class c)
|
||||
return (!_transitionInProgress || _transitionID != transitionID);
|
||||
}
|
||||
|
||||
#pragma mark - Layout Transition API / ASDisplayNode (Beta)
|
||||
|
||||
/*
|
||||
* Hook for subclasse to perform an animation based on the given ASContextTransitioning. By default this just layouts
|
||||
* applies all subnodes without animation and calls completes the transition on the context.
|
||||
*/
|
||||
- (void)animateLayoutTransition:(id<ASContextTransitioning>)context
|
||||
{
|
||||
[self __layoutSublayouts];
|
||||
[self __layoutSubnodes];
|
||||
[context completeTransition:YES];
|
||||
}
|
||||
|
||||
/*
|
||||
* Hook for subclasses to clean up nodes after the transition happened. Furthermore this can be used from subclasses
|
||||
* to manually perform deletions.
|
||||
*/
|
||||
- (void)didCompleteLayoutTransition:(id<ASContextTransitioning>)context
|
||||
{
|
||||
[_pendingLayoutTransition applySubnodeRemovals];
|
||||
[self _completeLayoutCalculation];
|
||||
_pendingLayoutTransition = nil;
|
||||
}
|
||||
|
||||
#pragma mark - _ASTransitionContextCompletionDelegate
|
||||
|
||||
/*
|
||||
* After completeTransition: is called on the ASContextTransitioning object in animateLayoutTransition: this
|
||||
* delegate method will be called that start the completion process of the
|
||||
*/
|
||||
- (void)transitionContext:(_ASTransitionContext *)context didComplete:(BOOL)didComplete
|
||||
{
|
||||
[self didCompleteLayoutTransition:context];
|
||||
_pendingLayoutTransitionContext = nil;
|
||||
|
||||
[self _pendingLayoutTransitionDidComplete];
|
||||
}
|
||||
|
||||
#pragma mark - Layout
|
||||
|
||||
/*
|
||||
* Completes the pending layout transition immediately without going through the the Layout Transition Animation API
|
||||
*/
|
||||
- (void)_completePendingLayoutTransition
|
||||
{
|
||||
ASDN::MutexLocker l(__instanceLock__);
|
||||
if (_pendingLayoutTransition) {
|
||||
[self setCalculatedLayout:_pendingLayoutTransition.pendingLayout];
|
||||
[self _completeLayoutTransition:_pendingLayoutTransition];
|
||||
}
|
||||
[self _pendingLayoutTransitionDidComplete];
|
||||
}
|
||||
|
||||
/*
|
||||
* Can be directly called to commit the given layout transition immediately to complete without calling through to the
|
||||
* Layout Transition Animation API
|
||||
*/
|
||||
- (void)_completeLayoutTransition:(ASLayoutTransition *)layoutTransition
|
||||
{
|
||||
// Layout transition is not supported for non implicit hierarchy managed nodes yet
|
||||
if (layoutTransition == nil || self.usesImplicitHierarchyManagement == NO) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Trampoline to the main thread if necessary
|
||||
if (ASDisplayNodeThreadIsMain() || layoutTransition.isSynchronous == NO) {
|
||||
[layoutTransition commitTransition];
|
||||
} else {
|
||||
// Subnode insertions and removals need to happen always on the main thread if at least one subnode is already loaded
|
||||
ASPerformBlockOnMainThread(^{
|
||||
[layoutTransition commitTransition];
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
- (void)_pendingLayoutTransitionDidComplete
|
||||
{
|
||||
ASDN::MutexLocker l(__instanceLock__);
|
||||
|
||||
// Subclass hook
|
||||
[self calculatedLayoutDidChange];
|
||||
|
||||
// We generate placeholders at measureWithSizeRange: time so that a node is guaranteed to have a placeholder ready to go.
|
||||
// This is also because measurement is usually asynchronous, but placeholders need to be set up synchronously.
|
||||
// First measurement is guaranteed to be before the node is onscreen, so we can create the image async. but still have it appear sync.
|
||||
if (_placeholderEnabled && [self _displaysAsynchronously] && self.contents == nil) {
|
||||
|
||||
// Zero-sized nodes do not require a placeholder.
|
||||
CGSize layoutSize = (_calculatedLayout ? _calculatedLayout.size : CGSizeZero);
|
||||
if (CGSizeEqualToSize(layoutSize, CGSizeZero)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!_placeholderImage) {
|
||||
_placeholderImage = [self placeholderImage];
|
||||
}
|
||||
}
|
||||
|
||||
// Cleanup pending layout transition
|
||||
_pendingLayoutTransition = nil;
|
||||
}
|
||||
|
||||
- (void)calculatedLayoutDidChange
|
||||
{
|
||||
// subclass override
|
||||
_transitionContext = nil;
|
||||
}
|
||||
|
||||
#pragma mark - Asynchronous display
|
||||
@@ -1124,7 +1070,7 @@ static ASDisplayNodeMethodOverrides GetASDisplayNodeMethodOverrides(Class c)
|
||||
|
||||
__instanceLock__.lock();
|
||||
|
||||
if (_calculatedLayout == nil) {
|
||||
if (_layout == nil) {
|
||||
// Can't proceed without a layout as no constrained size would be available
|
||||
__instanceLock__.unlock();
|
||||
return;
|
||||
@@ -1142,11 +1088,11 @@ static ASDisplayNodeMethodOverrides GetASDisplayNodeMethodOverrides(Class c)
|
||||
}
|
||||
|
||||
// This is the root node. Trigger a full measurement pass on *current* thread. Old constrained size is re-used.
|
||||
[self measureWithSizeRange:_calculatedLayout.constrainedSizeRange];
|
||||
[self measureWithSizeRange:_layout.constrainedSizeRange];
|
||||
|
||||
CGRect oldBounds = self.bounds;
|
||||
CGSize oldSize = oldBounds.size;
|
||||
CGSize newSize = _calculatedLayout.size;
|
||||
CGSize newSize = _layout.size;
|
||||
|
||||
if (! CGSizeEqualToSize(oldSize, newSize)) {
|
||||
self.bounds = (CGRect){ oldBounds.origin, newSize };
|
||||
@@ -1224,6 +1170,24 @@ static ASDisplayNodeMethodOverrides GetASDisplayNodeMethodOverrides(Class c)
|
||||
}
|
||||
}
|
||||
|
||||
- (void)layout
|
||||
{
|
||||
ASDisplayNodeAssertMainThread();
|
||||
|
||||
if ([self _hasDirtyLayout]) {
|
||||
return;
|
||||
}
|
||||
|
||||
[self __layoutSubnodes];
|
||||
}
|
||||
|
||||
- (void)__layoutSubnodes
|
||||
{
|
||||
for (ASLayout *subnodeLayout in _layout.sublayouts) {
|
||||
((ASDisplayNode *)subnodeLayout.layoutableObject).frame = [subnodeLayout frame];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)layoutDidFinish
|
||||
{
|
||||
// Hook for subclasses
|
||||
@@ -2137,30 +2101,19 @@ void recursivelyTriggerDisplayForLayer(CALayer *layer, BOOL shouldBlock)
|
||||
- (ASLayout *)calculatedLayout
|
||||
{
|
||||
ASDN::MutexLocker l(__instanceLock__);
|
||||
return _calculatedLayout;
|
||||
}
|
||||
|
||||
- (void)setCalculatedLayout:(ASLayout *)calculatedLayout
|
||||
{
|
||||
ASDN::MutexLocker l(__instanceLock__);
|
||||
|
||||
ASDisplayNodeAssertTrue(calculatedLayout.layoutableObject == self);
|
||||
ASDisplayNodeAssertTrue(calculatedLayout.size.width >= 0.0);
|
||||
ASDisplayNodeAssertTrue(calculatedLayout.size.height >= 0.0);
|
||||
|
||||
_calculatedLayout = calculatedLayout;
|
||||
return _layout;
|
||||
}
|
||||
|
||||
- (CGSize)calculatedSize
|
||||
{
|
||||
ASDN::MutexLocker l(__instanceLock__);
|
||||
return _calculatedLayout.size;
|
||||
return _layout.size;
|
||||
}
|
||||
|
||||
- (ASSizeRange)constrainedSizeForCalculatedLayout
|
||||
{
|
||||
ASDN::MutexLocker l(__instanceLock__);
|
||||
return _calculatedLayout.constrainedSizeRange;
|
||||
return _layout.constrainedSizeRange;
|
||||
}
|
||||
|
||||
- (void)setLayoutSpecBlock:(ASLayoutSpecBlock)layoutSpecBlock
|
||||
@@ -2223,7 +2176,7 @@ void recursivelyTriggerDisplayForLayer(CALayer *layer, BOOL shouldBlock)
|
||||
|
||||
// This will cause the next call to -measureWithSizeRange: to actually compute a new layout
|
||||
// instead of returning the current layout
|
||||
_calculatedLayout.dirty = YES;
|
||||
_layout.dirty = YES;
|
||||
}
|
||||
|
||||
- (void)__didLoad
|
||||
@@ -2590,7 +2543,7 @@ void recursivelyTriggerDisplayForLayer(CALayer *layer, BOOL shouldBlock)
|
||||
- (void)_applyLayout:(ASLayout *)layout layoutTransition:(ASLayoutTransition *)layoutTransition
|
||||
{
|
||||
ASDN::MutexLocker l(__instanceLock__);
|
||||
_calculatedLayout = layout;
|
||||
_layout = layout;
|
||||
|
||||
ASDisplayNodeAssertTrue(layout.layoutableObject == self);
|
||||
ASDisplayNodeAssertTrue(layout.size.width >= 0.0);
|
||||
@@ -2605,35 +2558,15 @@ void recursivelyTriggerDisplayForLayer(CALayer *layer, BOOL shouldBlock)
|
||||
|
||||
// Subnode insertions and removals need to happen always on the main thread if at least one subnode is already loaded
|
||||
ASPerformBlockOnMainThread(^{
|
||||
[layoutTransition commitTransition];
|
||||
[layoutTransition startTransition];
|
||||
});
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
[layoutTransition commitTransition];
|
||||
[layoutTransition startTransition];
|
||||
}
|
||||
|
||||
- (void)layout
|
||||
{
|
||||
ASDisplayNodeAssertMainThread();
|
||||
|
||||
if ([self _hasDirtyLayout]) {
|
||||
return;
|
||||
}
|
||||
|
||||
[self __layoutSublayouts];
|
||||
}
|
||||
|
||||
- (void)__layoutSublayouts
|
||||
{
|
||||
for (ASLayout *subnodeLayout in _calculatedLayout.sublayouts) {
|
||||
((ASDisplayNode *)subnodeLayout.layoutableObject).frame = [subnodeLayout frame];
|
||||
}
|
||||
}
|
||||
|
||||
#pragma mark - Display
|
||||
|
||||
- (void)displayWillStart
|
||||
{
|
||||
ASDisplayNodeAssertMainThread();
|
||||
|
||||
@@ -116,14 +116,14 @@ FOUNDATION_EXPORT NSString * const ASRenderingEngineDidDisplayNodesScheduledBefo
|
||||
CGFloat _contentsScaleForDisplay;
|
||||
|
||||
ASEnvironmentState _environmentState;
|
||||
ASLayout *_calculatedLayout;
|
||||
ASLayout *_layout;
|
||||
|
||||
|
||||
UIEdgeInsets _hitTestSlop;
|
||||
NSMutableArray *_subnodes;
|
||||
|
||||
// Main thread only
|
||||
_ASTransitionContext *_pendingLayoutTransitionContext;
|
||||
_ASTransitionContext *_transitionContext;
|
||||
BOOL _usesImplicitHierarchyManagement;
|
||||
|
||||
int32_t _pendingTransitionID;
|
||||
|
||||
@@ -34,7 +34,7 @@
|
||||
@property (nonatomic, readonly, strong) ASLayout *pendingLayout;
|
||||
|
||||
/**
|
||||
* Returns if the layout transition needs to happen synchronously
|
||||
* Returns if the layout transition can happen asynchronously
|
||||
*/
|
||||
@property (nonatomic, readonly, assign) BOOL isSynchronous;
|
||||
|
||||
@@ -47,7 +47,7 @@
|
||||
/**
|
||||
* Insert and remove subnodes that where added or removed between the previousLayout and the pendingLayout
|
||||
*/
|
||||
- (void)commitTransition;
|
||||
- (void)startTransition;
|
||||
|
||||
/**
|
||||
* Insert all new subnodes that where added between the previous layout and the pending layout
|
||||
|
||||
@@ -71,10 +71,10 @@ static inline BOOL ASLayoutCanTransitionAsynchronous(ASLayout *layout) {
|
||||
- (BOOL)isSynchronous
|
||||
{
|
||||
ASDN::MutexLocker l(__instanceLock__);
|
||||
return !ASLayoutCanTransitionAsynchronous(_pendingLayout);
|
||||
return ASLayoutCanTransitionAsynchronous(_pendingLayout);
|
||||
}
|
||||
|
||||
- (void)commitTransition
|
||||
- (void)startTransition
|
||||
{
|
||||
[self applySubnodeInsertions];
|
||||
[self applySubnodeRemovals];
|
||||
|
||||
Reference in New Issue
Block a user