frame
是View在其父视图的坐标系中的位置;
bounds
是View在其本身的坐标系中的位置。
当我们修改一个View的bounds时,View在其本身的坐标系中的位置发生了改变,但是其子View在该坐标系中的位置没有改变,所以它的子View和它本身的相对位置发生了改变,相当于子View移动了,但是子View的frame并没有改变。
UIScrollView滚动时,实际上就是修改它自己的bounds。我们可以创建一个UIScrollView的子类,然后在layoutSubviews方法中打印它的bounds:
- (void)layoutSubviews {
[super layoutSubviews];
NSLog(@"%@", NSStringFromCGRect(self.bounds));
}
可以发现滚动的时候UIScrollView的bounds一直在改变:
NestedScrollerView[4762:151333] {{0, 132}, {414, 736}}
NestedScrollerView[4762:151333] {{0, 132.33333333333334}, {414, 736}}
NestedScrollerView[4762:151333] {{0, 132.66666666666666}, {414, 736}}
NestedScrollerView[4762:151333] {{0, 133}, {414, 736}}
NestedScrollerView[4762:151333] {{0, 133.33333333333334}, {414, 736}}
NestedScrollerView[4762:151333] {{0, 133.66666666666666}, {414, 736}}
NestedScrollerView[4762:151333] {{0, 134}, {414, 736}}
NestedScrollerView[4762:151333] {{0, 133.66666666666666}, {414, 736}}
NestedScrollerView[4762:151333] {{0, 133.33333333333334}, {414, 736}}
我们可以自定义实现一个ScrollView:
//
// HOScrollView.m
// HOScrollView
//
// Created by HoChan on 2019/2/2.
// Copyright © 2019 Okhoochan. All rights reserved.
//
#import "HOScrollView.h"
#import <pop/POP.h>
@interface HOScrollView()
@property (nonatomic, assign) CGRect startBounds;
@end
@implementation HOScrollView
- (instancetype)initWithFrame:(CGRect)frame {
self = [super initWithFrame:frame];
if (self) {
UIPanGestureRecognizer *panGesture = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(handlePanGesture:)];
[self addGestureRecognizer:panGesture];
}
return self;
}
- (void)handlePanGesture:(UIPanGestureRecognizer *)panGesture {
switch (panGesture.state) {
case UIGestureRecognizerStateBegan: {
[self pop_removeAnimationForKey:@"bounce"];
[self pop_removeAnimationForKey:@"decelerate"];
self.startBounds = self.bounds;
break;
}
case UIGestureRecognizerStateChanged: {
CGPoint translation = [panGesture translationInView:self];
CGRect bounds = self.startBounds;
CGFloat newBoundsOriginX = self.startBounds.origin.x - translation.x;
CGFloat minBoundsOriginX = 0;
CGFloat maxBoundsOriginX = self.contentSize.width - self.bounds.size.width;
CGFloat constrainedBoundsOriginX = fmax(minBoundsOriginX, fmin(maxBoundsOriginX, newBoundsOriginX));
bounds.origin.x = constrainedBoundsOriginX + (newBoundsOriginX - constrainedBoundsOriginX) / 2.0;
CGFloat newBoundsOriginY = self.startBounds.origin.y - translation.y;
CGFloat minBoundsOriginY = 0;
CGFloat maxBoundsOriginY = self.contentSize.height - self.bounds.size.height;
CGFloat constrainedBoundsOriginY = fmax(minBoundsOriginY, fmin(maxBoundsOriginY, newBoundsOriginY));
bounds.origin.y = constrainedBoundsOriginY + (newBoundsOriginY - constrainedBoundsOriginY) / 2.0;
self.bounds = bounds;
break;
}
case UIGestureRecognizerStateEnded: {
CGPoint velocity = [panGesture velocityInView:self];
velocity.x = -velocity.x;
velocity.y = -velocity.y;
POPDecayAnimation *decayAnimation = [POPDecayAnimation animationWithPropertyNamed:kPOPViewBounds];
decayAnimation.velocity = [NSValue valueWithCGRect:CGRectMake(velocity.x, velocity.y, 0, 0)];
[self pop_addAnimation:decayAnimation forKey:@"decelerate"];
break;
}
default:
break;
}
}
- (void)setBounds:(CGRect)bounds {
[super setBounds:bounds];
BOOL outsideBoundsMinimum = bounds.origin.x < 0.0 || bounds.origin.y < 0.0;
BOOL outsideBoundsMaximum = bounds.origin.x > self.contentSize.width - self.bounds.size.width ||
bounds.origin.y > self.contentSize.height - self.bounds.size.height;
if (outsideBoundsMinimum || outsideBoundsMaximum) {
POPDecayAnimation *decayAnimation = [self pop_animationForKey:@"decelerate"];
if (decayAnimation) {
CGPoint target = bounds.origin;
target.x = fmin(fmax(target.x, 0.0), self.contentSize.width - bounds.size.width);
target.y = fmin(fmax(target.y, 0.0), self.contentSize.height - bounds.size.height);
POPSpringAnimation *springAnimation = [POPSpringAnimation animationWithPropertyNamed:kPOPViewBounds];
springAnimation.velocity = decayAnimation.velocity;
springAnimation.toValue = [NSValue valueWithCGRect:CGRectMake(target.x, target.y, bounds.size.width, bounds.size.height)];
springAnimation.springBounciness = 0.1;
springAnimation.springSpeed = 5.0;
[self pop_addAnimation:springAnimation forKey:@"bounce"];
[self pop_removeAnimationForKey:@"decelerate"];
}
}
}
@end
可以利用Fackbook的pop动画库实现bounce和减速效果。
参考:
Understanding UIScrollView
Replicating UIScrollView’s deceleration with Facebook Pop
Experimenting with Facebook’s open source animation framework POP
grp/CustomScrollView
网友评论