【項(xiàng)目Github地址】
先展示一張效果圖:
從圖片上可以看出都是彈簧效果忧勿,自然而然就會(huì)想到用系統(tǒng)的UIScrollView
類來實(shí)現(xiàn)。
接下來我將仔細(xì)的一步步的教你完成這個(gè)項(xiàng)目孕暇。
首先咱們先創(chuàng)建一個(gè)工程蜓氨,打開ViewController.m
文件,我們先簡單創(chuàng)建一個(gè)UIScrollView突倍,讓他的contentSize為兩倍的屏幕寬度腔稀,再加入兩張圖片。
代碼如下:
#import "ViewController.h"
#define kScreenWidth [UIScreen mainScreen].bounds.size.width
#define kScreenHeight [UIScreen mainScreen].bounds.size.height
#define kLeftRightMargin 8
#define kBottomMargin 16
#define kImageViewWidth (kScreenWidth - kLeftRightMargin*2)
#define kImageViewHeight (kImageViewWidth*392/344.5)
#define kImageViewY (kScreenHeight - kBottomMargin - kImageViewHeight)
@interface ViewController () <UIScrollViewDelegate>
@property (nonatomic, strong) UIScrollView *scrollView;
@property (nonatomic, strong) UIImageView *leftView;
@property (nonatomic, strong) UIImageView *rightView;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
self.view.backgroundColor = [UIColor lightGrayColor];
self.scrollView = [[UIScrollView alloc] initWithFrame:self.view.bounds];
self.scrollView.delegate = self;
self.scrollView.contentSize = CGSizeMake(kScreenWidth*2, kScreenHeight);
self.scrollView.pagingEnabled = YES;
self.scrollView.showsHorizontalScrollIndicator = NO;
self.scrollView.showsVerticalScrollIndicator = NO;
[self.view addSubview:self.scrollView];
self.leftView = [[UIImageView alloc] initWithFrame:CGRectMake(kLeftRightMargin, kImageViewY, kImageViewWidth, kImageViewHeight)];
self.leftView.backgroundColor = [UIColor yellowColor];
self.leftView.layer.cornerRadius = 15;
self.leftView.layer.masksToBounds = YES;
[self.scrollView addSubview:self.leftView];
self.rightView = [[UIImageView alloc] initWithFrame:CGRectMake(kLeftRightMargin+kScreenWidth, kImageViewY, kImageViewWidth, kImageViewHeight)];
self.rightView.backgroundColor = [UIColor greenColor];
self.rightView.layer.cornerRadius = 15;
self.rightView.layer.masksToBounds = YES;
[self.scrollView addSubview:self.rightView];
}
@end
運(yùn)行之后羽历,你將看到如下圖的效果:
但是你們也會(huì)發(fā)現(xiàn)有如下的反應(yīng)焊虏,向上向下并沒有彈性效果:
因?yàn)榇藭r(shí)scrollView
的高度和它contentSize
的高度是相同的,所以它在橫向上是可以滾動(dòng)的秕磷,而縱向上就不可以了诵闭。但是我們想要縱向也有彈性效果,腫么辦?如果你熟悉UIScrollView
疏尿,你就曉得有一個(gè)屬性可以讓它簡單實(shí)現(xiàn)了瘟芝。
咱們?cè)趧?chuàng)建scrollView
的地方加入一行代碼:
...
self.scrollView.alwaysBounceVertical = YES; // <--
[self.view addSubview:self.scrollView];
效果圖如下:
但此時(shí)可能你會(huì)發(fā)現(xiàn)另一個(gè)問題,此時(shí)的
scrollView
滾動(dòng)的方向沒有限制:
那咱們就去
UIScrollView
頭文件里去瞧瞧褥琐,說不定蘋果爸爸已經(jīng)給我們預(yù)設(shè)了解決辦法了也說不定模狭。很快你就會(huì)看到有這樣一個(gè)屬性定義
@property(nonatomic,getter=isDirectionalLockEnabled) BOOL directionalLockEnabled; // default NO. if YES, try to lock vertical or horizontal scrolling while dragging
那就用起來:
self.scrollView.alwaysBounceVertical = YES;
self.scrollView.directionalLockEnabled = YES; // <--
[self.view addSubview:self.scrollView];
突然很神奇般的,好像是有效了踩衩。至少我在iOS10.3的模擬器上是沒問題嚼鹉,但是如果你在真機(jī)上運(yùn)行的話,試試你就會(huì)發(fā)現(xiàn)驱富,當(dāng)你滑動(dòng)角度在45°左右的時(shí)候锚赤,前面設(shè)置的方向鎖屬性就不生效沒用了。
蘋果官方也承認(rèn)這個(gè)是個(gè)BUG了褐鸥。在stackoverflow上也有在討論這個(gè)問題线脚。
我在上面選擇了一個(gè)解決方式,實(shí)現(xiàn)UIScrollView
的delegate
叫榕,代碼如下:
@interface ViewController () <UIScrollViewDelegate>
...
@property (nonatomic, assign) CGPoint beginDragPoint;
@end
@implementation ViewController
...
#pragma mark - UIScrollViewDelegate
- (void)scrollViewWillBeginDragging:(UIScrollView *)scrollView {
_beginDragPoint = scrollView.contentOffset;
}
- (void)scrollViewDidScroll:(UIScrollView *)scrollView {
if (scrollView.contentOffset.x == _beginDragPoint.x) {
self.scrollView.contentOffset = CGPointMake(_beginDragPoint.x, (self.scrollView.contentOffset.y));
} else {
self.scrollView.contentOffset = CGPointMake((self.scrollView.contentOffset.x), _beginDragPoint.y);
}
}
@end
接下來說說另一個(gè)問題浑侥,手勢(shì)向下移動(dòng)過程中,手指移動(dòng)多少視圖就移動(dòng)多少晰绎,而我們寫的工程卻還是彈性的寓落。
我的思路就是,那么就讓scrollView
的contentSize
高度大于本身的高度吧荞下,那么也能手移動(dòng)多少伶选,他就移動(dòng)多少了。那高度設(shè)為多少合適呢尖昏?你可以先自己嘗試看看仰税,我最終的結(jié)果是兩倍的本身高度,感覺非常巧妙抽诉,最后視圖消失與否都通過pagingEnabled
已經(jīng)搞定了陨簇。
修改代碼如下:
// #define kImageViewY (kScreenHeight - kBottomMargin - kImageViewHeight)
#define kImageViewY (kScreenHeight*2 - kBottomMargin - kImageViewHeight)
self.scrollView.delegate = self;
// self.scrollView.contentSize = CGSizeMake(kScreenWidth*2, kScreenHeight);
self.scrollView.contentSize = CGSizeMake(kScreenWidth*2, kScreenHeight*2); // <--
self.scrollView.contentOffset = CGPointMake(0, kScreenHeight); // <--
self.scrollView.pagingEnabled = YES;
效果圖如下:
好了,基本大功告成了迹淌。但是還有一個(gè)致命的問題河绽,一般人找不到解決辦法,我也是不斷嘗試才發(fā)現(xiàn)原來還可以這么整巍沙。
問題就是葵姥,當(dāng)手指在非視圖區(qū)域移動(dòng)時(shí),視圖是不會(huì)動(dòng)不會(huì)有反應(yīng)的句携,一旦移動(dòng)到靠近視圖的時(shí)候榔幸,視圖才開始移動(dòng)的。如圖:
先分析一下,UIScrollView
的滾動(dòng)時(shí)因?yàn)閮?nèi)部封裝的pan手勢(shì)削咆,那我們可不可以拿到手勢(shì)調(diào)用的方法牍疏,重寫它,然后判斷如果手指沒有到達(dá)位置時(shí)拨齐,手勢(shì)的UIGestureRecognizerStateChanged
事件就不傳遞給父類鳞陨,父類就不能處理,那么視圖就不會(huì)移動(dòng)了瞻惋。
我們先來找找事件方法:
打斷點(diǎn)發(fā)現(xiàn)厦滤,手勢(shì)屬性里有個(gè)
SEL
方法handlePan:
,嗯歼狼,應(yīng)該就是它了掏导,創(chuàng)建一個(gè)UIScrollView
的子類重寫這個(gè)方法吧。
// 創(chuàng)建類
@interface LYScrollView : UIScrollView
@end
@implementation LYScrollView
- (void)handlePan:(UIPanGestureRecognizer *)pan {
NSLog(@"%s", __func__);
}
@end
.
.
#import "LYScrollView.h"
// 替換創(chuàng)建方法
// @property (nonatomic, strong) UIScrollView *scrollView;
@property (nonatomic, strong) LYScrollView *scrollView;
...
// self.scrollView = [[UIScrollView alloc] initWithFrame:self.view.bounds];
self.scrollView = [[LYScrollView alloc] initWithFrame:self.view.bounds];
很是令人欣慰啊羽峰,方法回調(diào)了趟咆。
可是問題又出現(xiàn)了,當(dāng)滿足條件的時(shí)候梅屉,我要把事件還給父類的值纱,可是編譯器通過不了:
(個(gè)人想的比較挫的解決方法啊,有好的方式麻煩告知下哈坯汤。)
再創(chuàng)建一個(gè)中間類虐唠,聲明一個(gè)
handlePan:
方法。玫霎。凿滤。
@interface MyScrollView : UIScrollView
- (void)handlePan:(UIPanGestureRecognizer *)pan;
@end
@interface LYScrollView : MyScrollView
@end
// 去除`.m`文件因?yàn)闆]有實(shí)現(xiàn)該方法而有的警告問題
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wincomplete-implementation"
@implementation MyScrollView
#pragma clang diagnostic pop
@end
最終方法實(shí)現(xiàn)如下:
@implementation LYScrollView {
BOOL _canMove;
}
- (void)handlePan:(UIPanGestureRecognizer *)pan {
CGPoint point = [pan locationInView:pan.view];
if (pan.state == UIGestureRecognizerStateBegan) {
[pan setTranslation:CGPointZero inView:pan.view];
[super handlePan:pan];
} else if (pan.state == UIGestureRecognizerStateChanged) {
// 一旦手指位置到達(dá)視圖時(shí)妈橄,則開始移動(dòng)
if (!_canMove && point.y > kImageViewY) {
_canMove = YES;
[pan setTranslation:CGPointZero inView:pan.view];
}
if (_canMove) {
[super handlePan:pan];
}
} else if (pan.state == UIGestureRecognizerStateEnded || pan.state == UIGestureRecognizerStateCancelled) {
_canMove = NO;
[pan setTranslation:CGPointZero inView:pan.view];
[super handlePan:pan];
}
}
@end
這邊需要[pan setTranslation:CGPointZero inView:pan.view];
調(diào)用一下庶近,將手勢(shì)的位置初始化為零后,再傳給父類眷蚓,不然就位置突變了鼻种。
大問題都解決完啦,剩下的背景色漸變沙热、出現(xiàn)消失叉钥、再封裝等實(shí)現(xiàn)就不在這里贅述了,可去看下工程代碼篙贸。