因爲博客遷移至www.coderyi.com,文章請看http://www.coderyi.com/archives/426app
UIkit動力學是UIkit框架中模擬真實世界的一些特性。框架
主要有UIDynamicAnimator類,經過這個類中的不一樣行爲來實現一些動態特性。dom
它通常有兩種初始化方法,先講常見的第一種ide
animator= [[UIDynamicAnimator alloc] initWithReferenceView:self.view];
動態特性的實現主要依靠它所添加的行爲,經過如下方法進行添加和移除,佈局
[animator addBehavior:attachmentBehavior]; [animator removeAllBehaviors];
接下來介紹五個不一樣的行爲,UIAttachmentBehavior(吸附),UICollisionBehavior(碰撞),UIGravityBehavior(重力),UIPushBehavior(推進),UISnapBehavior(捕捉)。另外還有一個輔助的行爲UIDynamicItemBehavior,用來在item層級設定一些參數,好比item的摩擦,阻力,角阻力,彈性密度和可容許的旋轉等等。動畫
先講吸附行爲,this
它的初始化方法spa
attachmentBehavior = [[UIAttachmentBehavior alloc] initWithItem:iv offsetFromCenter:centerOffset attachedToAnchor:location];
item是實現UIDynamicItem協議的id類型,這裏設置吸附一個UIImageView的實例iv。offset能夠設置吸附的偏移,anchor是設置錨點。代理
UIAttachmentBehavior有幾個屬性,例如damping,frequency。damping是阻尼數值,frequency是震動頻率code
直接上代碼,實現一個pan手勢,讓一個image跟着手勢跑
-(void)gesture:(UIPanGestureRecognizer *)gesture{ CGPoint location = [gesture locationInView:self.view]; CGPoint boxLocation = [gesture locationInView:iv]; switch (gesture.state) { case UIGestureRecognizerStateBegan:{ NSLog(@"you touch started position %@",NSStringFromCGPoint(location)); NSLog(@"location in image started is %@",NSStringFromCGPoint(boxLocation)); [animator removeAllBehaviors]; // Create an attachment binding the anchor point (the finger's current location) // to a certain position on the view (the offset) UIOffset centerOffset = UIOffsetMake(boxLocation.x - CGRectGetMidX(iv.bounds), boxLocation.y - CGRectGetMidY(iv.bounds)); attachmentBehavior = [[UIAttachmentBehavior alloc] initWithItem:iv offsetFromCenter:centerOffset attachedToAnchor:location]; attachmentBehavior.damping=0.5; attachmentBehavior.frequency=0.8; // Tell the animator to use this attachment behavior [animator addBehavior:attachmentBehavior]; break; } case UIGestureRecognizerStateEnded: { [animator removeBehavior:attachmentBehavior]; break; } default: [attachmentBehavior setAnchorPoint:[gesture locationInView:self.view]]; break; } }
UIPushBehavior 能夠爲一個UIView施加一個力的做用,這個力能夠是持續的,也能夠只是一個衝量。咱們能夠指定力的大小,方向和做用點等等信息。
pushBehavior = [[UIPushBehavior alloc] initWithItems:@[iv] mode:UIPushBehaviorModeInstantaneous];
UIPushBehavior 有pushDirection、magnitude等屬性,
//1 CGPoint velocity = [gesture velocityInView:self.view]; CGFloat magnitude = sqrtf((velocity.x * velocity.x) + (velocity.y * velocity.y)); if (magnitude > ThrowingThreshold) { //2 pushBehavior = [[UIPushBehavior alloc] initWithItems:@[iv] mode:UIPushBehaviorModeInstantaneous]; pushBehavior.pushDirection = CGVectorMake((velocity.x / 10) , (velocity.y / 10)); pushBehavior.magnitude = magnitude / ThrowingvelocityPadding; [animator addBehavior:pushBehavior]; //3 // UIDynamicItemBehavior 實際上是一個輔助的行爲,用來在item層級設定一些參數,好比item的摩擦,阻力,角阻力,彈性密度和可容許的旋轉等等 NSInteger angle = arc4random_uniform(20) - 10; itemBehavior = [[UIDynamicItemBehavior alloc] initWithItems:@[iv]]; itemBehavior.friction = 0.2; itemBehavior.allowsRotation = YES; [itemBehavior addAngularVelocity:angle forItem:iv]; [animator addBehavior:itemBehavior]; //4 [self performSelector:@selector(resetDemo) withObject:nil afterDelay:0.4]; }
直接上代碼,實現隨機掉落一張圖片的代碼
// Set up self.animator = [[UIDynamicAnimator alloc] initWithReferenceView:self.view]; self.gravityBeahvior = [[UIGravityBehavior alloc] initWithItems:nil]; [self.animator addBehavior:self.gravityBeahvior]; - (void)tapped:(UITapGestureRecognizer *)gesture { NSUInteger num = arc4random() % 40 + 1; NSString *filename = [NSString stringWithFormat:@"m%lu", (unsigned long)num]; UIImage *image = [UIImage imageNamed:filename]; UIImageView *imageView = [[UIImageView alloc] initWithImage:image]; [self.view addSubview:imageView]; CGPoint tappedPos = [gesture locationInView:gesture.view]; imageView.center = tappedPos; [self.gravityBeahvior addItem:imageView]; }
繼續上面的代碼,當圖片快掉落出邊界的時候有 碰撞效果,這個就是UICollisionBehavior實現的。
// Set up self.animator = [[UIDynamicAnimator alloc] initWithReferenceView:self.view]; self.gravityBeahvior = [[UIGravityBehavior alloc] initWithItems:nil]; self.collisionBehavior = [[UICollisionBehavior alloc] initWithItems:nil]; self.collisionBehavior.translatesReferenceBoundsIntoBoundary = YES; self.itemBehavior = [[UIDynamicItemBehavior alloc] initWithItems:nil]; self.itemBehavior.elasticity = 0.6; self.itemBehavior.friction = 0.5; self.itemBehavior.resistance = 0.5; [self.animator addBehavior:self.gravityBeahvior]; [self.animator addBehavior:self.collisionBehavior]; [self.animator addBehavior:self.itemBehavior]; - (void)tapped:(UITapGestureRecognizer *)gesture { NSUInteger num = arc4random() % 40 + 1; NSString *filename = [NSString stringWithFormat:@"m%lu", (unsigned long)num]; UIImage *image = [UIImage imageNamed:filename]; UIImageView *imageView = [[UIImageView alloc] initWithImage:image]; [self.view addSubview:imageView]; CGPoint tappedPos = [gesture locationInView:gesture.view]; imageView.center = tappedPos; [self.gravityBeahvior addItem:imageView]; [self.collisionBehavior addItem:imageView]; [self.itemBehavior addItem:imageView]; }
另外,UICollisionBehavior有它的代理,其中列舉兩個方法,它們表示行爲開始和結束的時候的代理。
- (void)collisionBehavior:(UICollisionBehavior*)behavior beganContactForItem:(id <UIDynamicItem>)item withBoundaryIdentifier:(id <NSCopying>)identifier atPoint:(CGPoint)p; - (void)collisionBehavior:(UICollisionBehavior*)behavior endedContactForItem:(id <UIDynamicItem>)item withBoundaryIdentifier:(id <NSCopying>)identifier;
UISnapBehavior 將UIView經過動畫吸附到某個點上。
- (void) handleTap:(UITapGestureRecognizer *)paramTap{ CGPoint tapPoint = [paramTap locationInView:self.view]; if (self.snapBehavior != nil){ [self.animator removeBehavior:self.snapBehavior]; } self.snapBehavior = [[UISnapBehavior alloc] initWithItem:self.squareView snapToPoint:tapPoint]; self.snapBehavior.damping = 0.5f; //劇列程度 [self.animator addBehavior:self.snapBehavior]; }
文章開頭說到UIDynamicAnimator有兩種初始化方法,這裏介紹它與UICollectionView的完美結合,讓UICollectionView產生各類動態特性的行爲。
你是否記得iOS系統中信息應用中的附有彈性的消息列表,他就是加入了UIAttachmentBehavior吸附行爲,這裏經過一個UICollectionView實現相似效果。
主要是複寫UICollectionViewFlowLayout,在layout中爲每個佈局屬性元素加上吸附行爲就能夠了。
關於複寫layout,能夠參考onevcat的博客
http://www.onevcat.com/2012/08/advanced-collection-view/
下面就直接上代碼了
首先遍歷每一個 collection view layout attribute 來建立和添加新的 dynamic animator
-(void)prepareLayout { [super prepareLayout]; if (!_animator) { _animator = [[UIDynamicAnimator alloc] initWithCollectionViewLayout:self]; CGSize contentSize = [self collectionViewContentSize]; NSArray *items = [super layoutAttributesForElementsInRect:CGRectMake(0, 0, contentSize.width, contentSize.height)]; for (UICollectionViewLayoutAttributes *item in items) { UIAttachmentBehavior *attachment = [[UIAttachmentBehavior alloc] initWithItem:item attachedToAnchor:item.center]; attachment.length = 0; attachment.damping = self.damping; attachment.frequency = self.frequency; [_animator addBehavior:attachment]; } } }
接下來咱們如今須要實現 layoutAttributesForElementsInRect: 和 layoutAttributesForItemAtIndexPath: 這兩個方法,UIKit 會調用它們來詢問 collection view 每個 item 的佈局信息。咱們寫的代碼會把這些查詢交給專門作這些事的 dynamic animator
-(NSArray *)layoutAttributesForElementsInRect:(CGRect)rect { return [_animator itemsInRect:rect]; } - (UICollectionViewLayoutAttributes *)layoutAttributesForItemAtIndexPath:(NSIndexPath *)indexPath { return [_animator layoutAttributesForCellAtIndexPath:indexPath]; }
而後是響應滾動事件的方法
這個方法會在 collection view 的 bound 發生改變的時候被調用,根據最新的 content offset 調整咱們的 dynamic animator 中的 behaviors 的參數。在從新調整這些 behavior 的 item 以後,咱們在這個方法中返回 NO;由於 dynamic animator 會關心 layout 的無效問題,因此在這種狀況下,它不須要去主動使其無效
-(BOOL)shouldInvalidateLayoutForBoundsChange:(CGRect)newBounds { UIScrollView *scrollView = self.collectionView; CGFloat scrollDelta = newBounds.origin.y - scrollView.bounds.origin.y; NSLog(@" %f %f",newBounds.origin.y,scrollView.bounds.origin.y); CGPoint touchLocation = [scrollView.panGestureRecognizer locationInView:scrollView]; for (UIAttachmentBehavior *behavior in _animator.behaviors) { CGPoint anchorPoint = behavior.anchorPoint; CGFloat distanceFromTouch = fabsf(touchLocation.y - anchorPoint.y); CGFloat scrollResistance = distanceFromTouch / self.resistanceFactor; UICollectionViewLayoutAttributes *item = [behavior.items firstObject]; CGPoint center = item.center; center.y += (scrollDelta > 0) ? MIN(scrollDelta, scrollDelta * scrollResistance) : MAX(scrollDelta, scrollDelta * scrollResistance); item.center = center; [_animator updateItemUsingCurrentState:item]; } return NO; }
讓咱們仔細查看這個代碼的細節。首先咱們獲得了這個 scroll view(就是咱們的 collection view ),而後計算它的 content offset 中 y 的變化(在這個例子中,咱們的 collection view 是垂直滑動的)。一旦咱們獲得這個增量,咱們須要獲得用戶接觸的位置。這是很是重要的,由於咱們但願離接觸位置比較近的那些物體能移動地更迅速些,而離接觸位置比較遠的那些物體則應該滯後些。
對於 dynamic animator 中的每一個 behavior,咱們將接觸點到該 behavior 物體的 y 的距離除以 500。分母越小,這個 collection view 的的交互就越有彈簧的感受。