iOS 實(shí)現(xiàn)在 UITableView 頂部加入視差圖片的效果

視差效果是我們?cè)谠S多 app 中經(jīng)常能夠看到的一種界面視覺效果钻洒。尤其是在滾動(dòng)列表中應(yīng)用得尤為廣泛。

我們首先來看看最終實(shí)現(xiàn)的效果:

正常狀態(tài)
向上滑動(dòng)一段距離
向下滑動(dòng)越界放大

整個(gè)效果實(shí)現(xiàn)的要點(diǎn)總結(jié)如下:

  • 圖片退出速度慢于列表滑動(dòng)速度
  • 圖片全程被列表覆蓋并且在退出同時(shí)淡出
  • 列表下拉越界后圖片按比例放大

首先我們準(zhǔn)備工程廊谓,在所需的 ViewController 中分別加入 UITableView 和 UIImageView:

- (void)viewDidLoad {
    [super viewDidLoad];
    
    self.automaticallyAdjustsScrollViewInsets = NO;
            
    self.tableView = [[UITableView alloc] initWithFrame:self.view.bounds];
    [self.tableView setBackgroundColor:[UIColor colorWithWhite:1 alpha:0]];
    [self.tableView setContentInset:UIEdgeInsetsMake(300, 0, 0, 0)];
    [self.tableView setDelegate:self];
    [self.tableView setDataSource:self];
    
    self.imageView = [[UIImageView alloc] initWithFrame:CGRectMake(0, -150, self.view.bounds.size.width, 300)];
    self.imageView.layer.anchorPoint = CGPointMake(0.5f, 0);
    
    [self.view addSubview:self.imageView];
    [self.view addSubview:self.tableView];
}

下面我們一一分析,首先automaticallyAdjustsScrollViewInsets是 UIViewController 的一個(gè)內(nèi)建屬性麻削,它用來設(shè)置是否讓內(nèi)部的 UITableView 在頂部留出一定空間來防止被導(dǎo)航條覆蓋蒸痹,因?yàn)槲覀円謩?dòng)調(diào)節(jié) inset,所以把這個(gè)屬性就設(shè)置為NO呛哟。

然后我們用setContentInset為 UITableView 設(shè)置內(nèi)部間距叠荠,這里我們假定圖片最大高度為300。

下面是對(duì) UIImageView 進(jìn)行設(shè)置扫责,我們?cè)O(shè)置了它的錨點(diǎn)榛鼎,這里作用是什么我們后面會(huì)講到。

最后,需要注意的是subView的添加順序者娱,要先添加圖片蜘渣,再添加列表,因?yàn)閳D片要被蓋住肺然。為了避免圖片被列表完全蓋住,我們要把列表的背景設(shè)為透明腿准,然后通過列表中的 cell 來蓋住圖片际起。


接下來我們來編寫視差效果的核心部分,計(jì)算圖片位移和縮放:

- (void)makeParallaxEffect {
    CGPoint point = [((NSValue *) [self.tableView valueForKey:@"contentOffset"]) CGPointValue];
    if (point.y < -300) {
        float scaleFactor = fabs(point.y) / 300.f;
        self.imageView.transform = CGAffineTransformMakeScale(scaleFactor, scaleFactor);
    } else {
        self.imageView.transform = CGAffineTransformMakeScale(1, 1);
    }
    
    if (point.y <= 0) {
        if (point.y >= -300) {
            self.imageView.transform = CGAffineTransformTranslate(self.imageView.transform, 0, (fabs(point.y) - 300) / 2.f);
        }
        self.imageView.alpha = fabs(point.y / 300.f);
        self.navigationController.navigationBar.alpha = 1 - powf(fabs(point.y / 300.f), 3);
    } else {
        self.imageView.transform = CGAffineTransformTranslate(self.imageView.transform, 0, 0);
        self.imageView.alpha = 0;
        self.navigationController.navigationBar.alpha = 1;
    }
}

這段代碼我不全部解釋吐葱,絕大部分大家應(yīng)該能夠自己看懂街望。
首先我們要用 valueForKey 得到 Apple 沒有對(duì)外公開的一個(gè)屬性叫做contentOffset,它是用來表示列表滑動(dòng)距離最頂部的距離的弟跑,因?yàn)槲覀冊(cè)O(shè)置了內(nèi)補(bǔ)灾前,所以這個(gè)值會(huì)從-300開始計(jì)算。

如果列表下拉越界孟辑,那么這個(gè)值將會(huì)比-300還要小哎甲,因此我們可以依次判斷列表是否越界,一旦越界饲嗽,那么這個(gè)值得絕對(duì)值就會(huì)是圖片應(yīng)當(dāng)拉伸到的高度炭玫。因?yàn)橐缺瓤s放,所以我們計(jì)算縮放因子貌虾,然后交給 transform 來縮放圖片吞加,而不是直接設(shè)置圖片的 frame。

這里就要提到之前我們?cè)O(shè)置的 anchorPoint 了尽狠,為什么要設(shè)置它呢衔憨?因?yàn)槟J(rèn)情況下 transform 的中心點(diǎn)在整個(gè) UIView 的中心位置,這樣圖片縮放的時(shí)候就會(huì)以圖片的中心進(jìn)行縮放了袄膏。為了實(shí)現(xiàn)預(yù)想的效果践图,我們就要把anchorPoint設(shè)置為圖片的中上位置

anchorPoint

但是,這樣設(shè)置之后哩陕,圖片就會(huì)下降150像素平项,所以我們把 frame 的 y 設(shè)置為-150。

至于其他部分悍及,我們還設(shè)置了 UINavigationBar 的透明度和圖片的透明度闽瓢。


至此我們就基本實(shí)現(xiàn)了視差效果的邏輯和計(jì)算部分,但是這個(gè)makeParallaxEffect函數(shù)應(yīng)該什么時(shí)候被調(diào)用呢心赶?這里我們就要利用到 Runtime 的一個(gè)重要特性 —— KVO扣讼。即當(dāng)contentOffset發(fā)生變化時(shí)執(zhí)行一個(gè)回調(diào),這樣我們就可以實(shí)時(shí)地計(jì)算視差效果了缨叫。

那這個(gè) KVO 在哪里添加呢椭符?我之前添加在了viewDidLoad中荔燎,但是發(fā)現(xiàn)當(dāng) ViewController被 pop 后 app 會(huì) crash。最后我在viewWillDisappear中 remove 掉了這個(gè) KVO销钝,問題得到解決有咨。

但是問題又來了,我們知道蒸健,iOS 7之后用戶可以通過邊緣滑動(dòng)的方式來返回上一級(jí)頁面座享,但如果我們向右滑動(dòng)的距離不足以讓頁面返回,那么viewWillDisappear也會(huì)被調(diào)用似忧,viewWillAppear也會(huì)被調(diào)用渣叛。所以我們索性就把 KVO 添加在viewWillAppear中。

下面看最終的代碼:

- (void)viewWillAppear:(BOOL)animated {
    [self.tableView addObserver:self forKeyPath:@"contentOffset" options:NSKeyValueObservingOptionNew context:nil];
    
    [UIView beginAnimations:nil context:nil];
    [self makeParallaxEffect];
    [UIView commitAnimations];
}

- (void)viewWillDisappear:(BOOL)animated {
    [UIView beginAnimations:nil context:nil];
    self.navigationController.navigationBar.alpha = 1;
    [UIView commitAnimations];
    
    [self.tableView removeObserver:self forKeyPath:@"contentOffset"];
}

- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSString *,id> *)change context:(void *)context {
    if (object == self.tableView) {
        [self makeParallaxEffect];
    }
}

最后不要忘記在viewWillDisappear中把 UINavigationBar 的透明度設(shè)置回來盯捌。

好了淳衙,至此我們就實(shí)現(xiàn)了這樣一個(gè)簡(jiǎn)單的視差效果。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末饺著,一起剝皮案震驚了整個(gè)濱河市箫攀,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌幼衰,老刑警劉巖匠童,帶你破解...
    沈念sama閱讀 211,042評(píng)論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異塑顺,居然都是意外死亡汤求,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 89,996評(píng)論 2 384
  • 文/潘曉璐 我一進(jìn)店門严拒,熙熙樓的掌柜王于貴愁眉苦臉地迎上來扬绪,“玉大人,你說我怎么就攤上這事裤唠〖放#” “怎么了?”我有些...
    開封第一講書人閱讀 156,674評(píng)論 0 345
  • 文/不壞的土叔 我叫張陵种蘸,是天一觀的道長(zhǎng)墓赴。 經(jīng)常有香客問我,道長(zhǎng)航瞭,這世上最難降的妖魔是什么诫硕? 我笑而不...
    開封第一講書人閱讀 56,340評(píng)論 1 283
  • 正文 為了忘掉前任,我火速辦了婚禮刊侯,結(jié)果婚禮上章办,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好藕届,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,404評(píng)論 5 384
  • 文/花漫 我一把揭開白布挪蹭。 她就那樣靜靜地躺著,像睡著了一般休偶。 火紅的嫁衣襯著肌膚如雪梁厉。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,749評(píng)論 1 289
  • 那天踏兜,我揣著相機(jī)與錄音懂算,去河邊找鬼。 笑死庇麦,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的喜德。 我是一名探鬼主播山橄,決...
    沈念sama閱讀 38,902評(píng)論 3 405
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼舍悯!你這毒婦竟也來了航棱?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,662評(píng)論 0 266
  • 序言:老撾萬榮一對(duì)情侶失蹤萌衬,失蹤者是張志新(化名)和其女友劉穎饮醇,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體秕豫,經(jīng)...
    沈念sama閱讀 44,110評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡朴艰,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,451評(píng)論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了混移。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片祠墅。...
    茶點(diǎn)故事閱讀 38,577評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖歌径,靈堂內(nèi)的尸體忽然破棺而出毁嗦,到底是詐尸還是另有隱情,我是刑警寧澤回铛,帶...
    沈念sama閱讀 34,258評(píng)論 4 328
  • 正文 年R本政府宣布狗准,位于F島的核電站,受9級(jí)特大地震影響茵肃,放射性物質(zhì)發(fā)生泄漏腔长。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,848評(píng)論 3 312
  • 文/蒙蒙 一验残、第九天 我趴在偏房一處隱蔽的房頂上張望饼酿。 院中可真熱鬧,春花似錦、人聲如沸故俐。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,726評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽药版。三九已至辑舷,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間槽片,已是汗流浹背何缓。 一陣腳步聲響...
    開封第一講書人閱讀 31,952評(píng)論 1 264
  • 我被黑心中介騙來泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留还栓,地道東北人碌廓。 一個(gè)月前我還...
    沈念sama閱讀 46,271評(píng)論 2 360
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像剩盒,于是被迫代替她去往敵國(guó)和親谷婆。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,452評(píng)論 2 348

推薦閱讀更多精彩內(nèi)容

  • 發(fā)現(xiàn) 關(guān)注 消息 iOS 第三方庫辽聊、插件纪挎、知名博客總結(jié) 作者大灰狼的小綿羊哥哥關(guān)注 2017.06.26 09:4...
    肇東周閱讀 12,058評(píng)論 4 62
  • 天亮了嗎? 它是無光的, 轟亂的城中從未有雞鳴狗吠的寧靜跟匆。 天黑了嗎? 它一直是陰冷的异袄, 沒有落幕,未知夕陽余嘆玛臂。...
    Gavin_keen閱讀 186評(píng)論 0 1
  • 早上6點(diǎn)40起床烤蜕,跑步4000步。8點(diǎn)送小兒子陳志邦上學(xué)迹冤,洗完衣服后8點(diǎn)50到公司玖绿。上午10點(diǎn)和胡會(huì)計(jì)對(duì)賬,11點(diǎn)...
    31c47a10aded閱讀 191評(píng)論 0 0
  • 永遠(yuǎn)不要把別人曾掏心掏肺告知你的隱私或傷痛當(dāng)成嘲諷他攻擊他的利劍叁巨,這樣做不僅給他帶來二次創(chuàng)傷斑匪,也等于踐踏了他人對(duì)你...
    Looloo閱讀 357評(píng)論 0 1
  • “她想用一種放蕩的形象刺痛他,可是他太了解她了锋勺。這讓她感到難堪蚀瘸,又有點(diǎn)溫暖。
    林景熙閱讀 265評(píng)論 0 0