使用UICollectionView+CATransform3D實現3D滑動效果

首先來看一下效果圖:

效果圖.gif

看完效果圖覺得還不錯那就繼續往下看哦??

實現思路

拋開滑動的3D效果,我們先來實現普通的分頁滑動效果。

要做這種卡片式的分頁滑動,最先想到的應該就是用UICollectionView來實現了。但是UICollectionView的分頁只能相對于本身的frame來做分頁,要根據cell 的大小來做分頁就需要來自定義分頁了。

現在來解決第一個問題:自定義分頁。
自定義分類想到兩種方式:

  • 1 、一種是開啟UICollectionView的分頁,把UICollectionView的大小設置成分頁的一個頁面大小,然后再把clipsToBounds設置成NO,這樣就可以使用自帶的分頁效果,并且被遮擋的部分也能顯示出來了,但是這樣 在當前頁的邊上部分是不能接受事件的,所以還需要自己處理事件接收。
  • 2 、實現UIScrollView的協議方法,監聽滑動結束,計算出滑動到的當前頁位置,然后自定義結束位置,將得到的當前頁面居中。

第二種方法只需要在滑動結束后,計算需要居中顯示的頁面,使用起來相對更加靈活。所以使用第二種來實現?

首先新建一個UICollectionView的子類,定義好需要使用的屬性。

@interface DCollectionView : UICollectionView
@property (nonatomic, retain) NSArray *imgDatas;
@property (nonatomic, assign) NSInteger currentIndex;
@end

.m中實現協議方法

- (instancetype)initWithFrame:(CGRect)frame {
    _layout = [[LayoutView alloc]init];
    _layout.scrollDirection = UICollectionViewScrollDirectionHorizontal;
    
    self = [super initWithFrame:frame collectionViewLayout:_layout];
    if(self){
        [self _initView];
    }
    return self;
}
- (void)_initView {
    _curIndex = 1;
    self.delegate = self;
    self.dataSource = self;
    self.pagingEnabled = NO;
 
    [self registerClass:[UICollectionViewCell class] forCellWithReuseIdentifier:@"UICollectionViewCell"];
}

#pragma mark collection協議
- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section {
    return _imgDatas.count;
}

- (__kindof UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath {
    UICollectionViewCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:@"UICollectionViewCell" forIndexPath:indexPath];
    
    cell.backgroundView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:_imgDatas[indexPath.row]]];
    cell.layer.masksToBounds = YES;
    cell.layer.cornerRadius = 8;
    return cell;
}

// 設置每個塊的寬、高
- (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout*)collectionViewLayout sizeForItemAtIndexPath:(NSIndexPath *)indexPath {
    return CGSizeMake(itemWidth, itemHight);
}

// 四個邊緣的間隙
- (UIEdgeInsets)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout insetForSectionAtIndex:(NSInteger)section{
    return UIEdgeInsetsMake(0, 5, 0, 5);
}

UICollectionView繼承自UIScrollView,在UIScrollViewDelegate的協議方法scrollViewDidEndDragging中,根據滑動減速停止時的位置計算當前需要居中的頁面。

- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView {
    NSInteger scrIndex = scrollView.contentOffset.x/(itemWidth + 10);
    
    [self scrollToItemAtIndexPath:[NSIndexPath indexPathForRow:scrIndex + 1 inSection:0] atScrollPosition:UICollectionViewScrollPositionCenteredHorizontally animated:YES];
    
    _curIndex = scrIndex + 1;
}

這樣在滑動完成后繼續滑動到計算出的cell頁面,但這樣只實現了在當手離開屏幕時,頁面還在滑動時的效果,還需要在scrollViewDidEndDragging中計算出居中的頁面。

- (void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate {
    NSInteger scrIndex = scrollView.contentOffset.x/(itemWidth + 10);
    // 當停止拖拽時,view不在滑動時才執行,還在滑動則調用scrollViewDidEndDecelerating
    if(!decelerate){
        [self scrollToItemAtIndexPath:[NSIndexPath indexPathForRow:scrIndex + 1 inSection:0] atScrollPosition:UICollectionViewScrollPositionCenteredHorizontally animated:YES];
        _curIndex = scrIndex + 1;
    }
}

最后再給點擊cell,將點擊的cell滑動居中即可:

- (void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(NSIndexPath *)indexPath {
    [self scrollToItemAtIndexPath:indexPath atScrollPosition:UICollectionViewScrollPositionCenteredHorizontally animated:YES];
    _curIndex = indexPath.row;
}

自定義分頁實現完成,下面在滑動中使用<code>CATransform3D</code>加上3D效果

在上面UICollectionView的初始化方法中可以看到,collectionViewLayout是使用的自定義UICollectionViewFlowLayout。
下面就在子類化UICollectionViewFlowLayout中來實現3D滑動的效果。

主要在layoutAttributesForElementsInRect協議方法中,給視圖元素利用CATransform3DMakeScale加上效果。那這個 layoutAttributesForElementsInRect協議方法是做什么的呢?

- (nullable NSArray<__kindof UICollectionViewLayoutAttributes *> *)layoutAttributesForElementsInRect:(CGRect)rect;

這個方法返回一個數組,是返回存放在CGRect范圍內所有視圖的布局屬性的一個數組。下面實現方法:

// 返回存放在CGRect范圍內所有視圖的布局屬性的一個數組
- (nullable NSArray<__kindof UICollectionViewLayoutAttributes *> *)layoutAttributesForElementsInRect:(CGRect)rect {
    
    NSArray *attArray = [super layoutAttributesForElementsInRect:rect];
    CGRect visRect;
    visRect.origin = self.collectionView.contentOffset;
    visRect.size = self.collectionView.bounds.size;
    
    for (UICollectionViewLayoutAttributes *layoutAttribute in attArray) {
        CGFloat distance = CGRectGetMidX(visRect) - layoutAttribute.center.x;
        CGFloat norDistance = fabs(distance/ActiveDistance);
        CGFloat zoom = 1- ScaleFactor * norDistance;
        layoutAttribute.transform3D = CATransform3DMakeScale(zoom, zoom, 1.0);
        layoutAttribute.zIndex = 1;
    }
    
    return attArray;
}

完成上面那個方法后,會發現3D效果是有了,但是滑動時并沒有實時刷新layout,還需要實現shouldInvalidateLayoutForBoundsChange這個協議:

- (BOOL)shouldInvalidateLayoutForBoundsChange:(CGRect)newBounds {
    return YES;
}

到此效果圖上面的效果已經實現了。最后給上demo

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

推薦閱讀更多精彩內容

  • 發現 關注 消息 iOS 第三方庫、插件、知名博客總結 作者大灰狼的小綿羊哥哥關注 2017.06.26 09:4...
    肇東周閱讀 12,200評論 4 61
  • 1. 那一年去那老城車站,我賭氣離開,因為H先生總說著那些不可能與可能,從來沒有一個肯定的答案。 而我總是有所期...
    阿里與阿卡閱讀 562評論 0 0
  • 網絡小說這種東西直指人心最直接的欲望 不用負責的性 他人的關注 廉價的自我滿足
    defineaset閱讀 158評論 0 1
  • 多日的暴雨,多地被洪水圍困,十九年前的今天,我們出發! 那一年,24歲,奔赴當時洪災最嚴重的安鄉,八點,在...
    0f48f46d2bd3閱讀 253評論 0 1
  • 一大早女兒就開始貼春聯,這是女兒家新房的第一個春節,我寫的。 女兒做事很細心,我只好站旁邊遞透明膠帶。 喜歡趁她不...
    惠兒和好時光閱讀 336評論 0 0