首先欣賞下demo的效果圖
大家可以到我的github下載代碼,自己跑一下效果會(huì)更好喲(__)

我們追求的視差效果
- 當(dāng)我們的cell滾動(dòng)到屏幕中間的時(shí)候,所展示的圖片也應(yīng)該剛好處于中心
- 當(dāng)一個(gè)cell從底部滾動(dòng)到頂部的過(guò)程,cell中的圖片位置應(yīng)該從偏高滾動(dòng)偏低(偏高、偏低是針對(duì)圖片自己來(lái)說(shuō)的)
如何去實(shí)現(xiàn)
既然我們要隨著tableView的滾動(dòng)而更新cell中圖片的位置,必然會(huì)使用到tableView的contentOffset,然后為了性能著想,我們更新圖片位置的cell不應(yīng)該是全部,而應(yīng)該是當(dāng)前屏幕中的cell,所以又想到了tableView的visibleCells屬性
Just do it!!!
為了方便移植使用,我創(chuàng)建了基類(lèi)cell ParallaxCell,然后新建一個(gè)cell繼承于它就可以使用視差化子視圖的特性了;
首先看一下ParallaxCell.h文件
//初始化cell的時(shí)候記得先用注冊(cè)喲?。?!且僅針對(duì)cell的高度是一樣的cell
@interface ParallaxCell : UITableViewCell
/**
* 重置視差化狀態(tài),因?yàn)閏ell的重用機(jī)制,重用之后要刷新?tīng)顟B(tài)
*/
- (void)resetParallaxState;
/**
* 設(shè)置可視差化的子視圖,以及centerY上偏移的值和centerY下偏移的值
*
* @param view 允許被可視差化的視圖
* @param minNum centerY上偏移的值
* @param maxNum centerY下偏移的值
*/
- (void)parallaxWithView:(UIView *)view offsetUp:(CGFloat)offsetUp offsetDown:(CGFloat)offsetDown;
/**
* 通過(guò)傳入scrollview就更可以根據(jù)滾動(dòng)的情況,更新之前需要視差化的視圖的位置
*
* @param scrollView scrollView
*/
- (void)updateViewFrameWithScrollView:(UIScrollView *)scrollView;
再看一下ParallaxCell.m文件
static NSString *const kParallaxView = @"kParallaxView";
static NSString *const kParallaxOriginalCenterY = @"kParallaxOriginalCenterY";
static NSString *const kParallaxOffsetUp = @"kParallaxOffsetUp";
static NSString *const kParallaxOffsetDown = @"kParallaxOffsetDown";
@interface ParallaxCell ()
@property (nonatomic, strong) NSMutableArray *originalCenterYArray;//用于記錄視差化視圖的原始中心Y值
@property (nonatomic, strong) NSMutableArray *parallaxViewArray;//記錄視差化的視圖
@property (nonatomic, assign) BOOL hasInited;//為了應(yīng)付重用,用于只記錄alloc出來(lái)的,重用出來(lái)的需要重置狀態(tài)
@end
@implementation ParallaxCell
- (void)resetParallaxState
{
self.parallaxViewArray = [NSMutableArray array];
}
- (void)parallaxWithView:(UIView *)view offsetUp:(CGFloat)offsetUp offsetDown:(CGFloat)offsetDown
{
if (!self.hasInited) {
if (!self.originalCenterYArray) {
self.originalCenterYArray = [NSMutableArray array];
}
[self.originalCenterYArray addObject:@(view.center.y)];
}
NSDictionary *dict = @{kParallaxView : view,
kParallaxOriginalCenterY : self.originalCenterYArray[self.parallaxViewArray.count],
kParallaxOffsetUp : @(offsetUp),
kParallaxOffsetDown : @(offsetDown)};
[self.parallaxViewArray addObject:dict];
}
- (void)updateViewFrameWithScrollView:(UIScrollView *)scrollView
{
self.hasInited = YES;
//(cell的origin.y 加上 一個(gè)cell的高度 減去 當(dāng)前滾動(dòng)的偏移Y值 )除以 (屏幕的高度 加上 一個(gè)cell的高度)這個(gè)下面有畫(huà)圖解釋
CGFloat percent = (self.frame.origin.y + self.frame.size.height - scrollView.contentOffset.y)/([UIScreen mainScreen].bounds.size.height+self.frame.size.height);
[self updateViewFrameWithPercent:percent];
}
- (void)updateViewFrameWithPercent:(CGFloat)percent
{
for (NSInteger index = 0; index < self.parallaxViewArray.count; index ++) {
NSDictionary *dataDict = self.parallaxViewArray[index];
UIView *view = dataDict[kParallaxView];
CGFloat originalCenterY = [dataDict[kParallaxOriginalCenterY] floatValue];
CGFloat offsetUp = [dataDict[kParallaxOffsetUp] floatValue];
CGFloat offsetDown = [dataDict[kParallaxOffsetDown] floatValue];
view.center = CGPointMake(view.center.x, [self interpolateFrom:originalCenterY - offsetUp to:originalCenterY + offsetDown percent:percent]);
}
}
/**
* 插值計(jì)算(線性),設(shè)置一個(gè)值的起始值與結(jié)束值,然后根據(jù)傳入的百分比返回當(dāng)前對(duì)應(yīng)的值
*
* @param from 起始值
* @param to 結(jié)束值
* @param percent 百分比
*
* @return 當(dāng)前值
*/
- (CGFloat)interpolateFrom:(CGFloat)from to:(CGFloat)to percent:(CGFloat)percent
{
if (percent > 1) {
return to;
}
if (percent < 0) {
return from;
}
return (to - from)*percent + from;
}
@end
最需要注意的地方:
- 就是cell 的重用機(jī)制,當(dāng)cell從重用隊(duì)列取出cell的時(shí)候,這個(gè)時(shí)候它的狀態(tài)還是之前cell的狀態(tài),比如我們之前對(duì)它圖片的位置進(jìn)行的修改也保留了下來(lái),如果我們重用之后不進(jìn)行重置,就會(huì)以之前的狀態(tài)進(jìn)行處理,這當(dāng)然不是我們想要的;
- 然后又因?yàn)槲沂褂?code>- (void)parallaxWithView:(UIView *)view offsetUp:(CGFloat)offsetUp offsetDown:為需要視差化的控件,逐一添加,而不是一次性添加所有需要視差化的控件,所以?xún)?nèi)部不好判斷時(shí)機(jī)去重置cell的狀態(tài),所以我就添加了一個(gè)
- (void)resetParallaxState方法去重置; - 然后就是percent的計(jì)算原理,如下圖所示:

cell的位置相對(duì)于視差化范圍height的percent ,和cell中視差化視圖的位置相對(duì)于移動(dòng)范圍的percent是相同的。比如說(shuō)最上面即將離開(kāi)屏幕cell在視差化范圍的percent接近于0,那么cell中的圖片也就處于可移動(dòng)范圍中最偏下的位置;同理,最下面即將進(jìn)入屏幕cell在視差化范圍percent接近于1,那么cell中圖片也就處于可移動(dòng)范圍中最偏上的位置。
自定義cell的使用
自定義一個(gè)cell繼承于ParallaxCell,并且把需要可視差化的控件暴露在.h文件,這里代碼需要注意布局,如果用masonry代碼約束,或者xib約束,可能出現(xiàn)異常;然后就是cell的真實(shí)高度和寬度獲取,在初始化的時(shí)候是獲取不到的,所以布局的時(shí)候使用[UIScreen mainScreen].bounds.size.width來(lái)獲取寬度,高度就類(lèi)方法+ (CGFloat)getHeight來(lái)獲?。黄鋵?shí)我想說(shuō)的是,如果你用ParallaxCell來(lái)實(shí)現(xiàn)視差化,如果出現(xiàn)異常,麻煩看看自定義cell的布局;
ViewController中的使用
-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
CustomScrollCell *cell = [tableView dequeueReusableCellWithIdentifier:@"CustomScrollCell" forIndexPath:indexPath];
[cell resetParallaxState];
cell.headerImageView.image = [UIImage imageNamed:[self.imageNameArray objectAtIndex:indexPath.row]];
[cell parallaxWithView:cell.headerImageView offsetUp:50 offsetDown:50];
[cell parallaxWithView:cell.nameLabel offsetUp:10 offsetDown:10];
[cell updateViewFrameWithScrollView:tableView];
return cell;
}
- (void)scrollViewDidScroll:(UIScrollView *)scrollView
{
for (CustomScrollCell *cell in self.tableView.visibleCells) {
[cell updateViewFrameWithScrollView:scrollView];
}
}
注意調(diào)用方法順序:
- 第一要先調(diào)用
[cell resetParallaxState]; - 第二調(diào)用
[cell parallaxWithView:cell.headerImageView offsetUp:50 offsetDown:50]; - 第三調(diào)用
[cell updateViewFrameWithScrollView:tableView]; - 在
cellForRow和scrollViewDidScroll方法里面都要調(diào)用[cell updateViewFrameWithScrollView:scrollView];
寫(xiě)在最后
自己參考過(guò)兩個(gè)demo,代碼質(zhì)量與實(shí)現(xiàn)方法有高低之分;而且別人的代碼總有取巧的方法,如果你不能完全明白作者的意圖,可能會(huì)思考一段時(shí)間才會(huì)想通;所以你覺(jué)得不錯(cuò)的,多花點(diǎn)時(shí)間耐心的看下去總會(huì)懂的;
第一個(gè)參考DiceTableViewCell;這個(gè)有點(diǎn)low,不過(guò)至少實(shí)現(xiàn)了一些基本效果但是問(wèn)題很多,可以喵一眼;
第二個(gè)參考MJParallaxCollectionView;我的思路大體和它相同,其中有一些取巧的地方需要自己甄別一下;但是我覺(jué)得它沒(méi)有進(jìn)行封裝,所以進(jìn)行了大量?jī)?yōu)化;
最最后,如果描述中出現(xiàn)錯(cuò)誤,抑或?qū)崿F(xiàn)思路有問(wèn)題,麻煩各位大神多多指正?。。?/p>