mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-12-23 22:55:00 +00:00
[Layout Transition] Add default fade in / out layout transition (#2052)
* Add default fade in / out layout transition * Add example for layout transition * Update for recent layout transition API changes * To be able to do a layoutTransition the node needs to be loaded * Rename layoutTransitionDuration to defaultLayoutTransitionDuration * Expose default layout transition duration delay and options * Use `UIViewAnimationOptionBeginFromCurrentState` for initial defaultLayoutTransitionOptions
This commit is contained in:
committed by
Adlai Holler
parent
43370fe6ff
commit
adcc9afb5a
@@ -291,6 +291,10 @@ static ASDisplayNodeMethodOverrides GetASDisplayNodeMethodOverrides(Class c)
|
||||
|
||||
_environmentState = ASEnvironmentStateMakeDefault();
|
||||
|
||||
_defaultLayoutTransitionDuration = 0.2;
|
||||
_defaultLayoutTransitionDelay = 0.0;
|
||||
_defaultLayoutTransitionOptions = UIViewAnimationOptionBeginFromCurrentState;
|
||||
|
||||
_flags.canClearContentsOfLayer = YES;
|
||||
_flags.canCallNeedsDisplayOfLayer = NO;
|
||||
}
|
||||
@@ -857,14 +861,118 @@ static ASDisplayNodeMethodOverrides GetASDisplayNodeMethodOverrides(Class c)
|
||||
|
||||
#pragma mark Layout Transition API
|
||||
|
||||
- (void)setDefaultLayoutTransitionDuration:(NSTimeInterval)defaultLayoutTransitionDuration
|
||||
{
|
||||
ASDN::MutexLocker l(__instanceLock__);
|
||||
_defaultLayoutTransitionDuration = defaultLayoutTransitionDuration;
|
||||
}
|
||||
|
||||
- (NSTimeInterval)defaultLayoutTransitionDuration
|
||||
{
|
||||
ASDN::MutexLocker l(__instanceLock__);
|
||||
return _defaultLayoutTransitionDuration;
|
||||
}
|
||||
|
||||
- (void)setDefaultLayoutTransitionDelay:(NSTimeInterval)defaultLayoutTransitionDelay
|
||||
{
|
||||
ASDN::MutexLocker l(__instanceLock__);
|
||||
_defaultLayoutTransitionDelay = defaultLayoutTransitionDelay;
|
||||
}
|
||||
|
||||
- (NSTimeInterval)defaultLayoutTransitionDelay
|
||||
{
|
||||
ASDN::MutexLocker l(__instanceLock__);
|
||||
return _defaultLayoutTransitionDelay;
|
||||
}
|
||||
|
||||
- (void)setDefaultLayoutTransitionOptions:(UIViewAnimationOptions)defaultLayoutTransitionOptions
|
||||
{
|
||||
ASDN::MutexLocker l(__instanceLock__);
|
||||
_defaultLayoutTransitionOptions = defaultLayoutTransitionOptions;
|
||||
}
|
||||
|
||||
- (UIViewAnimationOptions)defaultLayoutTransitionOptions
|
||||
{
|
||||
ASDN::MutexLocker l(__instanceLock__);
|
||||
return _defaultLayoutTransitionOptions;
|
||||
}
|
||||
|
||||
/*
|
||||
* 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.
|
||||
* Hook for subclasse to perform an animation based on the given ASContextTransitioning. By default a fade in and out
|
||||
* animation is provided.
|
||||
*/
|
||||
- (void)animateLayoutTransition:(id<ASContextTransitioning>)context
|
||||
{
|
||||
[self __layoutSublayouts];
|
||||
[context completeTransition:YES];
|
||||
ASDisplayNode *node = self;
|
||||
|
||||
NSAssert(node.isNodeLoaded == YES, @"Invalid node state");
|
||||
NSAssert([context isAnimated] == YES, @"Can't animate a non-animatable context");
|
||||
|
||||
NSArray<ASDisplayNode *> *removedSubnodes = [context removedSubnodes];
|
||||
NSMutableArray<UIView *> *removedViews = [NSMutableArray array];
|
||||
NSMutableArray<ASDisplayNode *> *insertedSubnodes = [[context insertedSubnodes] mutableCopy];
|
||||
NSMutableArray<ASDisplayNode *> *movedSubnodes = [NSMutableArray array];
|
||||
|
||||
for (ASDisplayNode *subnode in [context subnodesForKey:ASTransitionContextToLayoutKey]) {
|
||||
if ([insertedSubnodes containsObject:subnode] == NO) {
|
||||
// This is an existing subnode, check if it is resized, moved or both
|
||||
CGRect fromFrame = [context initialFrameForNode:subnode];
|
||||
CGRect toFrame = [context finalFrameForNode:subnode];
|
||||
if (CGSizeEqualToSize(fromFrame.size, toFrame.size) == NO) {
|
||||
// To crossfade resized subnodes, show a snapshot of it on top.
|
||||
// The node itself can then be treated as a newly-inserted one.
|
||||
UIView *snapshotView = [subnode.view snapshotViewAfterScreenUpdates:YES];
|
||||
snapshotView.frame = [context initialFrameForNode:subnode];
|
||||
snapshotView.alpha = 1;
|
||||
|
||||
[node.view insertSubview:snapshotView aboveSubview:subnode.view];
|
||||
[removedViews addObject:snapshotView];
|
||||
|
||||
[insertedSubnodes addObject:subnode];
|
||||
}
|
||||
if (CGPointEqualToPoint(fromFrame.origin, toFrame.origin) == NO) {
|
||||
[movedSubnodes addObject:subnode];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (ASDisplayNode *insertedSubnode in insertedSubnodes) {
|
||||
insertedSubnode.frame = [context finalFrameForNode:insertedSubnode];
|
||||
insertedSubnode.alpha = 0;
|
||||
}
|
||||
|
||||
[UIView animateWithDuration:self.defaultLayoutTransitionDuration delay:self.defaultLayoutTransitionDelay options:self.defaultLayoutTransitionOptions animations:^{
|
||||
// Fade removed subnodes and views out
|
||||
for (ASDisplayNode *removedSubnode in removedSubnodes) {
|
||||
removedSubnode.alpha = 0;
|
||||
}
|
||||
for (UIView *removedView in removedViews) {
|
||||
removedView.alpha = 0;
|
||||
}
|
||||
|
||||
// Fade inserted subnodes in
|
||||
for (ASDisplayNode *insertedSubnode in insertedSubnodes) {
|
||||
insertedSubnode.alpha = 1;
|
||||
}
|
||||
|
||||
// Update frame of self and moved subnodes
|
||||
CGSize fromSize = [context layoutForKey:ASTransitionContextFromLayoutKey].size;
|
||||
CGSize toSize = [context layoutForKey:ASTransitionContextToLayoutKey].size;
|
||||
BOOL isResized = (CGSizeEqualToSize(fromSize, toSize) == NO);
|
||||
if (isResized == YES) {
|
||||
CGPoint position = node.frame.origin;
|
||||
node.frame = CGRectMake(position.x, position.y, toSize.width, toSize.height);
|
||||
}
|
||||
for (ASDisplayNode *movedSubnode in movedSubnodes) {
|
||||
movedSubnode.frame = [context finalFrameForNode:movedSubnode];
|
||||
}
|
||||
} completion:^(BOOL finished) {
|
||||
for (UIView *removedView in removedViews) {
|
||||
[removedView removeFromSuperview];
|
||||
}
|
||||
// Subnode removals are automatically performed
|
||||
[context completeTransition:finished];
|
||||
}];
|
||||
}
|
||||
|
||||
/*
|
||||
|
||||
Reference in New Issue
Block a user