iOS 實現在 UITableView 頂部加入視差圖片的效果

視差效果是我們在許多 app 中經常能夠看到的一種界面視覺效果。尤其是在滾動列表中應用得尤為廣泛。

我們首先來看看最終實現的效果:

正常狀態
向上滑動一段距離
向下滑動越界放大

整個效果實現的要點總結如下:

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

首先我們準備工程,在所需的 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 的一個內建屬性,它用來設置是否讓內部的 UITableView 在頂部留出一定空間來防止被導航條覆蓋,因為我們要手動調節 inset,所以把這個屬性就設置為NO

然后我們用setContentInset為 UITableView 設置內部間距,這里我們假定圖片最大高度為300。

下面是對 UIImageView 進行設置,我們設置了它的錨點,這里作用是什么我們后面會講到。

最后,需要注意的是subView的添加順序,要先添加圖片,再添加列表,因為圖片要被蓋住。為了避免圖片被列表完全蓋住,我們要把列表的背景設為透明,然后通過列表中的 cell 來蓋住圖片。


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

- (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;
    }
}

這段代碼我不全部解釋,絕大部分大家應該能夠自己看懂。
首先我們要用 valueForKey 得到 Apple 沒有對外公開的一個屬性叫做contentOffset,它是用來表示列表滑動距離最頂部的距離的,因為我們設置了內補,所以這個值會從-300開始計算。

如果列表下拉越界,那么這個值將會比-300還要小,因此我們可以依次判斷列表是否越界,一旦越界,那么這個值得絕對值就會是圖片應當拉伸到的高度。因為要等比縮放,所以我們計算縮放因子,然后交給 transform 來縮放圖片,而不是直接設置圖片的 frame。

這里就要提到之前我們設置的 anchorPoint 了,為什么要設置它呢?因為默認情況下 transform 的中心點在整個 UIView 的中心位置,這樣圖片縮放的時候就會以圖片的中心進行縮放了。為了實現預想的效果,我們就要把anchorPoint設置為圖片的中上位置

anchorPoint

但是,這樣設置之后,圖片就會下降150像素,所以我們把 frame 的 y 設置為-150。

至于其他部分,我們還設置了 UINavigationBar 的透明度和圖片的透明度。


至此我們就基本實現了視差效果的邏輯和計算部分,但是這個makeParallaxEffect函數應該什么時候被調用呢?這里我們就要利用到 Runtime 的一個重要特性 —— KVO。即當contentOffset發生變化時執行一個回調,這樣我們就可以實時地計算視差效果了。

那這個 KVO 在哪里添加呢?我之前添加在了viewDidLoad中,但是發現當 ViewController被 pop 后 app 會 crash。最后我在viewWillDisappear中 remove 掉了這個 KVO,問題得到解決。

但是問題又來了,我們知道,iOS 7之后用戶可以通過邊緣滑動的方式來返回上一級頁面,但如果我們向右滑動的距離不足以讓頁面返回,那么viewWillDisappear也會被調用,viewWillAppear也會被調用。所以我們索性就把 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 的透明度設置回來。

好了,至此我們就實現了這樣一個簡單的視差效果。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容

  • 發現 關注 消息 iOS 第三方庫、插件、知名博客總結 作者大灰狼的小綿羊哥哥關注 2017.06.26 09:4...
    肇東周閱讀 12,180評論 4 61
  • 天亮了嗎? 它是無光的, 轟亂的城中從未有雞鳴狗吠的寧靜。 天黑了嗎? 它一直是陰冷的, 沒有落幕,未知夕陽余嘆。...
    Gavin_keen閱讀 193評論 0 1
  • 早上6點40起床,跑步4000步。8點送小兒子陳志邦上學,洗完衣服后8點50到公司。上午10點和胡會計對賬,11點...
    31c47a10aded閱讀 200評論 0 0
  • 永遠不要把別人曾掏心掏肺告知你的隱私或傷痛當成嘲諷他攻擊他的利劍,這樣做不僅給他帶來二次創傷,也等于踐踏了他人對你...
    Looloo閱讀 375評論 0 1
  • “她想用一種放蕩的形象刺痛他,可是他太了解她了。這讓她感到難堪,又有點溫暖。
    林景熙閱讀 277評論 0 0