Bläddra i källkod

Solve the mess between default animation and UIViewAnimation in UICollectionView. (#1372)

Rename redundant block type definition.
Solve the mess between default animation and UIViewAnimation in UICollectionView
Frank 6 år sedan
förälder
incheckning
c086852791

+ 4 - 4
MJRefresh/Base/MJRefreshBackFooter.m

@@ -89,11 +89,11 @@ - (void)setState:(MJRefreshState)state
         // 刷新完毕
         if (MJRefreshStateRefreshing == oldState) {
             [UIView animateWithDuration:MJRefreshSlowAnimationDuration animations:^{
-                self.scrollView.mj_insetB -= self.lastBottomDelta;
-                
-                if (self.endRefreshingAnimateCompletionBlock) {
-                    self.endRefreshingAnimateCompletionBlock();
+                if (self.endRefreshingAnimationBeginAction) {
+                    self.endRefreshingAnimationBeginAction();
                 }
+                
+                self.scrollView.mj_insetB -= self.lastBottomDelta;
                 // 自动调整透明度
                 if (self.isAutomaticallyChangeAlpha) self.alpha = 0.0;
             } completion:^(BOOL finished) {

+ 11 - 7
MJRefresh/Base/MJRefreshComponent.h

@@ -31,11 +31,14 @@ typedef NS_ENUM(NSInteger, MJRefreshState) {
 };
 
 /** 进入刷新状态的回调 */
-typedef void (^MJRefreshComponentRefreshingBlock)(void);
+typedef void (^MJRefreshComponentRefreshingBlock)(void) MJRefreshDeprecated("first deprecated in 3.3.0 - Use `MJRefreshComponentAction` instead");
 /** 开始刷新后的回调(进入刷新状态后的回调) */
-typedef void (^MJRefreshComponentBeginRefreshingCompletionBlock)(void);
+typedef void (^MJRefreshComponentBeginRefreshingCompletionBlock)(void) MJRefreshDeprecated("first deprecated in 3.3.0 - Use `MJRefreshComponentAction` instead");
 /** 结束刷新后的回调 */
-typedef void (^MJRefreshComponentEndRefreshingCompletionBlock)(void);
+typedef void (^MJRefreshComponentEndRefreshingCompletionBlock)(void) MJRefreshDeprecated("first deprecated in 3.3.0 - Use `MJRefreshComponentAction` instead");
+
+/** 刷新用到的回调类型 */
+typedef void (^MJRefreshComponentAction)(void);
 
 /** 刷新控件的基类 */
 @interface MJRefreshComponent : UIView
@@ -47,7 +50,7 @@ typedef void (^MJRefreshComponentEndRefreshingCompletionBlock)(void);
 }
 #pragma mark - 刷新回调
 /** 正在刷新的回调 */
-@property (copy, nonatomic, nullable) MJRefreshComponentRefreshingBlock refreshingBlock;
+@property (copy, nonatomic, nullable) MJRefreshComponentAction refreshingBlock;
 /** 设置回调对象和回调方法 */
 - (void)setRefreshingTarget:(id)target refreshingAction:(SEL)action;
 
@@ -63,11 +66,12 @@ typedef void (^MJRefreshComponentEndRefreshingCompletionBlock)(void);
 - (void)beginRefreshing;
 - (void)beginRefreshingWithCompletionBlock:(void (^)(void))completionBlock;
 /** 开始刷新后的回调(进入刷新状态后的回调) */
-@property (copy, nonatomic, nullable) MJRefreshComponentBeginRefreshingCompletionBlock beginRefreshingCompletionBlock;
+@property (copy, nonatomic, nullable) MJRefreshComponentAction beginRefreshingCompletionBlock;
 /** 带动画的结束刷新的回调 */
-@property (copy, nonatomic, nullable) MJRefreshComponentEndRefreshingCompletionBlock endRefreshingAnimateCompletionBlock;
+@property (copy, nonatomic, nullable) MJRefreshComponentAction endRefreshingAnimateCompletionBlock MJRefreshDeprecated("first deprecated in 3.3.0 - Use `endRefreshingAnimationStartingBlock` instead");
+@property (copy, nonatomic, nullable) MJRefreshComponentAction endRefreshingAnimationBeginAction;
 /** 结束刷新的回调 */
-@property (copy, nonatomic, nullable) MJRefreshComponentEndRefreshingCompletionBlock endRefreshingCompletionBlock;
+@property (copy, nonatomic, nullable) MJRefreshComponentAction endRefreshingCompletionBlock;
 /** 结束刷新状态 */
 - (void)endRefreshing;
 - (void)endRefreshingWithCompletionBlock:(void (^)(void))completionBlock;

+ 5 - 0
MJRefresh/Base/MJRefreshComponent.m

@@ -237,6 +237,11 @@ - (void)executeRefreshingCallback
         }
     })
 }
+
+#pragma mark - <<< Deprecation compatible function >>> -
+- (void)setEndRefreshingAnimateCompletionBlock:(MJRefreshComponentEndRefreshingCompletionBlock)endRefreshingAnimateCompletionBlock {
+    _endRefreshingAnimationBeginAction = endRefreshingAnimateCompletionBlock;
+}
 @end
 
 @implementation UILabel(MJRefresh)

+ 1 - 1
MJRefresh/Base/MJRefreshFooter.h

@@ -13,7 +13,7 @@ NS_ASSUME_NONNULL_BEGIN
 
 @interface MJRefreshFooter : MJRefreshComponent
 /** 创建footer */
-+ (instancetype)footerWithRefreshingBlock:(MJRefreshComponentRefreshingBlock)refreshingBlock;
++ (instancetype)footerWithRefreshingBlock:(MJRefreshComponentAction)refreshingBlock;
 /** 创建footer */
 + (instancetype)footerWithRefreshingTarget:(id)target refreshingAction:(SEL)action;
 

+ 1 - 1
MJRefresh/Base/MJRefreshFooter.m

@@ -16,7 +16,7 @@ @interface MJRefreshFooter()
 
 @implementation MJRefreshFooter
 #pragma mark - 构造方法
-+ (instancetype)footerWithRefreshingBlock:(MJRefreshComponentRefreshingBlock)refreshingBlock
++ (instancetype)footerWithRefreshingBlock:(MJRefreshComponentAction)refreshingBlock
 {
     MJRefreshFooter *cmp = [[self alloc] init];
     cmp.refreshingBlock = refreshingBlock;

+ 1 - 1
MJRefresh/Base/MJRefreshHeader.h

@@ -13,7 +13,7 @@ NS_ASSUME_NONNULL_BEGIN
 
 @interface MJRefreshHeader : MJRefreshComponent
 /** 创建header */
-+ (instancetype)headerWithRefreshingBlock:(MJRefreshComponentRefreshingBlock)refreshingBlock;
++ (instancetype)headerWithRefreshingBlock:(MJRefreshComponentAction)refreshingBlock;
 /** 创建header */
 + (instancetype)headerWithRefreshingTarget:(id)target refreshingAction:(SEL)action;
 

+ 97 - 33
MJRefresh/Base/MJRefreshHeader.m

@@ -9,13 +9,16 @@
 
 #import "MJRefreshHeader.h"
 
-@interface MJRefreshHeader()
+NSString * const MJRefreshHeaderRefreshing2IdleBoundsKey = @"MJRefreshHeaderRefreshing2IdleBounds";
+NSString * const MJRefreshHeaderRefreshingBoundsKey = @"MJRefreshHeaderRefreshingBounds";
+
+@interface MJRefreshHeader() <CAAnimationDelegate>
 @property (assign, nonatomic) CGFloat insetTDelta;
 @end
 
 @implementation MJRefreshHeader
 #pragma mark - 构造方法
-+ (instancetype)headerWithRefreshingBlock:(MJRefreshComponentRefreshingBlock)refreshingBlock
++ (instancetype)headerWithRefreshingBlock:(MJRefreshComponentAction)refreshingBlock
 {
     MJRefreshHeader *cmp = [[self alloc] init];
     cmp.refreshingBlock = refreshingBlock;
@@ -109,8 +112,18 @@ - (void)scrollViewContentOffsetDidChange:(NSDictionary *)change
     }
 }
 
+
 - (void)setState:(MJRefreshState)state
 {
+    /**
+     这个解决方法的思路出自 https://github.com/CoderMJLee/MJRefresh/pull/844
+     修改了用+ [UIView animateWithDuration: animations:]实现的修改contentInset的动画
+     fix issue#225 https://github.com/CoderMJLee/MJRefresh/issues/225
+     另一种解法 pull#737 https://github.com/CoderMJLee/MJRefresh/pull/737
+     
+     同时, 处理了 Refreshing 中的动画替换.
+    */
+    
     MJRefreshCheckState
     
     // 根据状态做事情
@@ -120,39 +133,90 @@ - (void)setState:(MJRefreshState)state
         // 保存刷新时间
         [[NSUserDefaults standardUserDefaults] setObject:[NSDate date] forKey:self.lastUpdatedTimeKey];
         [[NSUserDefaults standardUserDefaults] synchronize];
+
+        // 由于修改 Inset 会导致 self.pullingPercent 联动设置 self.alpha, 故提前获取 alpha 值, 后续用于还原 alpha 动画
+        CGFloat viewAlpha = self.alpha;
         
-        // 恢复inset和offset
-        [UIView animateWithDuration:MJRefreshSlowAnimationDuration animations:^{
-            self.scrollView.mj_insetT += self.insetTDelta;
-            
-            if (self.endRefreshingAnimateCompletionBlock) {
-                self.endRefreshingAnimateCompletionBlock();
-            }
-            // 自动调整透明度
-            if (self.isAutomaticallyChangeAlpha) self.alpha = 0.0;
-        } completion:^(BOOL finished) {
-            self.pullingPercent = 0.0;
-            
-            if (self.endRefreshingCompletionBlock) {
-                self.endRefreshingCompletionBlock();
-            }
-        }];
+        self.scrollView.mj_insetT += self.insetTDelta;
+        // 禁用交互, 如果不禁用可能会引起渲染问题.
+        self.scrollView.userInteractionEnabled = NO;
+
+        //CAAnimation keyPath 不支持 contentInset 用Bounds的动画代替
+        CABasicAnimation *boundsAnimation = [CABasicAnimation animationWithKeyPath:@"bounds"];
+        boundsAnimation.fromValue = [NSValue valueWithCGRect:CGRectOffset(self.scrollView.bounds, 0, self.insetTDelta)];
+        boundsAnimation.duration = MJRefreshSlowAnimationDuration;
+        //在delegate里移除
+        boundsAnimation.removedOnCompletion = NO;
+        boundsAnimation.fillMode = kCAFillModeBoth;
+        boundsAnimation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
+        boundsAnimation.delegate = self;
+
+        [self.scrollView.layer addAnimation:boundsAnimation forKey:MJRefreshHeaderRefreshing2IdleBoundsKey];
+        
+        if (self.endRefreshingAnimationBeginAction) {
+            self.endRefreshingAnimationBeginAction();
+        }
+        // 自动调整透明度的动画
+        if (self.isAutomaticallyChangeAlpha) {
+            CABasicAnimation *opacityAnimation = [CABasicAnimation animationWithKeyPath:@"opacity"];
+            opacityAnimation.fromValue = @(viewAlpha);
+            opacityAnimation.toValue = @(0.0);
+            opacityAnimation.duration = MJRefreshSlowAnimationDuration;
+            opacityAnimation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
+            [self.layer addAnimation:opacityAnimation forKey:@"MJRefreshHeaderRefreshing2IdleOpacity"];
+
+            // 由于修改了 inset 导致, pullingPercent 被设置值, alpha 已经被提前修改为 0 了. 所以这里不用置 0, 但为了代码的严谨性, 不依赖其他的特殊实现方式, 这里还是置 0.
+            self.alpha = 0;
+        }
     } else if (state == MJRefreshStateRefreshing) {
-        MJRefreshDispatchAsyncOnMainQueue({
-            [UIView animateWithDuration:MJRefreshFastAnimationDuration animations:^{
-                if (self.scrollView.panGestureRecognizer.state != UIGestureRecognizerStateCancelled) {
-                    CGFloat top = self.scrollViewOriginalInset.top + self.mj_h;
-                    // 增加滚动区域top
-                    self.scrollView.mj_insetT = top;
-                    // 设置滚动位置
-                    CGPoint offset = self.scrollView.contentOffset;
-                    offset.y = -top;
-                    [self.scrollView setContentOffset:offset animated:NO];
-                }
-            } completion:^(BOOL finished) {
-                [self executeRefreshingCallback];
-            }];
-        })
+        if (self.scrollView.panGestureRecognizer.state != UIGestureRecognizerStateCancelled) {
+            CGFloat top = self.scrollViewOriginalInset.top + self.mj_h;
+            // 禁用交互, 如果不禁用可能会引起渲染问题.
+            self.scrollView.userInteractionEnabled = NO;
+
+            // CAAnimation keyPath不支持contentOffset 用Bounds的动画代替
+            CABasicAnimation *boundsAnimation = [CABasicAnimation animationWithKeyPath:@"bounds"];
+            CGRect bounds = self.scrollView.bounds;
+            bounds.origin.y = -top;
+            boundsAnimation.fromValue = [NSValue valueWithCGRect:self.scrollView.bounds];
+            boundsAnimation.toValue = [NSValue valueWithCGRect:bounds];
+            boundsAnimation.duration = MJRefreshFastAnimationDuration;
+            //在delegate里移除
+            boundsAnimation.removedOnCompletion = NO;
+            boundsAnimation.fillMode = kCAFillModeBoth;
+            boundsAnimation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
+            boundsAnimation.delegate = self;
+            [self.scrollView.layer addAnimation:boundsAnimation forKey:MJRefreshHeaderRefreshingBoundsKey];
+        } else {
+            [self executeRefreshingCallback];
+        }
+    }
+}
+
+#pragma mark - CAAnimationDelegate
+- (void)animationDidStop:(CAAnimation *)anim finished:(BOOL)flag {
+    if ([anim isEqual:[self.scrollView.layer animationForKey:MJRefreshHeaderRefreshing2IdleBoundsKey]]) {
+        [self.scrollView.layer removeAnimationForKey:MJRefreshHeaderRefreshing2IdleBoundsKey];
+        self.pullingPercent = 0.0;
+
+        self.scrollView.userInteractionEnabled = YES;
+        if (self.endRefreshingCompletionBlock) {
+            self.endRefreshingCompletionBlock();
+        }
+    }
+    
+    if ([anim isEqual:[self.scrollView.layer animationForKey:MJRefreshHeaderRefreshingBoundsKey]]) {
+        [self.scrollView.layer removeAnimationForKey:MJRefreshHeaderRefreshingBoundsKey];
+        
+        CGFloat top = self.scrollViewOriginalInset.top + self.mj_h;
+        self.scrollView.mj_insetT = top;
+        // 设置最终滚动位置
+        CGPoint offset = self.scrollView.contentOffset;
+        offset.y = -top;
+        [self.scrollView setContentOffset:offset animated:NO];
+        
+        self.scrollView.userInteractionEnabled = YES;
+        [self executeRefreshingCallback];
     }
 }