2015-02-10 21 views
6

मुझे दो राज्यों के बीच UIImageView को एनिमेट करना मुश्किल लगता है: इसका मूल आयत फ्रेम, और UIBezierPath के साथ बनाया गया एक नया आकार। कई अलग-अलग तकनीकों का उल्लेख किया गया है, जिनमें से अधिकांश मेरे लिए काम नहीं करते हैं।दो बेजियर पथ आकारों के बीच एनिमेटिंग

सबसे पहले यह अहसास था कि UIView ब्लॉक एनीमेशन का उपयोग करना काम नहीं करेगा; जाहिर है कि कोई animateWithDuration: ब्लॉक में सब्लेयर एनिमेशन नहीं कर सकता है। (here और here देखें)

, CABasicAnimation जैसे ठोस उप-वर्गों के साथ CAAnimation छोड़ दिया गया। मुझे जल्द ही एहसास हुआ कि कोई ऐसा दृश्य से एनिमेट नहीं कर सकता है जिसमें CAShapeLayer ऐसा नहीं है (उदाहरण के लिए here देखें)।

और वे बस किसी भी दो आकार परत पथ, बल्कि नहीं किया जा सकता (here देखें)

के साथ "एक आकार परत की राह एनिमेट करना ही काम करने के लिए जब आप की तरह करने के लिए की तरह से एनिमेट कर रहे हैं गारंटी है" उस जगह में, अधिक सांसारिक समस्याओं, क्या fromValue और toValue के लिए उपयोग करने के लिए (वे एक CAShapeLayer, या एक CGPath होना चाहिए?) की तरह, के लिए एनीमेशन जोड़ने के लिए क्या आदि आता है (layer, या mask?),

ऐसा लगता है कि इतने सारे चर थे; कौन सा संयोजन मुझे वह एनीमेशन देगा जो मैं ढूंढ रहा था?

उत्तर

11

पहला महत्वपूर्ण बिंदु दो बेजियर पथों को समान रूप से बनाना है, इसलिए आयताकार अधिक जटिल आकार के लिए एक (तुच्छ) एनालॉग है।

// the complex bezier path 
let initialPoint = CGPoint(x: 0, y: 0) 
let curveStart = CGPoint(x: 0, y: (rect.size.height) * (0.2)) 
let curveControl = CGPoint(x: (rect.size.width) * (0.6), y: (rect.size.height) * (0.5)) 
let curveEnd = CGPoint(x: 0, y: (rect.size.height) * (0.8)) 
let firstCorner = CGPoint(x: 0, y: rect.size.height) 
let secondCorner = CGPoint(x: rect.size.width, y: rect.size.height) 
let thirdCorner = CGPoint(x: rect.size.width, y: 0) 

var myBezierArc = UIBezierPath() 
myBezierArc.moveToPoint(initialPoint) 
myBezierArc.addLineToPoint(curveStart) 
myBezierArc.addQuadCurveToPoint(curveEnd, controlPoint: curveControl) 
myBezierArc.addLineToPoint(firstCorner) 
myBezierArc.addLineToPoint(secondCorner) 
myBezierArc.addLineToPoint(thirdCorner) 

सरल 'तुच्छ' बेज़ियर पथ, कि, एक आयत बनाता है बिल्कुल वैसा ही है, लेकिन इतना है कि ऐसा लगता है वहाँ नहीं होने के लिए controlPoint सेट है:

let curveControl = CGPoint(x: 0, y: (rect.size.height) * (0.5)) 

(addQuadCurveToPoint निकालने का प्रयास करें रेखा एक बहुत ही अजीब एनीमेशन पाने के लिए)

और अंत में, एनीमेशन आदेश:

let myAnimation = CABasicAnimation(keyPath: "path") 

if (isArcVisible == true) { 
    myAnimation.fromValue = myBezierArc.CGPath 
    myAnimation.toValue = myBezierTrivial.CGPath 
} else { 
    myAnimation.fromValue = myBezierTrivial.CGPath 
    myAnimation.toValue = myBezierArc.CGPath 
}  
myAnimation.duration = 0.4 
myAnimation.fillMode = kCAFillModeForwards 
myAnimation.removedOnCompletion = false 

myImageView.layer.mask.addAnimation(myAnimation, forKey: "animatePath") 

यदि कोई दिलचस्पी है, तो परियोजना here है।

+0

उदाहरण के लिए धन्यवाद, और आपके प्रश्न में सभी लिंक। इस मुद्दे के आसपास चर्चाओं को पढ़ने के लिए यह दिलचस्प और जानकारीपूर्ण है। – rdelmar

+0

एनीमेशन कमांड में tovalue (myBezierTrivial.CGPath) कैसे सेट करें? – ABJ

3

मैं आपके उदाहरण और कुछ लिंक में उल्लिखित तकनीकों का उपयोग करके स्क्वायर एनीमेशन को सर्कल करने का प्रयास करने के लिए आपके उदाहरण से प्रेरित था। मैं इसे बहुभुज एनीमेशन के लिए एक और सामान्य सर्कल के रूप में विस्तारित करना चाहता हूं, लेकिन वर्तमान में यह केवल वर्गों के लिए काम करता है। मेरे पास RDPolyCircle (CAShapeLayer का उप-वर्ग) नामक एक वर्ग है जो भारी भारोत्तोलन करता है। यहाँ उसका कोड,

@interface RDPolyCircle() 
@property (strong,nonatomic) UIBezierPath *polyPath; 
@property (strong,nonatomic) UIBezierPath *circlePath; 
@end 

@implementation RDPolyCircle { 
    double cpDelta; 
    double cosR; 
} 

-(instancetype)initWithFrame:(CGRect) frame numberOfSides:(NSInteger)sides isPointUp:(BOOL) isUp isInitiallyCircle:(BOOL) isCircle { 
    if (self = [super init]) { 
     self.frame = frame; 
     _isPointUp = isUp; 
     _isExpandedPolygon = !isCircle; 

     double radius = (frame.size.width/2.0); 
     cosR = sin(45 * M_PI/180.0) * radius; 
     double fractionAlongTangent = 4.0*(sqrt(2)-1)/3.0; 
     cpDelta = fractionAlongTangent * radius * sin(45 * M_PI/180.0); 

     _circlePath = [self createCirclePathForFrame:frame]; 
     _polyPath = [self createPolygonPathForFrame:frame numberOfSides:sides]; 
     self.path = (isCircle)? self.circlePath.CGPath : self.polyPath.CGPath; 
     self.fillColor = [UIColor clearColor].CGColor; 
     self.strokeColor = [UIColor blueColor].CGColor; 
     self.lineWidth = 6.0; 
    } 
    return self; 
} 


-(UIBezierPath *)createCirclePathForFrame:(CGRect) frame { 

    CGPoint ctr = CGPointMake(frame.origin.x + frame.size.width/2.0, frame.origin.y + frame.size.height/2.0); 

    // create a circle using 4 arcs, with the first one symmetrically spanning the y-axis 
    CGPoint leftUpper = CGPointMake(ctr.x - cosR, ctr.y - cosR); 
    CGPoint cp1 = CGPointMake(leftUpper.x + cpDelta, leftUpper.y - cpDelta); 

    CGPoint rightUpper = CGPointMake(ctr.x + cosR, ctr.y - cosR); 
    CGPoint cp2 = CGPointMake(rightUpper.x - cpDelta, rightUpper.y - cpDelta); 
    CGPoint cp3 = CGPointMake(rightUpper.x + cpDelta, rightUpper.y + cpDelta); 

    CGPoint rightLower = CGPointMake(ctr.x + cosR, ctr.y + cosR); 
    CGPoint cp4 = CGPointMake(rightLower.x + cpDelta, rightLower.y - cpDelta); 
    CGPoint cp5 = CGPointMake(rightLower.x - cpDelta, rightLower.y + cpDelta); 

    CGPoint leftLower = CGPointMake(ctr.x - cosR, ctr.y + cosR); 
    CGPoint cp6 = CGPointMake(leftLower.x + cpDelta, leftLower.y + cpDelta); 
    CGPoint cp7 = CGPointMake(leftLower.x - cpDelta, leftLower.y - cpDelta); 
    CGPoint cp8 = CGPointMake(leftUpper.x - cpDelta, leftUpper.y + cpDelta); 

    UIBezierPath *circle = [UIBezierPath bezierPath]; 
    [circle moveToPoint:leftUpper]; 
    [circle addCurveToPoint:rightUpper controlPoint1:cp1 controlPoint2:cp2]; 
    [circle addCurveToPoint:rightLower controlPoint1:cp3 controlPoint2:cp4]; 
    [circle addCurveToPoint:leftLower controlPoint1:cp5 controlPoint2:cp6]; 
    [circle addCurveToPoint:leftUpper controlPoint1:cp7 controlPoint2:cp8]; 
    [circle closePath]; 
    circle.lineCapStyle = kCGLineCapRound; 
    return circle; 
} 


-(UIBezierPath *)createPolygonPathForFrame:(CGRect) frame numberOfSides:(NSInteger) sides { 
    CGPoint leftUpper = CGPointMake(self.frame.origin.x, self.frame.origin.y); 
    CGPoint cp1 = CGPointMake(leftUpper.x + cpDelta, leftUpper.y); 

    CGPoint rightUpper = CGPointMake(self.frame.origin.x + self.frame.size.width, self.frame.origin.y); 
    CGPoint cp2 = CGPointMake(rightUpper.x - cpDelta, rightUpper.y); 
    CGPoint cp3 = CGPointMake(rightUpper.x, rightUpper.y + cpDelta); 

    CGPoint rightLower = CGPointMake(self.frame.origin.x + self.frame.size.width, self.frame.origin.y + self.frame.size.height); 
    CGPoint cp4 = CGPointMake(rightLower.x , rightLower.y - cpDelta); 
    CGPoint cp5 = CGPointMake(rightLower.x - cpDelta, rightLower.y); 

    CGPoint leftLower = CGPointMake(self.frame.origin.x, self.frame.origin.y + self.frame.size.height); 
    CGPoint cp6 = CGPointMake(leftLower.x + cpDelta, leftLower.y); 
    CGPoint cp7 = CGPointMake(leftLower.x, leftLower.y - cpDelta); 
    CGPoint cp8 = CGPointMake(leftUpper.x, leftUpper.y + cpDelta); 

    UIBezierPath *square = [UIBezierPath bezierPath]; 
    [square moveToPoint:leftUpper]; 
    [square addCurveToPoint:rightUpper controlPoint1:cp1 controlPoint2:cp2]; 
    [square addCurveToPoint:rightLower controlPoint1:cp3 controlPoint2:cp4]; 
    [square addCurveToPoint:leftLower controlPoint1:cp5 controlPoint2:cp6]; 
    [square addCurveToPoint:leftUpper controlPoint1:cp7 controlPoint2:cp8]; 
    [square closePath]; 
    square.lineCapStyle = kCGLineCapRound; 
    return square; 
} 


-(void)toggleShape { 
    if (self.isExpandedPolygon) { 
     [self restore]; 
    }else{ 

     CABasicAnimation *expansionAnimation = [CABasicAnimation animationWithKeyPath:@"path"]; 
     expansionAnimation.fromValue = (__bridge id)(self.circlePath.CGPath); 
     expansionAnimation.toValue = (__bridge id)(self.polyPath.CGPath); 
     expansionAnimation.duration = 0.5; 
     expansionAnimation.fillMode = kCAFillModeForwards; 
     expansionAnimation.removedOnCompletion = NO; 

     [self addAnimation:expansionAnimation forKey:@"Expansion"]; 
     self.isExpandedPolygon = YES; 
    } 
} 


-(void)restore { 
    CABasicAnimation *contractionAnimation = [CABasicAnimation animationWithKeyPath:@"path"]; 
    contractionAnimation.fromValue = (__bridge id)(self.polyPath.CGPath); 
    contractionAnimation.toValue = (__bridge id)(self.circlePath.CGPath); 
    contractionAnimation.duration = 0.5; 
    contractionAnimation.fillMode = kCAFillModeForwards; 
    contractionAnimation.removedOnCompletion = NO; 

    [self addAnimation:contractionAnimation forKey:@"Contraction"]; 
    self.isExpandedPolygon = NO; 
} 

दृश्य नियंत्रक से, मैं इस परत का एक उदाहरण बना सकते हैं और एक साधारण दृश्य की परत में जोड़ने, तो एक बटन धक्का पर एनिमेशन करना है,

#import "ViewController.h" 
#import "RDPolyCircle.h" 

@interface ViewController() 
@property (weak, nonatomic) IBOutlet UIView *circleView; // a plain UIView 150 x 150 centered in the superview 
@property (strong,nonatomic) RDPolyCircle *shapeLayer; 
@end 

@implementation ViewController 

- (void)viewDidLoad { 
    [super viewDidLoad]; 

    // currently isPointUp and numberOfSides are not implemented (the shape created has numberOfSides=4 and isPointUp=NO) 
    // isInitiallyCircle is implemented 
    self.shapeLayer = [[RDPolyCircle alloc] initWithFrame:self.circleView.bounds numberOfSides: 4 isPointUp:NO isInitiallyCircle:YES]; 
    [self.circleView.layer addSublayer:self.shapeLayer]; 
} 


- (IBAction)toggleShape:(UIButton *)sender { 
    [self.shapeLayer toggleShape]; 
} 

परियोजना यहां पाई जा सकती है, http://jmp.sh/iK3kuVs

+0

क्या मैं अपना सरल 'func bezierPathWithPolygonInRect (Rect: CGRect, numberOfSides: Int) साझा कर सकता हूं -> UIBezierPath {'method – coco

9

एक और दृष्टिकोण एक प्रदर्शन लिंक का उपयोग करना है। यह एक टाइमर की तरह है, सिवाय इसके कि यह प्रदर्शन के अद्यतन के साथ समन्वयित है।उसके बाद डिस्प्ले लिंक का हैंडलर एनीमेशन के किसी भी विशेष बिंदु पर दिखने के अनुसार दृश्य को संशोधित करता है।

उदाहरण के लिए, यदि आप 0 से 50 अंक से मास्क के कोनों के गोलाकार को एनिमेट करना चाहते हैं, तो आप निम्न की तरह कुछ कर सकते हैं, जहां percent 0.0 और 1.0 के बीच एक मान है जो दर्शाता है कि एनीमेशन का प्रतिशत क्या है किया:

let path = UIBezierPath(rect: imageView.bounds) 
let mask = CAShapeLayer() 
mask.path = path.CGPath 
imageView.layer.mask = mask 

let animation = AnimationDisplayLink(duration: 0.5) { percent in 
    let cornerRadius = percent * 50.0 
    let path = UIBezierPath(roundedRect: self.imageView.bounds, cornerRadius: cornerRadius) 
    mask.path = path.CGPath 
} 

कहाँ:

class AnimationDisplayLink : NSObject { 
    var animationDuration: CGFloat 
    var animationHandler: (percent: CGFloat) ->() 
    var completionHandler: (() ->())? 

    private var startTime: CFAbsoluteTime! 
    private var displayLink: CADisplayLink! 

    init(duration: CGFloat, animationHandler: (percent: CGFloat)->(), completionHandler: (()->())? = nil) { 
     animationDuration = duration 
     self.animationHandler = animationHandler 
     self.completionHandler = completionHandler 

     super.init() 

     startDisplayLink() 
    } 

    private func startDisplayLink() { 
     startTime = CFAbsoluteTimeGetCurrent() 
     displayLink = CADisplayLink(target: self, selector: "handleDisplayLink:") 
     displayLink.addToRunLoop(NSRunLoop.currentRunLoop(), forMode: NSRunLoopCommonModes) 
    } 

    private func stopDisplayLink() { 
     displayLink.invalidate() 
     displayLink = nil 
    } 

    func handleDisplayLink(displayLink: CADisplayLink) { 
     let elapsed = CFAbsoluteTimeGetCurrent() - startTime 
     var percent = CGFloat(elapsed)/animationDuration 

     if percent >= 1.0 { 
      stopDisplayLink() 
      animationHandler(percent: 1.0) 
      completionHandler?() 
     } else { 
      animationHandler(percent: percent) 
     } 
    } 
} 

प्रदर्शन लिंक दृष्टिकोण के आधार पर कि यह कुछ संपत्ति है कि अन्यथा unanimatable है चेतन करने के लिए इस्तेमाल किया जा सकता है। यह आपको एनीमेशन के दौरान अंतरिम स्थिति को ठीक से निर्देशित करने देता है।

यदि आप CAAnimation या UIKit ब्लॉक-आधारित एनीमेशन का उपयोग कर सकते हैं, तो शायद यह जाने का तरीका है। लेकिन डिस्प्ले लिंक कभी-कभी एक अच्छा फॉलबैक दृष्टिकोण हो सकता है।

+0

यह एक दिन में बहुत आसान हो सकता है, जब' CAAnimation' और' UIKit' कुछ संभाल नहीं पाएगा! – coco

संबंधित मुद्दे