天天看點

UICollectionView 橫向滑動停止的兩種效果。

UICollectionView 橫向停止的兩種效果。

類似于 Airbnb 這款App的首頁酒店效果,從最開始的減速停止效果,到現在的分頁效果。 

本文主要說一下Demo的關鍵類及代碼的使用, 還有算法的大概思路。看下面~

使用方式 

關鍵類:

UICollectionView 橫向滑動停止的兩種效果。

注意: Demo類中使用的布局方式是第三方限制 Masonry ,請使用Pods自行導入到項目中。

部分代碼說明:

#import <UIKit/UIKit.h>

typedef NS_ENUM(NSUInteger, WBScrollType) {
    
    WBScrollTypeFree ,      //自由減速效果
    WBScrollTypePage        //分頁的效果
};

@interface UIContainerCollectionView : UIView

- (void)setupWithDataSource:(NSArray<id> *)dataSource pointValue:(NSValue *)pointValue;

@property (nonatomic, copy) void (^pointChangeBlock)(NSValue *pointValue);

@property (nonatomic, assign) WBScrollType scrollType;      //預設是分頁的

@end
           

UIContainerCollectionView 是 UICollectionView 的容器類,直接在需要使用的地方建立 UIContainerCollectionView 即可。

- setupWithDataSource: pointValue ;  //設定資料源 和 目前的滑動到的初始位置。 (pointValue 為了解決重用的問題)

void (^pointChangeBlock)(NSValue *pointValue);//每次滑動CollectionView, 都會把停止的位置回傳出來,在VC記錄,也是為了解決重用問題

scrollType             //設定滑動停止類型。 預設是分頁效果。 設定為WBScrollTypeFree 為減速效果。

下面的代碼是建立在 UITableViewCell 裡面的:

- (void)setupUI {
    
    WEAK_SELF();
    self.containerView = [UIContainerCollectionView new];
    //預設是 WBScrollTypePage 分頁
    self.containerView.scrollType = WBScrollTypeFree;
    [self.containerView setPointChangeBlock:^(NSValue *pointValue) {
        weakSelf.pointChangeBlock(pointValue);
    }];
    
    [self.contentView addSubview:self.containerView];
    
    [self.containerView mas_makeConstraints:^(MASConstraintMaker *make) {
        make.top.leading.trailing.bottom.equalTo(@0);
    }];
}

- (void)setupWithDataSource:(NSArray<id> *)dataSource pointValue:(NSValue *)pointValue {
    [self.containerView setupWithDataSource:dataSource pointValue:pointValue];
}
           

使用方式直接建立就行了,資料源再指派一下就ok了~~ 

實作思路 :

首先都是要實作 UIScrollViewDelegate ,及下面這段代碼 (UIScrollView 的減速Delegate)

- (void)scrollViewWillEndDragging:(UIScrollView *)scrollView withVelocity:(CGPoint)velocity targetContentOffset:(inout CGPoint *)targetContentOffset {
    
    CGPoint estimateContentOffset = CGPointMake(targetContentOffset -> x, targetContentOffset -> y);
    CGPoint currentPoint = [self itemCenterOffsetWithOriginalTargetContentOffset:estimateContentOffset];
    
    self.pointChangeBlock([NSValue valueWithCGPoint:currentPoint]);
    *targetContentOffset = currentPoint;
}
           

這個方法能在你拖動,甩動ScrollView ,手指離開時會調用此方法,這個會提前計算出ScrollView 最終大概會停止的位置  targetContentOffset 。

然後我們要根據這個位置,自己計算出 需要停止的合适的具體位置 ,再傳給 targetContentOffset。 

在初始化 UICollectionView 的時候, 就要設定好 滑動的減速速度 decelerationRate ,這個值等于 1.0f 的時候,速度最慢,就會是減速運動。 等于0.1f 的時候,就會是分頁效果(分頁效果這隻是其中的一個條件,為0.1f)。

具體看Demo中的這個方法 :

- (CGPoint)itemCenterOffsetWithOriginalTargetContentOffset:(CGPoint)orifinalTargetContentOffset {
    
    if (self.scrollType == WBScrollTypeFree) {  //自由慣性的
        
        CGFloat pageWidth = self.contentSizeWidth / (CGFloat)self.imageNameds.count;
        NSUInteger cellWidth = (self.collectionView.width - CONTENTOFFSET_X * 2 - 10 ) / 2.0;
        NSInteger row = 0;
        CGPoint point ;
        
        if (orifinalTargetContentOffset.x <= pageWidth / 2.0) {
            row = 0;
            point = CGPointMake(0 - CONTENTOFFSET_X, 0);
            self.collectionView.contentInset = UIEdgeInsetsMake(0, CONTENTOFFSET_X, 0, 0);
            return point;
        }
        if (orifinalTargetContentOffset.x > self.contentSizeWidth - cellWidth * 2.5 + 20) {
            row = self.imageNameds.count - 2;
            point = CGPointMake(row * (cellWidth + CONTENTOFFSET_X / 2.0) - CONTENTOFFSET_X, 0);
            self.collectionView.contentInset = UIEdgeInsetsMake(0, 0, 0, CONTENTOFFSET_X);
            return point;
        }
        
        NSUInteger index = orifinalTargetContentOffset.x / pageWidth;
        row = index + (orifinalTargetContentOffset.x - pageWidth * index > pageWidth / 2.0 ? 1 : 0);
        
        point = CGPointMake(row * (cellWidth + CONTENTOFFSET_X / 2.0) - CONTENTOFFSET_X, 0);
        return point;

    } else {
    
        CGFloat pageWidth = self.contentSizeWidth / (CGFloat)self.imageNameds.count;
        NSUInteger cellWidth = (self.collectionView.width - CONTENTOFFSET_X * 2 - 10 ) / 2.0;
        NSInteger row = 0;
        CGPoint point ;
        
        CGFloat scrollBeforeContentOffsetX = self.row * ((cellWidth + CONTENTOFFSET_X / 2.0) - CONTENTOFFSET_X);    //滑動之前的 X 位置
        NSUInteger scrollDirection = orifinalTargetContentOffset.x - scrollBeforeContentOffsetX >= 0.0 ? 1 : 0;    //1向右, 0向左
        
        if (orifinalTargetContentOffset.x == -CONTENTOFFSET_X) {
            scrollDirection = 0;
        }
        
        if (fabs(orifinalTargetContentOffset.x - scrollBeforeContentOffsetX) > pageWidth * 1.5) {
            // + 2
            
            row = (scrollDirection == 0) ? (self.row - 2) : (self.row + 2);
        } else {
            // + 1
            
            row = scrollDirection == 0 ? (self.row - 1) : (self.row + 1);
        }
        
        row = row < 0 ? 0 : row;
        row = row > self.imageNameds.count - 2 ? (self.imageNameds.count - 2) : row;
        self.row = row;
        
        if (row == 0) {
            self.collectionView.contentInset = UIEdgeInsetsMake(0, CONTENTOFFSET_X, 0, 0);
        }
        
        if (row == self.imageNameds.count - 2) {
            self.collectionView.contentInset = UIEdgeInsetsMake(0, 0, 0, CONTENTOFFSET_X);
        }
        
        point = CGPointMake(row * (cellWidth + CONTENTOFFSET_X / 2.0) - CONTENTOFFSET_X, 0);
        return point;
    }
    
    return CGPointMake(0, 0);
}
           

這個方法首先分了兩種情況,

這裡隻說下自由減速的思路,把系統告訴我們大概停下的位置,除以 pageWidth, 能獲得一個至少要停止的整數值index (假設我們這邊index == 5),那我們到底是停在 5 還是 6 呢? 這個還要取決于系統告訴我們的位置是否大于 5.5 (暫時這是比較合理的需求吧~當然你也可以是5.3 或者 5.8) , 所有就有下面的:

row = index + (orifinalTargetContentOffset.x - pageWidth * index > pageWidth / 2.0 ? 1 : 0);
           

最終再計算出point 傳回給系統,就能停止到指定的位置了。

分頁效果的思路:

和減速不同的是,我們要确定分頁一次最多能滑動幾頁,這個Demo是一般滑動都是一頁,如果最用力的滑,是兩頁。 是以計算範圍 就不能像減速那樣自由。

如果滑動停止的位置是在一頁到兩頁的寬度之間,那麼 row就在原來的基礎上 +1 ,如果超過兩頁的距離, 就加 +2 。 并且這裡還要記錄下 滑動的方向,向左的話就 -1 或者 -2。 方向的判斷,可以用 滑動之前的位置(a) 和 停止位置(b) 做比較,  如果 b - a > 0 說明向右滑動,反之向左。

最終傳回合适的point 。

效果樣式圖

分頁效果:

UICollectionView 橫向滑動停止的兩種效果。

減速效果:

UICollectionView 橫向滑動停止的兩種效果。

Demo下載下傳位址:

http://download.csdn.net/detail/yutianlong9306/9583643

補充一個 Item的 UI 效果。

效果圖:

UICollectionView 橫向滑動停止的兩種效果。

說明:

這個Demo中,是實作了一個Item的計算方式,并且做了一些極端情況的優化,更友好的滾動互動。

Demo下載下傳位址:

http://download.csdn.net/detail/yutianlong9306/9623331