日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

谈谈iOS中粘性动画以及果冻效果的实现

發布時間:2024/8/26 编程问答 40 豆豆
生活随笔 收集整理的這篇文章主要介紹了 谈谈iOS中粘性动画以及果冻效果的实现 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.


文 / 楊騎滔

在最近做個一個自定義PageControl——KYAnimatedPageControl中,我實現了CALayer的形變動畫以及CALayer的彈性動畫,效果先過目:
?


先做個提綱:

第一個分享的主題是“如何讓CALayer發生形變”,這個技術在我之前一個項目 ———— KYCuteView 中有涉及,也寫了篇簡短的實現原理博文。今天再舉一個例子。

之前我也做過類似果凍效果的彈性動畫,比如這個項目—— KYGooeyMenu。用到的核心技術是CAKeyframeAnimation,然后設置幾個不同狀態的關鍵幀,就能初步達到這種彈性效果。但是,畢竟只有幾個關鍵幀,而且是需要手動計算,不精確不說,動畫也不夠細膩,畢竟你不可能手動創建60個關鍵幀。所以,今天的第二個主題是 —— “如何用阻尼振動函數創建出60個關鍵幀”,從而實現CALayer產生類似[UIView animateWithDuration:delay:usingSpringWithDamping:initialSpringVelocity:ptions:animations:completion] 的彈性動畫。

正文。

如何讓CALayer發生形變?

關鍵技術很簡單:你需要用多條貝塞爾曲線 “拼” 出這個Layer。之所以這樣做的原因不言而喻,因為這樣方便我們發生形變。

比如 KYAnimatedPageControl 中的這個小球,其實它是這么被畫出來的:
?


小球是由弧AB、弧BC、弧CD、弧DA 四段組成,其中每段弧都綁定兩個控制點:弧AB 綁定的是 C1 、 C2;弧BC 綁定的是 C3 、 C4 .....

如何表達各個點?

首先,A、B、C、D是四個動點,控制他們動的變量是ScrollView的contentOffset.x。我們可以在-(void)scrollViewDidScroll:UIScrollView *)scrollView中實時獲取這個變量,并把它轉換成一個控制在 0~1 的系數,取名為factor。
?

  • _factor = MIN(1, MAX(0, (ABS(scrollView.contentOffset.x - self.lastContentOffset) / scrollView.frame.size.width)));
  • 復制代碼


    假設A、B、C、D的最大變化距離為小球直徑的2/5。那么結合這個0~1的系數,我們可以得出A、B、C、D的真實變化距離 extra 為:extra = (self.width * 2 / 5) * factor。當factor == 1時,達到最大形變狀態,此時四個點的變化距離均為(self.width * 2 / 5)。

    注意:根據滑動方向,我們還要根據是B點移動還是D點移動。
    ?

  • CGPoint pointA = CGPointMake(rectCenter.x ,self.currentRect.origin.y + extra);
  • CGPoint pointB = CGPointMake(self.scrollDirection == ScrollDirectionLeft ? rectCenter.x + self.currentRect.size.width/2 : rectCenter.x + self.currentRect.size.width/2 + extra*2 ,rectCenter.y);
  • CGPoint pointC = CGPointMake(rectCenter.x ,rectCenter.y + self.currentRect.size.height/2 - extra);
  • CGPoint pointD = CGPointMake(self.scrollDirection == ScrollDirectionLeft ? self.currentRect.origin.x - extra*2 : self.currentRect.origin.x, rectCenter.y);
  • 復制代碼


    然后是控制點:

    關鍵是要知道上圖中A-C1 、B-C2、B-C3、C-C4....這些水平和垂直虛線的長度,命名為offSet。經過多次嘗試,我得出的結論是:

    當offSet設置為 直徑除以3.6 的時候,弧線能完美地貼合成圓弧。我隱約感覺這個 3.6 是必然,貌似和360度有某種關系,或許通過演算能得出 3.6 這個值的必然性,但我沒有嘗試。

    因此,各個控制點的坐標:
    ?

  • CGPoint c1 = CGPointMake(pointA.x + offset, pointA.y);
  • CGPoint c2 = CGPointMake(pointB.x, pointB.y - offset);
  • CGPoint c3 = CGPointMake(pointB.x, pointB.y + offset);
  • CGPoint c4 = CGPointMake(pointC.x + offset, pointC.y);
  • CGPoint c5 = CGPointMake(pointC.x - offset, pointC.y);
  • CGPoint c6 = CGPointMake(pointD.x, pointD.y + offset);
  • CGPoint c7 = CGPointMake(pointD.x, pointD.y - offset);
  • CGPoint c8 = CGPointMake(pointA.x - offset, pointA.y);
  • 復制代碼


    有了終點和控制點,就可以用UIBezierPath 中提供的方法 - (void)addCurveToPoint:CGPoint)endPoint controlPoint1:CGPoint)controlPoint1 controlPoint2:CGPoint)controlPoint2; 畫線段了。

    重載CALayer的- (void)drawInContext:CGContextRef)ctx;方法,在里面畫圖案:
    ?

  • - (void)drawInContext:(CGContextRef)ctx{
  • ....//在這里計算每個點的坐標
  • UIBezierPath* ovalPath = [UIBezierPath bezierPath];
  • [ovalPath moveToPoint: pointA];
  • [ovalPath addCurveToPoint:pointB controlPoint1:c1 controlPoint2:c2];
  • [ovalPath addCurveToPoint:pointC controlPoint1:c3 controlPoint2:c4];
  • [ovalPath addCurveToPoint:pointD controlPoint1:c5 controlPoint2:c6];
  • [ovalPath addCurveToPoint:pointA controlPoint1:c7 controlPoint2:c8];
  • [ovalPath closePath];
  • CGContextAddPath(ctx, ovalPath.CGPath);
  • CGContextSetFillColorWithColor(ctx, self.indicatorColor.CGColor);
  • CGContextFillPath(ctx);
  • }
  • 復制代碼


    現在,當你滑動ScrollView的時候,小球就會形變了。

    如何用阻尼振動函數創建出60個關鍵幀?

    上面的例子中,有個很重要的因素,就是ScrollView中的contentOffset.x這個變量,沒有這個輸入,那接下來什么都不會發生。但想要獲得這個變量,是需要用戶觸摸、滑動去交互產生的。在某個動畫中用戶是沒有直接的交互輸入的,比如當手指離開之后,二手手機拍賣要讓這個小球以果凍效果彈回初始狀態,這個過程手指已經離開屏幕,也就沒有了輸入,那么用上面的方法肯定行不通,所以,我們可以用CAAnimation.

    我們知道,iOS7中蘋果在 UIView(UIViewAnimationWithBlocks) 加入了一個新的制作彈性動畫的工廠方法:

  • + (void)animateWithDuration:(NSTimeInterval)duration delay:(NSTimeInterval)delay usingSpringWithDamping:(CGFloat)dampingRatio initialSpringVelocity:(CGFloat)velocity options:(UIViewAnimationOptions)options animations:(void (^)(void))animations completion:(void (^)(BOOL finished))completion NS_AVAILABLE_IOS(7_0);
  • 復制代碼


    但是沒有直接的關于彈性的 CAAnimation 子類,類似CABasicAnimation或CAKeyframeAnimation 來直接給CALayer添加動畫。好消息是iOS9中添加了公開的 CASpringAnimation。但是出于兼容低版本以及對知識探求的角度,我們可以了解一下如何手動給CALayer創建一個彈性動畫。

    在開始之前需要復習一下高中物理知識 ———— 阻尼振動,你可以點擊高亮字體的鏈接稍微復習一下。
    ?


    根據維基百科,我們可以得到如下振動函數通式:
    ?


    當然這只是一個通式,我們需要讓 圖像過(0,0),并且最后衰減到1 。我們可以讓原圖像先繞X軸翻轉180度,也就是加一個負號。然后沿y軸向上平移一個單位。所以稍加變形可以得到如下函數:
    ?


    想看函數的圖像?沒問題,推薦一個在線查看函數圖象的網站 —— Desmos ,把這段公式 1-\left(e^{-5x}\cdot \cos (30x)\right) 復制粘帖進去就可以看到圖像。

    改進后的函數圖像是這樣的:
    ?


    完美滿足了我們 圖形過(0,0),震蕩衰減到1 的要求。其中式子中的 5 相當于阻尼系數,數值越小幅度越大;式子中的 30 相當于震蕩頻率 ,數值越大震蕩次數越多。

    接下來就需要轉換成代碼。

    總體思路是創建60幀關鍵幀(因為屏幕的最高刷新頻率就是60FPS),然后把這60幀數據賦值給 CAKeyframeAnimation 的 values 屬性。

    用以下代碼生成60幀后保存到一個數組并返回它,其中//1就是利用剛才的公式創建60個數值:
    ?

  • +(NSMutableArray *) animationValues:(id)fromValue toValue:(id)toValue usingSpringWithDamping:(CGFloat)damping initialSpringVelocity:(CGFloat)velocity duration:(CGFloat)duration{
  • //60個關鍵幀
  • NSInteger numOfPoints = duration * 60;
  • NSMutableArray *values = [NSMutableArray arrayWithCapacity:numOfPoints];
  • for (NSInteger i = 0; i < numOfPoints; i++) {
  • [values addObject:@(0.0)];
  • }
  • //差值
  • CGFloat d_value = [toValue floatValue] - [fromValue floatValue];
  • for (NSInteger point = 0; point CGFloat x = (CGFloat)point / (CGFloat)numOfPoints;
  • CGFloat value = [toValue floatValue] - d_value * (pow(M_E, -damping * x) * cos(velocity * x)); //1 y = 1-e^{-5x} * cos(30x)
  • values[point] = @(value);
  • }
  • return values;
  • }
  • 復制代碼


    接下來創建一個對外的類方法,并返回一個 CAKeyframeAnimation :
    ?

  • +(CAKeyframeAnimation *)createSpring:(NSString *)keypath duration:(CFTimeInterval)duration usingSpringWithDamping:(CGFloat)damping initialSpringVelocity:(CGFloat)velocity fromValue:(id)fromValue toValue:(id)toValue{
  • CAKeyframeAnimation *anim = [CAKeyframeAnimation animationWithKeyPath:keypath];
  • NSMutableArray *values = [KYSpringLayerAnimation animationValues:fromValue toValue:toValue usingSpringWithDamping:damping * dampingFactor initialSpringVelocity:velocity * velocityFactor duration:duration];
  • anim.values = values;
  • anim.duration = duration;
  • return anim;
  • }
  • 復制代碼


    另一個關鍵

    以上,我們創建了 CAKeyframeAnimation 。但是這些values到底是對誰起作用的呢?如果你熟悉CoreAnimation的話,沒錯,是對傳入的keypath起作用。而這些keypath其實就是CALayer中的屬性@property。比如,之所以當傳入的keypath為transform.rotation.x時CAKeyframeAnimation會讓layer發生旋轉,就是因為CAKeyframeAnimation發現CALayer中有這么個屬性叫transform,于是動畫就發生了。現在我們需要改變的是主題一中的那個factor變量,所以,很自然地想到,我們可以給CALayer補充一個屬性名為factor就行了,這樣CAKeyframeAnimation加到layer上時發現layer有這個factor屬性,就會把60幀不同的values賦值給factor。當然我們要把fromValue和toValue控制在0~1:
    ?

  • ??CAKeyframeAnimation *anim = [KYSpringLayerAnimation createSpring:@"factor" duration:0.8 usingSpringWithDamping:0.5 initialSpringVelocity:3 fromValue:@(1) toValue:@(0)];
  • self.factor = 0;
  • [self addAnimation:anim forKey:@"restoreAnimation"];
  • 復制代碼


    最后一步,雖然CAKeyframeAnimation實時地去改變了我們想要的factor,但我們還得通知屏幕刷新,這樣才能看到動畫。
    ?

  • +(BOOL)needsDisplayForKey:(NSString *)key{
  • if ([key isEqual:@"factor"]) {
  • return YES;
  • }
  • return [super needsDisplayForKey:key];
  • }
  • 復制代碼


    上面的代碼通知屏幕當factor發生變化時,實時刷新屏幕。

    最后的最后,你需要重載CALayer中的-(id)initWithLayer:GooeyCircle *)layer方法,為了保證動畫能連貫起來,你需要拷貝前一個狀態的layer及其所有屬性。
    ?

  • -(id)initWithLayer:(GooeyCircle *)layer{
  • self = [super initWithLayer:layer];
  • if (self) {
  • self.indicatorSize = layer.indicatorSize;
  • self.indicatorColor = layer.indicatorColor;
  • self.currentRect = layer.currentRect;
  • self.lastContentOffset = layer.lastContentOffset;
  • self.scrollDirection = layer.scrollDirection;
  • self.factor = layer.factor;
  • }
  • return self;
  • }
  • 復制代碼


    總結:

    做自定義的動畫最關鍵的就是要有變量,要有輸入。像滑動ScrollView的時候,滑動的距離就是動畫的輸入,可以作為動畫的變量;當沒有交互的時候,可以用CAAnimation。其實CAAnimation底層就有個定時器,而定時器的作用就是可以產生變量,時間就是變量,就可以產生變化的輸入,就能看到變化的狀態,連起來就是動畫了。

    總結

    以上是生活随笔為你收集整理的谈谈iOS中粘性动画以及果冻效果的实现的全部內容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。

    主站蜘蛛池模板: 亚洲日本欧美在线 | av综合色 | 三级麻豆| 亚洲不卡视频在线观看 | 91黄色免费观看 | 国产第五页| 在线视频精品免费 | 亚洲第一av | 天天色天天 | 国产成人久久77777精品 | 久久久久无码精品国产 | 久草色在线 | 欧美z○zo重口另类黄 | 亚洲午夜毛片 | 免费三级av | 欧美日本高清视频 | 精品九九在线 | 极品尤物一区二区三区 | 爱情岛论坛亚洲入口 | 午夜免费小视频 | 国产女主播喷水视频在线观看 | 黄色一级带| 性欧美18一19性猛交 | 国产精品aaaa | 国产黄色一区二区 | 色漫在线观看 | av网站大全免费 | 欧美成人午夜77777 | 精久久久久 | 欧美重口另类 | 亚洲色图在线视频 | 国产videos| heyzo国产 | 香蕉精品视频在线观看 | 人人干人人爽 | 日韩av在线资源 | 欧美日韩成人 | 免费看黄20分钟 | 人人爽夜夜爽 | www黄色| 精品欧美乱码久久久久久 | 一区福利视频 | 国产精品传媒 | 丰满少妇高潮一区二区 | 视频一区二区三区在线观看 | 亚洲1024 | 亚洲不卡中文字幕 | 3d成人动漫在线观看 | 日本精品免费视频 | 少妇高潮a一级 | 久久国产精品国语对白 | 青娱乐在线免费视频 | 99国产揄拍国产精品 | 午夜67194| 黄色在线免费 | 爱av在线 | 国产成人超碰人人澡人人澡 | 免费成人毛片 | 国产顶级毛片 | 国产传媒av | 欧美aaaaaaaaa | 人成在线免费视频 | 精品人妻伦一二三区免费 | 日韩成人高清 | 黄色国产小视频 | 秋霞久久精品 | 久久曰视频 | 日韩二级片 | 久久欧| 日韩精品小视频 | 国产成人精品一区二区三区网站观看 | 小俊大肉大捧一进一出好爽 | 国产精品毛片久久久久久久 | 国产大片中文字幕 | 国产乡下妇女做爰 | 亚洲爽爽 | 蜜桃av鲁一鲁一鲁一鲁俄罗斯的 | 欧洲亚洲一区二区三区 | 一级黄色性视频 | 国产色吧 | 毛片9| 国产精品va在线观看无码 | 成人免费在线小视频 | 午夜看黄神器 | jizz日本少妇 | 韩日视频在线观看 | 黄色三级大片 | 日韩精品第1页 | 精品96久久久久久中文字幕无 | 波多野结衣av电影 | 国产精品无码专区av免费播放 | www.热久久| 久久99草 | 熟女高潮一区二区三区 | 久久狠狠高潮亚洲精品 | 欧美视频一区二区三区在线观看 | 亚洲区自拍偷拍 | 男人干女人视频 | 日韩熟妇一区二区三区 |