2015-04-06 12 views
5

ho sotto lo schermo:multiplo di UIView con UIBezierPath

enter image description here

Quando clicco Get More Button, si aggiungerà nuovo pulsante arrotondato. Ho aggiunto Pan Gesture sullo UIView in cui sono stati aggiunti tutti i pulsanti arrotondati, che quindi estraggono uno UIBezierPath solo dal pulsante Arrotondato. Fino a questo punto, tutto funziona.

Ora, voglio il seguente:

  1. ho genitore UIView chiamato playGroundView, che contiene tutte pulsante arrotondato. Questo disegnerà un percorso Bezier per tutti quei pulsanti.
  2. Una volta disegnato il tracciato di Bezier per tutti i pulsanti, faccio clic su Sposta pulsante per animare tutti i pulsanti arrotondati contemporaneamente.
  3. Una volta che tutto il pulsante si sposta sull'estremità del percorso di Bézier, posso quindi disegnare nuovamente il percorso di Bezier (aggiungere più passaggi per il pulsante arrotondato) allo stesso percorso per lo stesso pulsante.
  4. Dopo questo, il pulsante Spostamento finale si animerà interamente per lo stesso pulsante in una sola volta.

fare riferimento al codice di sotto per parco giochi UIView sottoclasse:

@interface PlayGroundView : UIView 
@property (nonatomic, weak) PlayGroundViewController *playViewController; 
@end 

#import "PlayGroundView.h" 
#import "RoundedButton.h" 
#import "PlayGroundViewController.h" 

@interface PlayGroundView() { 
    UIBezierPath *path; 
    BOOL isDrawPointInside; 
} 

@end 

@implementation PlayGroundView 

#pragma mark - View Methods - 
- (id)initWithCoder:(NSCoder *)aDecoder { 
    if (self = [super initWithCoder:aDecoder]) 
     [self commonInit]; 
    return self; 
} 

- (id)initWithFrame:(CGRect)frame { 
    if (self = [super initWithFrame:frame]) 
     [self commonInit]; 
    return self; 
} 

- (void)drawRect:(CGRect)rect { 
    [[UIColor redColor] setStroke]; 
    [path stroke]; 
} 

#pragma mark - Action Methods - 
- (void)pan:(UIPanGestureRecognizer *)pan { 
    CGPoint currentPoint = [pan locationInView:self]; 
    // NSLog(@"currentPoint: %@", NSStringFromCGPoint(currentPoint)); 
    if (pan.state == UIGestureRecognizerStateBegan) { 
     NSMutableArray *buttonAry = [NSMutableArray array]; 
     buttonAry = self.playViewController.rBtnOpponentPlayerList; 
     [buttonAry addObjectsFromArray:self.playViewController.rBtnPlayerList]; 
     for (int i=0; i<buttonAry.count; i++) { 
      UIButton *btn = [buttonAry objectAtIndex:i]; 
      CGRect nearByRect = CGRectMake(btn.frame.origin.x - 20, btn.frame.origin.y - 20, btn.frame.size.width + 40, btn.frame.size.height + 40); 
      if (CGRectContainsPoint(nearByRect, currentPoint)) { 
       isDrawPointInside = YES; 
       [path moveToPoint:btn.center]; 
       //    NSLog(@"point is inside...."); 
      } 
     } 
    } 
    if (pan.state == UIGestureRecognizerStateChanged) { 
     if (isDrawPointInside) { 
      [path addLineToPoint:currentPoint]; 
      [self setNeedsDisplay]; 
     } 
    } 

    if (pan.state == UIGestureRecognizerStateEnded) { 
     isDrawPointInside = NO; 
     //  NSLog(@"pan ended"); 
    } 
} 

#pragma mark - Helper Methods - 
- (void)commonInit { 
    path = [UIBezierPath bezierPath]; 
    path.lineWidth = 3.0; 

    // Capture touches 
    UIPanGestureRecognizer *pan = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(pan:)]; 
    pan.maximumNumberOfTouches = pan.minimumNumberOfTouches = 1; 
    [self addGestureRecognizer:pan]; 

    // Erase with long press 
    [self addGestureRecognizer:[[UILongPressGestureRecognizer alloc] initWithTarget:self action:@selector(erase)]]; 
} 

- (void)erase { 
    path = [UIBezierPath bezierPath]; 
    path.lineWidth = 3.0; 

    [self setNeedsDisplay]; 
} 

@end 

non sono ancora in grado di gestire tutto lo stato pulsante arrotondato. Fammi sapere il modo migliore per gestire tutto il passaggio di Button arrotondato durante lo spostamento.

ho fatto riferimento a: http://nachbaur.com/blog/core-animation-part-4

Il mio codice demo completo è disponibile all'indirizzo: https://www.dropbox.com/s/f0ri0bff8ioxtpz/FreeHandDrawingDemo%202.zip?dl=0

risposta

0

Per ottenere le cose, ho usato la categoria UIBezierPath + Points. Oltre a questo, nel mio codice seguente classi e logica:

In PlayGroundView, ho modificato il codice in modo drawRect, codice resto è lo stesso:

- (void)drawRect:(CGRect)rect { 
    [[UIColor redColor] setStroke]; 
    for (int i=0; i<[self subviews].count; i++) { 
     RoundButton *tmpPlayerButton = [self.subviews objectAtIndex:i]; 
     for (UIBezierPath *bezierPath in tmpPlayerButton.allPathsOfPlayer) { 
      [bezierPath strokeWithBlendMode:kCGBlendModeNormal alpha:1.0]; 
     } 
    } 
    [path stroke]; 
} 

Nella mia classe ViewController, a ottenere più pulsanti di seguito il codice va:

- (IBAction)getBallPressed:(id)sender { 
    [self moveBalls]; 

// self.playView.viewController = self; 

    int xPos = self.view.frame.size.width-30; 
    int yPos = self.view.frame.size.height-30; 
    int btnXPos = arc4random_uniform(xPos); 
    int btnYPos = arc4random_uniform(yPos); 

    RoundButton *newButton = [[RoundButton alloc] initWithFrame:CGRectMake(btnXPos, btnYPos, 30, 30)]; 
    [newButton setBackgroundColor:[UIColor redColor]]; 
    newButton.layer.cornerRadius = newButton.frame.size.height/2; 
    [newButton setTitle:[self randomStringWithLength:1] forState:UIControlStateNormal]; 

    UILongPressGestureRecognizer *longGesture = [[UILongPressGestureRecognizer alloc] initWithTarget:self action:@selector(longGestureRecognized:)]; 
    [newButton addGestureRecognizer:longGesture]; 

    [self.playView.playerArray addObject:newButton]; 
    [self.playView addSubview:newButton]; 
} 

- (void)moveBalls { 
    for (int i=0; i<[self.playView subviews].count; i++) { 
     RoundButton *playerButton = [self.playView.subviews objectAtIndex:i]; 
     if (playerButton.isEditPath) { 
      id point = [[[playerButton.allPathsOfPlayer lastObject] points] lastObject]; 
      CGPoint lastPoint = [point CGPointValue]; 
      NSLog(@"lastPoint: %@", NSStringFromCGPoint(lastPoint)); 
      [playerButton setCenter:lastPoint]; 
     } 
    } 
} 

In pulsante Sposta, sotto il codice va, che mi permette di spostare tutto il mio tasto rotondo allo stesso tempo che è la mia esigenza esatta:

- (void)playAnimation { 
    for (int i=0; i<self.playView.subviews.count; i++) { 
     //Prepare the animation - we use keyframe animation for animations of this complexity 
     CAKeyframeAnimation *pathAnimation = [CAKeyframeAnimation animationWithKeyPath:@"position"]; 
     //Set some variables on the animation 
     pathAnimation.calculationMode = kCAAnimationPaced; 

     //We want the animation to persist - not so important in this case - but kept for clarity 
     //If we animated something from left to right - and we wanted it to stay in the new position, 
     //then we would need these parameters 
     pathAnimation.fillMode = kCAFillModeForwards; 
     pathAnimation.removedOnCompletion = NO; 
     pathAnimation.duration = 2.0; 
     pathAnimation.repeatCount = 0; 

     RoundButton *tmpRoundButton; 
     UIBezierPath *path; 

     tmpRoundButton = [self.playView.subviews objectAtIndex:i]; 

     for (int j=0; j<tmpRoundButton.allPathsOfPlayer.count; j++) { 
      if (path) 
       [path appendPath:[tmpRoundButton.allPathsOfPlayer objectAtIndex:j]]; 
      else 
       path = [tmpRoundButton.allPathsOfPlayer objectAtIndex:j]; 

      //Now we have the path, we tell the animation we want to use this path - then we release the path 
      pathAnimation.path = [path CGPath]; 
      [tmpRoundButton.layer addAnimation:pathAnimation forKey:@"moveTheSquare"]; 
     } 
     [path moveToPoint:[[[path points] firstObject] CGPointValue]]; 
    } 
    [self.playView setNeedsDisplay]; 
} 
2

darò per scontato che, quando si dice "gestire tutto lo stato pulsante tondo", vuol dire che si desidera avere un modo per sapere che la vista ha iniziato ad animare, qualcosa come una callback viewHasMovedABitAutomatically :) Se è così, per quanto ne so non c'è modo implementato nel framework di CA per farlo. Tuttavia, dai un'occhiata a questa domanda: Core animation progress callback Ecco un modo per fare una soluzione che sembra molto carina, forse puoi lavorare su qualcosa che inizia da qui.

Oh, e una cosa. Vedo che stai usando un riconoscitore di gesti di Pan. Ho appena sfogliato sia il codice che lo scudo '(il tizio nell'altro thread;)) ma penso che stia usando drawInContext per controllare che il livello sia disegnato. Questo potrebbe innescare anche se la tua vista è trascinabile quindi, se questo è il caso, considera l'utilizzo del flag viewIsBeingDragged sul metodo pan e controllalo sul callback.

EDIT: Mi dispiace, questo sembra non avere nulla a che fare con il tuo problema reale. Capisco che quello che intendevi è controllare la velocità dell'animazione in modo che tutti si muovano di conseguenza. Facciamo un approfondimento su questo.

Per quanto ne so, non esiste una cosa come un "passo" di animazione. Animare un'immagine per spostarla di 20 pixel a sinistra non implica necessariamente che l'immagine venga ridisegnata 20 volte. L'immagine sarà resa tante volte quante sono necessarie, quindi non possiamo fare affidamento sul metodo render per calcolarlo. Per quanto ne so, l'unico modo per controllare la velocità dell'animazione è tramite il parametro duration di animateWithDuration:. E tu non conosci la durata, vuoi rendere costante la velocità in modo che la durata sia quella che serve.

Fisica di base: la durata è uguale alla distanza divisa per la velocità, quindi se avessimo la distanza da coprire potremmo facilmente calcolare la durata dell'animazione. All'inizio pensavo che forse UIBezierPath avesse un metodo o un attributo per calcolare la lunghezza totale, ma mi sbagliavo. Tuttavia, in questo thread Get CGPath total length c'è un codice che lo calcola per integrazione numerica, dovresti testarlo per vedere se funziona. Una volta che lo avete, si può fare qualcosa di simile (non ho la prova che, sto solo entrando pseudocodice)

#define SPEED_CALIBRATION 30.0 // or whatever 

-(void)animateView:(UIView*)view withBezierPath:(UIBezierPath*)path { 
    CGFloat distance = [self getBezierPathDistance:path]; 
    CGFloat duration = distance/SPEED_CALIBRATION; 
    [self animateView:view withDuration:duration andBezierPath:path]; 
} 

Qualcosa di simile. getBezierPathDistance è il metodo dalla discussione sopra, supponendo che funzioni, e animateView:withDuration:andBezierPath: è il tuo metodo per eseguire l'animazione non lineare usando la documentazione di CA (non mi ricordo abbastanza tutti i dettagli, ma ricordo che era una lunga fetta di codice ;)).

Mi dispiace se la risposta è un po 'vaga, speriamo che aiuti!

+0

Ciao, non voglio ricevere il callback per il completamento dell'animazione. Il mio requisito è che, voglio spostare tutti i RoundButton contemporaneamente con movimento relativo. Dì se il mio primo percorso del pulsante è 1 cm e il percorso del Secondo pulsante è 2 cm, quindi il primo pulsante dovrebbe raggiungere il primo e il secondo pulsante dovrebbe richiedere il doppio tempo del primo pulsante. Questa animazione dovrebbe essere simultanea. – DShah

+0

Il mio male, non ti ho capito affatto :) Ho un'idea su come farlo, sto modificando la mia risposta in questo momento. – Bartserk