上一篇我們講了一個簡單的計數器的設計。
這一期我們主要圍繞整個app最難的部分--卡片動畫來講解。
這是完成的大概效果。
1.有3D透視效果
2.每次顯示3張圖片層疊擺放
3.能夠響應pan手勢,可以隨著pan手勢滑動。
做動畫需要拆解。
一個復雜的動畫總是有許多個獨立的小動畫構成的,如果我們能夠清晰的拆解出一個個小動畫再逐個擊破的話會容易編寫的多。
首先我們需要解決的問題是如何擺放我們的3個卡片,使之看起來有3d的效果,其實通過仔細觀察。我們發現每一張卡片的後面一張卡片都會比前一張的寬度等比縮放,然後Y軸下降一定的距離。
so,我們就有兩個簡單的做法了。
第一種是利用transform的translate和scale
if (_vertical) { #ifdef ICAROUSEL_MACOS //invert again tilt = -tilt; offset = -offset; #endif return CATransform3DTranslate(transform, 0.0, -offset * _itemWidth * tilt, offset * _itemWidth * spacing); } else { return CATransform3DTranslate(transform, offset * _itemWidth * tilt, 0.0, offset * _itemWidth * spacing); }
這是第三方控件icarousel裡的TimeMachine效果的一段代碼,大家可以去github上搜索。
這個第三方控件基本實現了我們需要的效果。大意就是利用移動Y軸和Z軸實現透視效果。關於3D透視的內容大家可以看這篇:《iOS的三維透視投影》
但是所有使用transform變形之後的view,有個巨大的問題是,以後每次進行位移或者別的transform變換的時候都要小心處理大量的仿射變換,非常的復雜麻煩。
有沒有簡單的方法呢?
實際上是有的,就是最簡單的Rect處理。
for i in 0..<3{ var cardView:CardView = CardView(frame: CGRectInset(self.bounds, CGFloat(i)*20.0, CGFloat(i)*20.0)) cardView.girlImage = UIImage(named: "meishi\(i+1).jpg")! print("meishi\(i).jpg\n") cardView.center = CGPointMake(CGRectGetWidth(self.bounds)/2.0 , CGRectGetHeight(self.bounds)/2.0 + CGFloat(i)*30.0) cardView.alpha = 1-CGFloat(i) * 0.1 alphaArray.append(cardView.alpha) cardViewArray.append(cardView) rectArray.append(cardView.frame) self.addSubview(cardView) self.sendSubviewToBack(cardView) }
CardView是我自定義的一個UIView,裡面很簡單只有一個UIImageView用來顯示圖片,不過UIImageView的尺寸比CardView小一點。就是上下左右都有留白。
然後我新建了3個CardView,後面的CardView總是比前一個CardView的寬高都小40(CGRectInset這個方法自行谷歌),然後把後一個cardView的Y軸向下移,讓他漏出來,然後每一個cardView的透明度都比前一個的透明度要小。所以從視覺上造成一種遮蓋的假象。
這樣利用Rect就可以做出偽3D效果,而不是利用transform來做。
現在有了基本的樣子了,現在還缺一個交互。
大家看原版的Gif
有一點值得注意。就是在手指滑動了相同距離的情況下,後面的cardView位移距離總是比前一個要少。
看一下這段代碼。
case .Changed: var translatePoint = gesture.translationInView(self) for i in 0..<3{ var cardView:CardView = cardViewArray[i] cardView.center = CGPointMake(cardView.center.x + translatePoint.x*0.5*CGFloat(3-i), cardView.center.y) } gesture.setTranslation(CGPointZero, inView: self)
這段代碼很簡單,就是依次取出我們的CardView(我之前把3個cardview都存在了數組裡,從0-2的cardview就是從最上面到最下面的),最上面的cardView因為移動距離是最快的。所以是0.5*CGFloat(3-0),每次移動距離都是手勢移動距離的1.5倍,那麼第二個就是1倍(跟手指移動距離相同),第三個是0.5倍,所以移動距離最小。很簡單的就實現了圖中效果。
再來看看我們如何實現刪除第一張圖篇,並且在整個CardViewContainer的最後添加一張新圖的方法。
case .Ended: var endPoint = gesture.locationInView(self) if endPoint.x > CGRectGetWidth(self.bounds) - 100 { var firstCardView:CardView = cardViewArray[0] var lastOriginalRect:CGRect = rectArray.last! var lastAlpha:CGFloat = alphaArray.last! cardViewArray.removeAtIndex(0) cardViewArray.append(firstCardView) UIView.animateWithDuration(0.25, animations: { () -> Void in firstCardView.center = CGPointMake(CGRectGetWidth(self.bounds)*2, firstCardView.center.y) }, completion: { (isCompletion:Bool) -> Void in for i in 0.. Void in cardView.frame = originalRect cardView.alpha = alphaInArray }, completion: { (flag:Bool) -> Void in self.sendSubviewToBack(firstCardView) firstCardView.frame = lastOriginalRect firstCardView.alpha = lastAlpha }) } })
這段代碼我就不一句一句解釋了,我簡單說一下我當時的思考方法。
首先確定一個阈值,就是向左滑到多少或者向右滑到多少的時候執行刪除操作,這裡我設置了是左滑到x<100的事後刪除。
然後是,怎麼刪除?
很簡單,讓最上面的一個card在uiview.animation的block裡執行位移,讓他飛出屏幕,然後把第二個cardview的frame改為第一個cardView的初始frame,第三個變成第二個。然後讓飛出屏幕的card,self.sendSubviewToBack(firstCardView),放到最後一個(因為這個view之前是在最前面的),最後把這個view的frame改成以前的第三個view的rect。Ok。搞定。
因為整個動畫過程是要有spring效果的,所以我們使用了
IView.animateWithDuration(1, delay: 0, usingSpringWithDamping: 0.4, initialSpringVelocity: 0, options: UIViewAnimationOptions.CurveEaseOut, animations: { () -> Void in
這個方法。
最後代碼在這裡:https://github.com/zangqilong198812/HotGirls
現在我們的代碼裡有很多魔法數字,並且只能放在iPhone6的屏幕裡才能正常顯示,所以下一步我們需要封裝一些東西,讓他適用性更廣。更好用。