-
Notifications
You must be signed in to change notification settings - Fork 947
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(iOS): add iOS custom scroll time
- Loading branch information
v_zxingli
committed
Sep 6, 2023
1 parent
eec0d79
commit 3da1b67
Showing
4 changed files
with
340 additions
and
0 deletions.
There are no files selected for viewing
10 changes: 10 additions & 0 deletions
10
framework/examples/ios-demo/HippyDemo/DemoCustomScrollViewController.h
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
|
||
#import <UIKit/UIKit.h> | ||
|
||
NS_ASSUME_NONNULL_BEGIN | ||
|
||
@interface DemoCustomScrollViewController : UIViewController | ||
|
||
@end | ||
|
||
NS_ASSUME_NONNULL_END |
57 changes: 57 additions & 0 deletions
57
framework/examples/ios-demo/HippyDemo/DemoCustomScrollViewController.m
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,57 @@ | ||
|
||
#import "DemoCustomScrollViewController.h" | ||
#import "HippyCustomScrollView.h" | ||
|
||
@interface DemoCustomScrollViewController () <UITableViewDelegate ,UITableViewDataSource> | ||
|
||
@property (nonatomic ,strong) UITableView *tableView; | ||
|
||
@end | ||
|
||
@implementation DemoCustomScrollViewController | ||
|
||
- (UITableView *)tableView { | ||
if (!_tableView) { | ||
_tableView = [[UITableView alloc] initWithFrame:self.view.bounds style:UITableViewStylePlain]; | ||
_tableView.delegate = self; | ||
_tableView.dataSource = self; | ||
} | ||
return _tableView; | ||
} | ||
|
||
- (void)viewDidLoad { | ||
[super viewDidLoad]; | ||
// Do any additional setup after loading the view. | ||
} | ||
|
||
- (void)click { | ||
[self.tableView setContentOffset:CGPointMake(0, 500) duration:2.25 completion:^{ | ||
|
||
}]; | ||
} | ||
|
||
/* | ||
#pragma mark - Navigation | ||
// In a storyboard-based application, you will often want to do a little preparation before navigation | ||
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender { | ||
// Get the new view controller using [segue destinationViewController]. | ||
// Pass the selected object to the new view controller. | ||
} | ||
*/ | ||
|
||
- (nonnull UITableViewCell *)tableView:(nonnull UITableView *)tableView cellForRowAtIndexPath:(nonnull NSIndexPath *)indexPath { | ||
static NSString *identifier = @"identifier"; | ||
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:identifier]; | ||
if (!cell) { | ||
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:identifier]; | ||
} | ||
cell.textLabel.text = [NSString stringWithFormat:@"%ld",(long)indexPath.row]; | ||
return cell; | ||
} | ||
|
||
- (NSInteger)tableView:(nonnull UITableView *)tableView numberOfRowsInSection:(NSInteger)section { | ||
return 100; | ||
} | ||
|
||
@end |
59 changes: 59 additions & 0 deletions
59
framework/examples/ios-demo/HippyDemo/HippyCustomScrollView.h
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,59 @@ | ||
|
||
#import <UIKit/UIKit.h> | ||
|
||
typedef enum | ||
{ | ||
linear = 0, | ||
quadIn, | ||
quadOut, | ||
quadInOut, | ||
cubicIn, | ||
cubicOut, | ||
cubicInOut, | ||
quartIn, | ||
quartOut, | ||
quartInOut, | ||
quintIn, | ||
quintOut, | ||
quintInOut, | ||
sineIn, | ||
sineOut, | ||
sineInOut, | ||
expoIn, | ||
expoOut, | ||
expoInOut, | ||
circleIn, | ||
circleOut, | ||
circleInOut | ||
} HippyScrollTimingEnum; | ||
|
||
NS_ASSUME_NONNULL_BEGIN | ||
|
||
@class HippyScrollTimingFunction; | ||
@interface UIScrollView (HippyCustomOffsetAnimation) | ||
|
||
- (void)setContentOffset:(CGPoint)contentOffset | ||
duration:(NSTimeInterval)duration | ||
completion:(void(^)(void))block; | ||
|
||
@end | ||
|
||
@interface HippyScrollTimingFunction : NSObject | ||
|
||
@property (nonatomic,assign) HippyScrollTimingEnum type; | ||
|
||
@end | ||
|
||
@interface HippyScrollViewAnimator : NSObject | ||
|
||
@property (nonatomic, weak) UIScrollView *scrollView; | ||
@property (nonatomic, copy) void(^block)(void); | ||
|
||
- (void)setContentOffset:(CGPoint)contentOffset duration:(NSTimeInterval)duration; | ||
- (instancetype)initWithScrollView:(UIScrollView *)scrollView | ||
timingFunction:(HippyScrollTimingFunction *)timingFunction | ||
type:(HippyScrollTimingEnum)type; | ||
|
||
@end | ||
|
||
NS_ASSUME_NONNULL_END |
214 changes: 214 additions & 0 deletions
214
framework/examples/ios-demo/HippyDemo/HippyCustomScrollView.m
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,214 @@ | ||
|
||
#import "HippyCustomScrollView.h" | ||
#import <objc/runtime.h> | ||
|
||
@class HippyScrollViewAnimator; | ||
@implementation UIScrollView (HippyCustomOffsetAnimation) | ||
|
||
static NSString *HippyCustomAnimatorKey = @"QModelOverlayParamsKey"; //定义一个key值 | ||
|
||
- (void)setAnimator:(HippyScrollViewAnimator *)animator | ||
{ | ||
objc_setAssociatedObject(self, &HippyCustomAnimatorKey, animator, OBJC_ASSOCIATION_RETAIN); | ||
} | ||
|
||
- (HippyScrollViewAnimator *)animator | ||
{ | ||
return objc_getAssociatedObject(self, &HippyCustomAnimatorKey); | ||
} | ||
|
||
- (void)setContentOffset:(CGPoint)contentOffset | ||
duration:(NSTimeInterval)duration | ||
completion:(void(^)(void))block { | ||
if (!self.animator) { | ||
self.animator = [[HippyScrollViewAnimator alloc] initWithScrollView:self timingFunction:[HippyScrollTimingFunction new] type:sineInOut]; | ||
} | ||
__weak __typeof(self) weakSelf = self; | ||
self.animator.block = ^{ | ||
__strong __typeof(weakSelf) strongSelf = weakSelf; | ||
dispatch_async(dispatch_get_main_queue(), ^{ | ||
if (strongSelf) { | ||
strongSelf.animator = nil; | ||
} | ||
}); | ||
block(); | ||
}; | ||
|
||
[self.animator setContentOffset:contentOffset duration:duration]; | ||
block(); | ||
} | ||
|
||
@end | ||
|
||
@implementation HippyScrollTimingFunction | ||
/// | ||
/// - Parameters: | ||
/// - t: time | ||
/// - b: begin | ||
/// - c: change | ||
/// - d: duration | ||
- (CGFloat)compute:(CGFloat)t b:(CGFloat)b c:(CGFloat)c d:(CGFloat)d { | ||
switch (self.type) { | ||
case linear: | ||
return c * t / d + b; | ||
case quadIn: | ||
t /= d; | ||
return c * t * t + b; | ||
case quadOut: | ||
t /= d; | ||
return -c * t * (t - 2) + b; | ||
case quadInOut: | ||
t /= d / 2; | ||
if (t < 1) { | ||
return c / 2 * t * t + b; | ||
} | ||
t -= 1; | ||
return -c / 2 * (t * (t - 2) - 1) + b; | ||
case cubicIn: | ||
t /= d; | ||
return c * t * t * t + b; | ||
case cubicOut: | ||
t = t / d - 1; | ||
return c * (t * t * t + 1) + b; | ||
case cubicInOut: | ||
t /= d / 2; | ||
if (t < 1) { | ||
return c / 2 * t * t * t + b; | ||
} | ||
t -= 2; | ||
return c / 2 * (t * t * t + 2) + b; | ||
case quartIn: | ||
t /= d; | ||
return c * t * t * t * t + b; | ||
case quartOut: | ||
t = t / d - 1; | ||
return -c * (t * t * t * t - 1) + b; | ||
case quartInOut: | ||
t /= d / 2; | ||
if (t < 1) { | ||
return c / 2 * t * t * t * t + b; | ||
} | ||
t -= 2; | ||
return -c / 2 * (t * t * t * t - 2) + b; | ||
case quintIn: | ||
t /= d; | ||
return c * t * t * t * t * t + b; | ||
case quintOut: | ||
t = t / d - 1; | ||
return c * ( t * t * t * t * t + 1) + b; | ||
case quintInOut: | ||
t /= d / 2; | ||
if (t < 1) { | ||
return c / 2 * t * t * t * t * t + b; | ||
} | ||
t -= 2; | ||
return c / 2 * (t * t * t * t * t + 2) + b; | ||
case sineIn: | ||
return -c * cos(t / d * (M_PI / 2)) + c + b; | ||
case sineOut: | ||
return c * sin(t / d * (M_PI / 2)) + b; | ||
case sineInOut: | ||
return -c / 2 * (cos(M_PI * t / d) - 1) + b; | ||
case expoIn: | ||
return (t == 0) ? b : c * pow(2, 10 * (t / d - 1)) + b; | ||
case expoOut: | ||
return (t == d) ? b + c : c * (-pow(2, -10 * t / d) + 1) + b; | ||
case expoInOut: | ||
if (t == 0) { | ||
return b; | ||
} | ||
if (t == d) { | ||
return b + c; | ||
} | ||
t /= d / 2; | ||
if (t < 1) { | ||
return c / 2 * pow(2, 10 * (t - 1)) + b; | ||
} | ||
t -= 1; | ||
return c / 2 * (-pow(2, -10 * t) + 2) + b; | ||
case circleIn: | ||
t /= d; | ||
return -c * (sqrt(1 - t * t) - 1) + b; | ||
case circleOut: | ||
t = t / d - 1; | ||
return c * sqrt(1 - t * t) + b; | ||
case circleInOut: | ||
t /= d / 2; | ||
if (t < 1) { | ||
return -c / 2 * (sqrt(1 - t * t) - 1) + b; | ||
} | ||
t -= 2; | ||
return c / 2 * (sqrt(1 - t * t) + 1) + b; | ||
} | ||
} | ||
|
||
@end | ||
|
||
@interface HippyScrollViewAnimator () | ||
|
||
@property (nonatomic, assign) HippyScrollTimingEnum type; | ||
@property (nonatomic, strong) HippyScrollTimingFunction *timingFunction; | ||
@property (nonatomic, assign) NSTimeInterval startTime; | ||
@property (nonatomic, assign) CGPoint startOffset; | ||
@property (nonatomic, assign) CGPoint destinationOffset; | ||
@property (nonatomic, assign) NSTimeInterval duration; | ||
@property (nonatomic, assign) NSTimeInterval runTime; | ||
@property (nonatomic, strong) CADisplayLink *timer; | ||
|
||
@end | ||
|
||
@implementation HippyScrollViewAnimator | ||
|
||
- (instancetype)initWithScrollView:(UIScrollView *)scrollView | ||
timingFunction:(HippyScrollTimingFunction *)timingFunction | ||
type:(HippyScrollTimingEnum)type { | ||
if (self = [super init]) { | ||
self.scrollView = scrollView; | ||
self.timingFunction = timingFunction; | ||
self.type = type; | ||
} | ||
return self; | ||
} | ||
|
||
- (void)setContentOffset:(CGPoint)contentOffset duration:(NSTimeInterval)duration { | ||
|
||
if (!self.scrollView) return; | ||
|
||
self.startTime = [[NSDate date] timeIntervalSince1970]; | ||
self.startOffset = self.scrollView.contentOffset; | ||
self.destinationOffset = contentOffset; | ||
self.duration = duration; | ||
self.runTime = 0; | ||
|
||
if (self.duration <= 0) { | ||
[self.scrollView setContentOffset:contentOffset animated:NO]; | ||
return; | ||
} | ||
if (!self.timer) { | ||
self.timer = [CADisplayLink displayLinkWithTarget:self selector:@selector(animtedScroll)]; | ||
[self.timer addToRunLoop:[NSRunLoop mainRunLoop] forMode:NSRunLoopCommonModes]; | ||
} | ||
} | ||
|
||
- (void)animtedScroll { | ||
|
||
if (!self.timer) return; | ||
if (!self.scrollView) return; | ||
|
||
self.runTime += self.timer.duration; | ||
|
||
if (self.runTime >= self.duration) { | ||
[self.scrollView setContentOffset:self.destinationOffset animated:NO]; | ||
[self.timer invalidate]; | ||
self.timer = nil; | ||
self.block(); | ||
return; | ||
} | ||
|
||
CGPoint offset = self.scrollView.contentOffset; | ||
offset.x = [self.timingFunction compute:self.runTime b:self.startOffset.x c:self.destinationOffset.x - self.startOffset.x d:self.duration]; | ||
offset.y = [self.timingFunction compute:self.runTime b:self.startOffset.y c:self.destinationOffset.y - self.startOffset.y d:self.duration]; | ||
[self.scrollView setContentOffset:offset animated:NO]; | ||
} | ||
|
||
@end |