animation: agrandir un rectangle

RocouRocou Membre
21:02 modifié dans API AppKit #1
Bonjour,

Je voudrais faire une "animation" toute simple; je voudrais agrandir un rectangle. D'un point au départ, le rectangle doit prendre environ la taille des trois quarts de ma vue.

J'ai tout d'abord tracé un rectangle en augmentant sa taille et en modifiant ses coordonnées dans une boucle. ça fonctionne mais c'est moche et on ne peut pas fixer la vitesse du "zoom" de mon rectangle.
J'ai alors utilisé un timer: c'est mieux mais ça reste très moche car pour avoir un effet de zoom suffisamment rapide, je dois incrémenter la taille de mon rectangle d'une valeur trop importante: "l'animation" est saccadée (pas dramatique mais c'est quand même bien moche).

J'essaie maintenant de regarder du côté des "Layer" mais je sèche complètement. Y-a-t-il une recette simple pour afficher le zoom animé d'un rectangle?

Réponses

  • AliGatorAliGator Membre, Modérateur
    21:02 modifié #2
    Crée un layer : CALayer* rectLayer = [CALayer layer];
    Règle les propriétés du layer : rectLayer.borderColor, rectLayer.borderWidth, ...
    Règle la frame initiale du layer : rectLayer.frame = ...
    Ajoute le layer à  la vue dans laquelle tu veux l'afficher : [maVue addSubview:rectLayer];
    Anime la frame avec CoreAnimation (soit une animation implicite, soit explicite si tu veux plus de contrôle) --> cf la doc "CoreAnimation Programming Guide" ou le "CoreAnimation Cook Book" etc.
  • RocouRocou Membre
    21:02 modifié #3
    dans 1266227739:

    Crée un layer : CALayer* rectLayer = [CALayer layer];
    Règle les propriétés du layer : rectLayer.borderColor, rectLayer.borderWidth, ...
    Règle la frame initiale du layer : rectLayer.frame = ...
    Ajoute le layer à  la vue dans laquelle tu veux l'afficher : [maVue addSubview:rectLayer];
    Anime la frame avec CoreAnimation (soit une animation implicite, soit explicite si tu veux plus de contrôle) --> cf la doc "CoreAnimation Programming Guide" ou le "CoreAnimation Cook Book" etc.


    Merci.
    J'ai repris un bout de code sur le site de Philippe en tentant de l'adapter mais cela ne fonctionne pas du tout.
    J'ai mis ce code dans la méthode initWithFrame de ma vue:
    // root of the Layer-Tree<br />	CALayer * rootLayer=[CALayer layer];<br />	<br />	[self setLayer:rootLayer];<br />	[self setWantsLayer:YES];<br /><br />	monLayer=rootLayer ;<br />	<br />	monLayer.frame=CGRectMake(50.0,50.0, 100.0,50.0);<br />	monLayer.borderWidth=3.0;<br />	monLayer.cornerRadius=5.0;<br />	<br />	monLayer.borderColor=CGAutorelease(CGColorCreateGenericRGB(1.0, 0., 0., 0.2));&nbsp; <br />	monLayer.backgroundColor=CGAutorelease(CGColorCreateGenericRGB(0., 0., 1., 0.5));&nbsp; <br />	<br />	monLayer.shadowOffset=CGSizeMake(5.0,-5.0);<br />	monLayer.shadowColor=CGAutorelease(CGColorCreateGenericRGB(1.0, 0., 0., 1.));&nbsp; <br />	monLayer.shadowOpacity=0.8;<br />	monLayer.shadowRadius=3.0;		<br />	<br />	[rootLayer addSublayer:monLayer];<br />	<br />
    


    Dans le fichier .h de ma vue j'ai bien évidemment:
    CALayer * monLayer;
    


    Il ne se passe rien du tout. Je n'ai même pas l'affichage du rectangle.
  • RocouRocou Membre
    21:02 modifié #4
    Bon, ça y est, ça fonctionne  :)

    Par contre, c'est maintenant la méthode drawRect de ma vue qui ne permet plus de dessiner quoi que ce soit. Est-ce incompatible avec un "layer"?
    Le "layer" s'affiche-t-il par dessus les éléments dessinés par drawRect?
  • AliGatorAliGator Membre, Modérateur
    février 2010 modifié #5
    et si tu mets sa fillColor à  clearColor (transparent) ?
    T'as fait des tests ? T'as regardé si tu affichait des trucs quand ton rectangle était petit et que ce que tu dessinais était sensé être visible même si jamais on suppose que le layer est opaque et masque ce contenu (pour savoir si ça vient bien de là ) ? T'as mis un NSLog dans le drawRect ?
    Bref, t'as essayé quoi ?


    PS : Heu pour ton code précédent, ça fait un peu "copier/coller sans réfléchir" on dirait... parce que quand je vois [tt][rootLayer addSublayer:monLayer][/tt] alors que [tt]monLayer = rootLayer[/tt] plus haut... hum hum...
  • RocouRocou Membre
    21:02 modifié #6
    dans 1266240241:

    et si tu mets sa fillColor à  clearColor (transparent) ?
    T'as fait des tests ? T'as regardé si tu affichait des trucs quand ton rectangle était petit et que ce que tu dessinais était sensé être visible même si jamais on suppose que le layer est opaque et masque ce contenu (pour savoir si ça vient bien de là ) ? T'as mis un NSLog dans le drawRect ?
    Bref, t'as essayé quoi ?

    Oui, à  l'origine j'affiche un graphe à  coup de NSBezierPath. ça fonctionnait très bien.
    Je voulais qu'en cliquant sur un élément du graphe, celui-ci s'affichait en plus grand (afin d'en voir les détail) au sein d'un rectangle qui s'affiche en sur-impression sur le graphe. cf les photos d'écran (pour le moment, il n'y a rien dans le rectangle).

    Je voudrais que ce rectangle s'agrandisse de façon fluide. Dés que j'utilise un layer, le graphe ne s'affiche plus.

    Cela dit, je suis déçu, CATransform3DScale est destructeur: un zoom et un retour à  l'origine, il ne reste plus grand chose du rectangle original. C'est du bitmap. Y-t-il une fonction similaire pour fluidifier un changement d'échelle d'une forme en NSBezierPath?

    dans 1266240241:

    PS : Heu pour ton code précédent, ça fait un peu "copier/coller sans réfléchir" on dirait... parce que quand je vois [tt][rootLayer addSublayer:monLayer][/tt] alors que [tt]monLayer = rootLayer[/tt] plus haut... hum hum...

    C'est parce que j'ai fait mille tests et qu'au moment de poster plus rien ne fonctionnait.  :D

  • AliGatorAliGator Membre, Modérateur
    21:02 modifié #7
    Et alors, je vois pas trop ton souci ?
    D'après ta capture ecran2.jpg, on voit que le layer est opaque.
    Si tu veux qu'il soit transparent, met le transparent comme je t'ai mentionné, via clearColor.
    Si tu veux qu'il soit opaque mais qu'une version agrandie de ton graphe soit dessinée dessus, il faut ton code qui va dessiner ton graphe dedans (enfin plutôt la partie de ton graphe qui t'intéresse)

    Note que tu n'es pas obligé de passer par un CALayer, tu peux passer par une NSView.
    Vu ce que tu me décris de ton problème, si tu as déjà  un pendant "Vue" de ton MVC pour représenter ton graphe, sous forme d'une sous-classe GraphView de NSView, et donc puisque tu as déjà  une GraphView, mettre une 2e GraphView en subview de cette dernière mais qui affiche ton graphe en plus grand (facteur de zoom à  prévoir dans ton code de GraphView*).

    Une fois que tu auras réussi à  afficher cette version zoomée de ton graphe directement dans la taille qui t'intéresse, là  on pourra regarder le côté animation (typiquement plutôt que de modifier la frame ce qui risque de faire appeler le drawRect, appliquer un scaleTransform de 0.1 à  1.0 par exemple) de cette partie zoomée.


    Sinon, c'est normal que CGAffineTransformMakeScale soit "bitmap", ça joue sur la matrice de projection, elle n'intervient qu'au moment du mapping de ton dessin sur les pixels de ton écran : donc si tu dessines ton truc à  l'échelle 1 et que tu le projettes à  l'échelle 2 il sera forcément pixelisé puisqu'il a été dessiné dans ton drawRect à  l'échelle 1. Il faut appliquer le facteur de zoom aux coordonnées des points de contrôle de ton bezierPath, pas au bezierPath une fois ce dernier calculé/interpolé évidemment.

    C'est comme si tu calculais les nombres entre 0 et 1 avec une résolution de 1/10e, puis qu'après coup tu décidais de "zoomer x10" pour avoir les nombres entre 0 et 10 : tu vas d'abord calculer {0.0, 0.1, 0.2, 0.3, ... 0.8, 0.9, 1.0} et si tu zoomes après avoir fait le calcul, tu auras {0, 1, 2, 3, ... 8,9,10}. Donc une résolution à  l'unité, 10x moins précise qu'avant. Alors que si tu zoomes d'abord sur les points / les extrêmités et donc détermine que tes bornes sont [0 10], zoom x10 de [0 1], et que tu fais le calcul ensuite avec ta résolution 1/10e, là  tu as une résolution de sortie bcp plus fine.
  • RocouRocou Membre
    21:02 modifié #8
    dans 1266245090:

    Et alors, je vois pas trop ton souci ?

    Les photos d'écrans que j'ai postées représentent ce que je voudrais obtenir  :)
    Il n'y a pas d'utilisation de layer dans ce cas. Je fais, en gros, ce que tu décris, mais le résultat n'est pas très fluide (et c'est trop lent).

    dans 1266245090:

    Il faut appliquer le facteur de zoom aux coordonnées des points de contrôle de ton bezierPath, pas au bezierPath une fois ce dernier calculé/interpolé évidemment.

    Appliquer un facteur zoom aux cordonnées de mon bezierPath, c'est ce que j'ai fait mais je ne comprends pas ce que signifie "pas au bezierPath une fois ce dernier calculé/interpolé ".

    dans 1266245090:

    C'est comme si tu calculais les nombres entre 0 et 1 avec une résolution de 1/10e, puis qu'après coup tu décidais de "zoomer x10" pour avoir les nombres entre 0 et 10 : tu vas d'abord calculer {0.0, 0.1, 0.2, 0.3, ... 0.8, 0.9, 1.0} et si tu zoomes après avoir fait le calcul, tu auras {0, 1, 2, 3, ... 8,9,10}. Donc une résolution à  l'unité, 10x moins précise qu'avant. Alors que si tu zoomes d'abord sur les points / les extrêmités et donc détermine que tes bornes sont [0 10], zoom x10 de [0 1], et que tu fais le calcul ensuite avec ta résolution 1/10e, là  tu as une résolution de sortie bcp plus fine.

    Oui, c'est le principe du "vectoriel" mais en pratique, où le coder?
    J'ai une méthode qui modifie les coordonnées de mon bezierPath et qui se termine par [self setNeedsDisplay:YES]; afin de réexécuter drawRect. Cette méthode est appelé par un Timer jusqu'à  ce que mon rectangle soit à  la bonne taille.
  • AliGatorAliGator Membre, Modérateur
    21:02 modifié #9
    dans 1266246542:

    dans 1266245090:

    Et alors, je vois pas trop ton souci ?

    Les photos d'écrans que j'ai postées représentent ce que je voudrais obtenir  :)
    Ah ok !
    dans 1266246542:
    Appliquer un facteur zoom aux cordonnées de mon bezierPath, c'est ce que j'ai fait mais je ne comprends pas ce que signifie "pas au bezierPath une fois ce dernier calculé/interpolé ".
    Ben quand tu construits ton bezierPath et lui fournit les points de contrôle, ces points doivent déjà  prendre en compte le facteur zoom. Si tu appliques le zoom après, par exemple sur la CTM du contexte graphique et pas sur ton bezierPath, ou juste avant de dessiner ton NSBezierPath, ça ne fera que projeter un BezierPath -- calculé à  l'échelle 1 -- en zoom x2.

    Mets ton code de dessin de tes BezierPath pour voir, ça sera plus clair à  identifier.
    dans 1266246542:

    Oui, c'est le principe du "vectoriel" mais en pratique, où le coder?
    Ben quand tu calcules le(s) BezierPath(s) de ton graphe (ce qui peut éventuellement être dans ton drawRect, à  moins que tu ne le calcules en amont)

    dans 1266246542:
    J'ai une méthode qui modifie les coordonnées de mon bezierPath et qui se termine par [self setNeedsDisplay:YES]; afin de réexécuter drawRect. Cette méthode est appelé par un Timer jusqu'à  ce que mon rectangle soit à  la bonne taille.
    En effet, côté optimisation (et surtout risque de "flicking" qui mènerait à  un manque de fluidité flagrant) c'est pas top.

    Tu n'as pas à  calculer 36 fois ton BezierPath, il faut optimiser.
    - Il te faut une méthode de dessin qui prend un facteur de zoom en paramètre et crée les NSBezierPath pour faire ton graphe en prennant en compte ce facteur de zoom lors de la construction des bezierPath
    - Tu appelles cette méthode une première fois avec un zoom adéquat pour faire tenir ton graphe dans ta vue initiale
    - Quand tu veux afficher la partie zoomée, tu appelles cette méthode avec un facteur de zoom plus grand, qui correspond au zoom que tu veux au final de ton animation, le facteur d'échelle final pour ton zoom quoi. Et tu dessines ce graphe agrandi dans une autre NSView
    - Quand tu auras fait ça, on pourra reparler des animations, de rajouter un effet pour que cette vue affichant le graphe agrandi s'affiche avec une animation pour l'agrandir de façon fluide, via CoreAnimation.

    Mais commence déjà  par afficher le graphe en version normale et zoomée, l'animation se fera toute seule avec CoreAnim ensuite : pas besoin de calculer les bezierPath aux zooms intermédiaires et de refaire toute la passe de calcul à  chaque frame (ça reste quand même violent, c'est le genre de calcul qui prend un peu de temps mine de rien) !! On se contentera de demander à  CoreAnim d'utiliser la version grande taille/zoomée de ton graphe, et de faire une interpolation sur la CTM pour la faire passer d'une projection scale x0.1 à  une projection avec scale x1. Mais pour ça faut que tu arrives à  afficher ton graphe en grande taille (la zone qui t'intéresse, avec zoom vectoriel et pas bitmap, donc recalcul de ton BezierPath avec les coordonnées multipliées par ton facteur de zoom), sans parler d'animation pour l'instant.
  • RocouRocou Membre
    21:02 modifié #10
    Un grand merci pour cette piste. J'ai beaucoup de chose à  réécrire pour faire quelque chose de propre mais je reviendrai t'ennuyer, n'en doute pas  :D
Connectez-vous ou Inscrivez-vous pour répondre.