實現Twitter-UI效果

在使用Twitter的APP後,我已開發者的視覺並注意到整體與部分之間相互協調是件極其有意思的事情。這引起了我的好奇心:這是怎麼做到的?

讓我們具體地討論下這個視圖佈局:此效果不優雅嗎?它看起開就像本應如此,但你仔細的觀察後就會發現更多。隨着Scrollview的偏移,圖層的覆蓋,動作和比例縮放是那麼的平滑連貫… … 實在是太喜歡這個效果了。

So,就讓我們立刻實現這個效果吧。

首先,先看下最終效果:

結構描述


在寫代碼之前,我想給你一個關於如何構建UI的簡單意見。

打開Main.storyboard文件,在這個控制器裏面你會發現2個主要的對象。第一個是一個呈現Header的視圖,第二個是Scrollview,它包含了Avatar和賬號相關的其他信息,如:username標籤和Follow按鈕。還有一個被叫做Sizer的視圖,它是爲了確保Scrollview擁有足夠大的垂直滑動的空間。

就像你看到的那樣,這個結構非常的簡單。稍微注意一下就可發現Header的外部放置了一個Scrollview,而不是與其他元素放置在一起。雖然沒必嚴格如此,但這樣會使它的結構變動更加靈活。

編碼


如果你仔細的看了最後的動畫,將會注意到你要管理2個不同的動作:

  1. 向下拉(當Scrollview已經停靠在屏幕的頂部的時候)
  2. 上下滑動

第二個動作可以細分爲4個小步驟:

  • 向上滑動,一直到導航條默認的大小並停靠在屏幕的頂部。
  • 向上滑動,Avatar開始逐漸變小。
  • Header被固定後,Avatar會移動到它的下邊。
  • username標籤抵達Header的頂部時,一個新的白色Label將會從Header中心的底部展現。這時Header的背景圖片將會用高斯模糊渲染。

打開ViewController讓我們一個一個的實現這些步驟。

構建管理者


首先要做的事情很明顯,就是獲取關於Scrollview的偏移量offset。我們可以通過UIScrollViewDelegate協議實現scrollViewDidScroll方法。

在一個View上執行最簡單地動畫方式是使用Core Animation逐漸的進行三維變換,並給layer.transform賦予新值。

關於Core Animation可以參考這篇文章

http://www.thinkandbuild.it/playing-around-with-core-graphics-core-animation-and-touch-events-part-1/

這些是scrollViewDidScroll:方法的第一部分

    CGFloat offset = scrollView.contentOffset.y;
    CATransform3D avatarTransform = CATransform3DIdentity;
    CATransform3D headerTransform = CATransform3DIdentity;
在這裏我們獲取一個當前垂直偏移量`offset`,並初始化2個`CATransform3D`變量。

下拉


下拉動作的管理:

if (offset < 0) {
        CGFloat headerScaleFactor = -(offset) / header.bounds.size.height;
        CGFloat headerSizevariation = (header.bounds.size.height * (1.0 + headerScaleFactor) - header.bounds.size.height) / 2.0;
        headerTransform = CATransform3DTranslate(headerTransform, 0, headerSizevariation, 0);
        headerTransform = CATransform3DScale(headerTransform, 1.0 + headerScaleFactor, 1.0 + headerScaleFactor, 0);
        header.layer.transform = headerTransform;

    }

首先,我們檢查offset是否爲負數:用戶在下拉的過程中,將會進入Scrollview的彈性區域。

剩下的代碼就是簡單的數學邏輯。

Header的擴大是因爲它的上邊緣固定於屏幕的頂部,而底部的圖片在等比縮放。

the transformation is made by scaling and subsequently translating to the top for a value equal to the size variation of the view.實際上,移動ImageView圖層的中點到頂部並同時縮放它,你可以獲得相同的效果。

headerScaleFactor是用來被計算的一部分。我們想用offset適當的對Header進行縮放。換句話說,當offsetHeader高度的2倍時,headerScaleFactor必須是2.0。

我們需要管理的第二個動作是上下滑動。讓我們看看,如何一步步通過UI的主要元素完成變換的。

頭部(第一階段)


當前的offset應該大於0。Header應該隨offset進行垂直變換,直到它期望的高度(我們後面將會講解Header的高斯模糊)。

headerTransform = CATransform3DTranslate(headerTransform, 0, MAX(-offset_HeaderStop, -offset), 0);
這句代碼非常簡單。我們只需定義一個讓`Header`在此停止移動的最小值。 讓我感到羞愧的是我比較懶!所以我寫死了一些數值,像`offset_HeaderStop`。其實,我們可以通過計算UI元素的位置來獲取相同的效果。下次有空再改吧。

頭像


Avatar的縮放與我們處理下拉的邏輯一樣,只是在這種情況下,圖片是到達底部而不是頂部。這段代碼和上邊的比較相似,除了減小縮放的比例爲1.4。

        // Avatar -----------
        CGFloat avatarScaleFactor = MIN(offset_HeaderStop, offset) / avatarImage.bounds.size.height / 1.4;
        CGFloat avatarSizevariation = (avatarImage.bounds.size.height * (1.0 + avatarScaleFactor) - avatarImage.bounds.size.height) / 2.0;
        avatarTransform = CATransform3DTranslate(avatarTransform, 0, avatarSizevariation, 0);
        avatarTransform = CATransform3DScale(avatarTransform, 1.0-avatarScaleFactor, 1.0-avatarScaleFactor, 0);
就像你看到的,當`Header`停止變化時,我們用`MIN`函數來使`Avatar`的縮放停止。 此時,我們根據當前`offset`設置最頂層的圖層。除非`offset`小於等於`offset_HeaderStop`,最頂層的圖層是`Avatar`,否則是`Header`。
if (offset <= offset_HeaderStop) {
            if (avatarImage.layer.zPosition < header.layer.zPosition) {
                header.layer.zPosition = 0;
            }
        } else {
            if (avatarImage.layer.zPosition >= header.layer.zPosition) {
                header.layer.zPosition = 2;
            }
        }
    }

白色Label


這段代碼是白色Label的動畫:

        //  ------------ Label
        CATransform3D labelTransform = CATransform3DMakeTranslation(0, MAX(-distance_W_LabelHeader, offset_B_LabelHeader - offset), 0);
        headerLabel.layer.transform = labelTransform;
這裏有2個令我感到羞愧的變量值:當`offset`等於`offset_B_LabelHeader`時,黑色的`username`標籤剛到觸碰到`Header`的底部。

distance_W_LabelHeaderHeader底部與白色Label終點之間的距離。

這個變換是通過此邏輯計算:黑色Label觸碰到Header,白色Label就會立即出現,並且到達Header中點位置就停止移動。所以我們使用下面代碼創建Y值:

MAX(-distance_W_LabelHeader, offset_B_LabelHeader - offset)

高斯模糊


最後一個效果是Header的模糊。爲了得到合適的解決方案,我用了3個不同的庫… … 我也嘗試過用OpenGL ES創建基類,但實時更新模糊總是非常緩慢。

然後我意識到我可以對模糊僅僅計算一次,將不模糊和模糊的圖片進行重疊,只是改變alpha值。我非常確信,Twitter就是這樣做的。

viewDidAppear中,我們計算Header的模糊值並隱藏它,設置alpha值爲0。

    // Header - Blurred Image
    headerBlurImageView = [[UIImageView alloc] initWithFrame:header.bounds];
    headerBlurImageView.image = [[UIImage imageNamed:@"header_bg"] blurredImageWithRadius:10 iterations:20 tintColor:[UIColor clearColor]];
    headerBlurImageView.contentMode = UIViewContentModeScaleAspectFill;
    headerBlurImageView.alpha = 0.0;
    [header insertSubview:headerBlurImageView belowSubview:headerLabel];
    header.clipsToBounds = YES;

模糊視圖是用過FXBlurView實現的。

scrollViewDidScroll:方法中,我們只需根據offset設置alpha:

//  ------------ Blur
        headerBlurImageView.alpha = MIN(1.0, (offset - offset_B_LabelHeader) / distance_W_LabelHeader);

這個計算的背後邏輯是:alpha最大值是1,當黑色Label觸碰到Header時模糊效果開始出現,當白色到達最終位置時,也將停止繼續模糊。

就這樣!

我希望你喜歡這個教程。學習如何重現這種很棒的動畫效果對我來說是很大的樂趣。

Swift代碼:Download Source
OC代碼:Download Source


原版:IMPLEMENTING THE TWITTER IOS APP UI

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章