Swift21/90Days – 動態變化的 UITableView 頭部

callmewhy發表於2014-12-11

Swift90Days – 動態變化的 UITableView 頭部

今天公司的有一個需求是實現動態的 UITableView 頭部,基本需求是:

  • 預設狀態顯示一個 headerView 展示頭像。
  • 上推時 headerView 縮小高度,影像逐漸模糊。
  • 下拉時 headerView 的影像相應下移,方便展示全景。

下面先看看開源專案 ParallaxTableViewHeader 的實現方式,和自己的做個對比。

Blur

它的虛化效果是通過 UIImage+ImageEffects 這個 category 實現的。

具體內容就不深究了,對於影像處理這塊的類庫不太熟悉。

在 headerView 中可以這樣使用獲取一張虛化的圖片:

- (void)refreshBlurViewForNewImage
{
    UIImage *screenShot = [self screenShotOfView:self];
    screenShot = [screenShot applyBlurWithRadius:5 tintColor:[UIColor colorWithWhite:0.6 alpha:0.2] saturationDeltaFactor:1.0 maskImage:nil];
    self.bluredImageView.image = screenShot;
}

Header

原始碼中提供了兩種工廠方法進行初始化:

+ (id)parallaxHeaderViewWithImage:(UIImage *)image forSize:(CGSize)headerSize;
+ (id)parallaxHeaderViewWithSubView:(UIView *)subView;

第一種方法是通過 image 進行初始化,會呼叫預設的 init 方法,第二種是自定義 subView 的方法。

我們只用看下預設的 init 方法:

- (void)initialSetupForDefaultHeader
{
    // 初始化一個 scrollView 作為容器
    UIScrollView *scrollView = [[UIScrollView alloc] initWithFrame:self.bounds];
    self.imageScrollView = scrollView;

    // 初始化預設大小的圖片,用於顯示
    UIImageView *imageView = [[UIImageView alloc] initWithFrame:scrollView.bounds];
    // 設定其拉伸模式為:上下左右間距不變,拉伸高度和寬度。
    imageView.autoresizingMask = UIViewAutoresizingFlexibleLeftMargin | UIViewAutoresizingFlexibleRightMargin | UIViewAutoresizingFlexibleTopMargin | UIViewAutoresizingFlexibleBottomMargin | UIViewAutoresizingFlexibleHeight | UIViewAutoresizingFlexibleWidth;
    // 設定圖片填充模式:儘量填充,適當裁剪
    imageView.contentMode = UIViewContentModeScaleAspectFill;
    imageView.image = self.headerImage;
    self.imageView = imageView;
    [self.imageScrollView addSubview:imageView];

    // 設定顯示的標籤文字
    CGRect labelRect = self.imageScrollView.bounds;
    labelRect.origin.x = labelRect.origin.y = kLabelPaddingDist;
    labelRect.size.width = labelRect.size.width - 2 * kLabelPaddingDist;
    labelRect.size.height = labelRect.size.height - 2 * kLabelPaddingDist;
    UILabel *headerLabel = [[UILabel alloc] initWithFrame:labelRect];
    headerLabel.textAlignment = NSTextAlignmentCenter;
    headerLabel.numberOfLines = 0;
    headerLabel.lineBreakMode = NSLineBreakByWordWrapping;
    headerLabel.autoresizingMask = imageView.autoresizingMask;
    headerLabel.textColor = [UIColor whiteColor];
    headerLabel.font = [UIFont fontWithName:@"AvenirNextCondensed-Regular" size:23];
    self.headerTitleLabel = headerLabel;
    [self.imageScrollView addSubview:self.headerTitleLabel];

    // 設定虛化的圖片,預設 alpha 為0,即完全透明
    self.bluredImageView = [[UIImageView alloc] initWithFrame:self.imageView.frame];
    self.bluredImageView.autoresizingMask = self.imageView.autoresizingMask;
    self.bluredImageView.alpha = 0.0f;
    [self.imageScrollView addSubview:self.bluredImageView];
    [self addSubview:self.imageScrollView];
}

大概瞭解了整個 view 的結構,不太清楚為什麼要通過截圖的方式獲取圖片。

通過實現 UISCrollViewDelegate 中的 scrollViewDidScroll 方法來監聽 UITableView 的滑動事件。

如果當前 UITableView 滑動了,則會呼叫 headerView 的 layoutHeaderViewForScrollViewOffset 方法:

- (void)layoutHeaderViewForScrollViewOffset:(CGPoint)offset
{
    CGRect frame = self.imageScrollView.frame;

    // 如果是上推
    if (offset.y > 0)
    {
        frame.origin.y = offset.y *kParallaxDeltaFactor;
        self.imageScrollView.frame = frame;

        // 設定虛化圖層的 alpha 值。乘2是為了增大虛化梯度
        self.bluredImageView.alpha = 2 * (offset.y / kDefaultHeaderFrame.size.height) ;

        // 裁切 subview
        self.clipsToBounds = YES;
    }
    // 如果是下拉
    else
    {
        CGFloat delta = 0.0f;
        CGRect rect = kDefaultHeaderFrame;
        delta = fabs(offset.y);
        // 為了保持 header 的 top 對齊需要設定 y 座標
        rect.origin.y -= delta;
        rect.size.height += delta;
        self.imageScrollView.frame = rect;
        self.clipsToBounds = NO;

        // 設定 label 的 alpha 值
        self.headerTitleLabel.alpha = 1 - delta / kMaxTitleAlphaOffset;
    }
}

上推的時候主要工作是虛化圖片,下拉的時候主要工作是設定圖片高度和座標。這個和我自己寫的基本思路相同。

大概就是這樣,引用中放了另一個類似的專案供大家參考。這個專案與 Swift 無關,不過實現的思路可以借鑑。


References

相關文章