71 votes

Dessiner des segments d'un cercle ou d'un beignet

J'ai essayé de trouver un moyen de dessiner des segments comme illustré dans l'image à l'aide de.

entrez la description de l'image ici

J'aimerais

a) dessine le segment a) inclut les dégradés b) inclut les ombres d) anime le dessin de 0 à n angle

J'ai essayé de le faire avec CGContextAddArc et des appels similaires, mais sans aller très loin.

Quelqu'un peut-il aider?

180voto

David Rönnqvist Points 25290

Il y a beaucoup de pièces à votre question.

Obtenir le chemin d'accès

La création de la voie pour un segment ne devrait pas être trop dur. Il y a deux arcs et deux lignes droites. J'ai déjà expliqué comment vous pouvez décomposer un chemin comme ça donc je ne vais pas le faire ici. Au lieu de cela, je vais être de fantaisie et de créer le chemin de caresser un autre chemin. Bien sûr, vous pouvez lire le détail de la répartition et de construire le chemin de vous-même. L'arc je parle en caressant l'orange à l'arc à l'intérieur du gris pointillé résultat final.

Path to be stroked

Pour la course du chemin, nous avons d'abord besoin d'elle. qui est en fait aussi simple que de déplacer vers le point de départ et le dessin d'un arc de cercle autour du centre de l'angle en cours à l'angle que vous voulez que le segment de la couverture.

CGMutablePathRef arc = CGPathCreateMutable();
CGPathMoveToPoint(arc, NULL,
                  startPoint.x, startPoint.y);
CGPathAddArc(arc, NULL,
             centerPoint.x, centerPoint.y,
             radius,
             startAngle,
             endAngle,
             YES);

Ensuite quand vous avez (l'arc), vous pouvez créer un nouveau segment, en le caressant avec une certaine largeur. La trajectoire résultante va avoir les deux lignes droites et les deux arcs. La course se passe à partir du centre d'une distance égale vers l'intérieur et vers l'extérieur.

CGFloat lineWidth = 10.0;
CGPathRef strokedArc =
    CGPathCreateCopyByStrokingPath(arc, NULL,
                                   lineWidth,
                                   kCGLineCapButt,
                                   kCGLineJoinMiter, // the default
                                   10); // 10 is default miter limit

Dessin

Ensuite il y a le dessin et il y a généralement deux choix principaux: Graphiques de Base en drawRect: ou calques de forme avec le Noyau de l'Animation. Graphiques de base va vous donner le plus de dessin puissant mais Core Animation va vous donner la meilleure performance d'animation. Comme les chemins sont impliqués pur Cora Animation ne fonctionne pas. Vous allez vous retrouver avec d'étranges artefacts. On peut cependant utiliser une combinaison de couches et Graphiques de Base par le dessin de la le contexte graphique de la couche.

Remplissage et en se caressant le segment

Nous avons déjà la forme de base, mais avant de nous ajouter des dégradés et des ombres, il me fera une base de remplissage et de contour (vous disposez d'un trait noir dans votre image).

CGContextRef c = UIGraphicsGetCurrentContext();
CGContextAddPath(c, strokedArc);
CGContextSetFillColorWithColor(c, [UIColor lightGrayColor].CGColor);
CGContextSetStrokeColorWithColor(c, [UIColor blackColor].CGColor);
CGContextDrawPath(c, kCGPathFillStroke);

Qui va mettre quelque chose comme ceci sur l'écran

Filled and stroked shape

Ajout d'ombres

Je vais modifier l'ordre et de faire de l'ombre avant de le dégradé. Pour dessiner l'ombre, nous avons besoin de configurer une ombre pour le contexte et le tirage remplir la forme à dessiner avec l'ombre. Ensuite, nous avons besoin de restaurer le contexte (à l'ombre) et de l'avc de nouveau la forme.

CGColorRef shadowColor = [UIColor colorWithWhite:0.0 alpha:0.75].CGColor;
CGContextSaveGState(c);
CGContextSetShadowWithColor(c,
                            CGSizeMake(0, 2), // Offset
                            3.0,              // Radius
                            shadowColor);
CGContextFillPath(c);
CGContextRestoreGState(c);

// Note that filling the path "consumes it" so we add it again
CGContextAddPath(c, strokedArc);
CGContextStrokePath(c);

À ce point, le résultat est quelque chose comme cela

enter image description here

Dessin du dégradé

Pour le dégradé, nous avons besoin d'un calque de dégradé. Je suis en train de faire un simple de deux couleurs gradient ici, mais vous pouvez le personnaliser tout ce que vous voulez. Pour créer le dégradé, nous avons besoin d'obtenir les couleurs et la couleur adéquate de l'espace. Ensuite, nous pouvons tracer le dégradé sur le dessus de la remplir (mais avant le coup). Nous avons également besoin de masquer le dégradé sur le même chemin qu'avant. Pour ce faire, nous clip le chemin.

CGFloat colors [] = {
    0.75, 1.0, // light gray   (fully opaque)
    0.90, 1.0  // lighter gray (fully opaque)
};

CGColorSpaceRef baseSpace = CGColorSpaceCreateDeviceGray(); // gray colors want gray color space
CGGradientRef gradient = CGGradientCreateWithColorComponents(baseSpace, colors, NULL, 2);
CGColorSpaceRelease(baseSpace), baseSpace = NULL;

CGContextSaveGState(c);
CGContextAddPath(c, strokedArc);
CGContextClip(c);

CGRect boundingBox = CGPathGetBoundingBox(strokedArc);
CGPoint gradientStart = CGPointMake(0, CGRectGetMinY(boundingBox));
CGPoint gradientEnd   = CGPointMake(0, CGRectGetMaxY(boundingBox));

CGContextDrawLinearGradient(c, gradient, gradientStart, gradientEnd, 0);
CGGradientRelease(gradient), gradient = NULL;
CGContextRestoreGState(c);

Ceci termine le dessin que nous avons actuellement ce résultat

Masked gradient

Animation

Quand il s'agit de l'animation de la forme qu'il a été écrit avant: Animation des morceaux de Tarte à l'Aide d'un Custom CALayer. Si vous essayez de faire le dessin de simplement l'animation de la trajectoire de propriété que vous allez voir quelques vraiment funky déformation de la trajectoire au cours de l'animation. L'ombre et de gradient a été laissé intact pour des fins d'illustration seulement l'image ci-dessous.

Funky warping of path

Je vous suggère de prendre le code de dessin que j'ai posté dans cette réponse, et de l'adopter à l'animation de code à partir de cet article. Ensuite, vous devriez vous retrouver avec la ce que vous demandez.


Pour référence: le même dessin à l'aide de Core Animation

Plaine forme

CAShapeLayer *segment = [CAShapeLayer layer];
segment.fillColor = [UIColor lightGrayColor].CGColor;
segment.strokeColor = [UIColor blackColor].CGColor;
segment.lineWidth = 1.0;
segment.path = strokedArc;

[self.view.layer addSublayer:segment];

Ajout d'ombres

La couche a un peu d'ombre liées à des propriétés que c'est à vous de les personnaliser. Howerever , vous devez régler le shadowPath de la propriété pour l'amélioration de la performance.

segment.shadowColor = [UIColor blackColor].CGColor;
segment.shadowOffset = CGSizeMake(0, 2);
segment.shadowOpacity = 0.75;
segment.shadowRadius = 3.0;
segment.shadowPath = segment.path; // Important for performance

Dessin du dégradé

CAGradientLayer *gradient = [CAGradientLayer layer];
gradient.colors = @[(id)[UIColor colorWithWhite:0.75 alpha:1.0].CGColor,  // light gray
                    (id)[UIColor colorWithWhite:0.90 alpha:1.0].CGColor]; // lighter gray
gradient.frame = CGPathGetBoundingBox(segment.path);

Si nous avons tiré le gradient de maintenant, il serait au-dessus de la forme et non pas à l'intérieur. Non, nous ne pouvons pas avoir un dégradé de remplissage de la forme (je sais que vous avez été la pensée de celui-ci). Nous avons besoin de masquer le gradient de sorte qu'il aller à l'extérieur du segment. Pour cela nous avons créer un autre calque pour être le masque de ce segment. Il doit être d'une autre couche, la documentation est claire que le comportement est "undefined" si le masque est la partie de la couche de la hiérarchie. Puisque le masque du système de coordonnées est le même que celui de sous-couches à gradient, nous allons traduire le segment de la forme avant de l'installer.

CAShapeLayer *mask = [CAShapeLayer layer];
CGAffineTransform translation = CGAffineTransformMakeTranslation(-CGRectGetMinX(gradient.frame),
                                                                 -CGRectGetMinY(gradient.frame));
mask.path = CGPathCreateCopyByTransformingPath(segment.path,
                                               &translation);
gradient.mask = mask;

39voto

rob mayoff Points 124153

Tout ce dont vous avez besoin est couvert dans le Quartz 2D Guide de Programmation. Je vous suggère de regarder à travers elle.

Cependant, il peut être difficile de mettre tout cela ensemble, donc je vais vous guider à travers elle. Nous allons écrire une fonction qui prend une taille et renvoie une image qui ressemble à peu près à l'un de vos segments:

arc with outline, gradient, and shadow

Nous commençons la définition de la fonction comme ceci:

static UIImage *imageWithSize(CGSize size) {

Nous aurons besoin d'une constante de l'épaisseur du segment:

    static CGFloat const kThickness = 20;

et une constante pour la largeur de la ligne décrivant le segment:

    static CGFloat const kLineWidth = 1;

et une constante pour la taille de l'ombre:

    static CGFloat const kShadowWidth = 8;

Ensuite, nous devons créer une image contexte dans lequel dessiner:

    UIGraphicsBeginImageContextWithOptions(size, NO, 0); {

J'ai mis une accolade gauche sur la fin de la ligne, parce que j'aime un niveau supplémentaire de retrait pour me rappeler d'appeler UIGraphicsEndImageContext plus tard.

Depuis beaucoup de fonctions dont nous avons besoin à l'appel sont des Graphiques de Base (aka Quartz 2D) de fonctions, de ne pas UIKit fonctions, nous devons obtenir de l' CGContext:

        CGContextRef gc = UIGraphicsGetCurrentContext();

Nous sommes maintenant prêts à vraiment commencer. Nous avons d'abord ajouter un arc pour le chemin d'accès. L'arc court le long du centre du segment nous voulons attirer l':

        CGContextAddArc(gc, size.width / 2, size.height / 2,
            (size.width - kThickness - kLineWidth) / 2,
            -M_PI / 4, -3 * M_PI / 4, YES);

Maintenant, nous allons demander Graphiques de Base pour remplacer le chemin d'accès avec une "caresse", version qui décrit le chemin d'accès. Nous avons d'abord définir l'épaisseur du trait à l'épaisseur de la nous voulons le segment d'avoir:

        CGContextSetLineWidth(gc, kThickness);

et nous avons défini la ligne de casquette style "bout à bout" de sorte que nous allons carré-off se termine:

        CGContextSetLineCap(gc, kCGLineCapButt);

Ensuite, nous pouvons nous demander Graphiques de Base pour remplacer le chemin d'accès avec un caressa version:

        CGContextReplacePathWithStrokedPath(gc);

Pour remplir ce chemin avec un dégradé linéaire, nous devons le dire Graphiques de Base à clip toutes les opérations à l'intérieur de la trajectoire. Cela permettra de faire des Graphiques de Base réinitialiser le chemin, mais nous allons avoir besoin de le chemin plus tard pour dessiner la ligne noire autour du bord. Donc, nous allons copier le chemin d'accès ici:

        CGPathRef path = CGContextCopyPath(gc);

Puisque nous voulons que le segment de jeter une ombre, nous allons mettre l'ombre des paramètres avant de nous faire un dessin:

        CGContextSetShadowWithColor(gc,
            CGSizeMake(0, kShadowWidth / 2), kShadowWidth / 2,
            [UIColor colorWithWhite:0 alpha:0.3].CGColor);

Nous allons à la fois de combler le segment (avec un dégradé) et d'accident vasculaire cérébral (pour dessiner le contour noir). Nous voulons une seule ombre pour les deux opérations. Nous dire Graphiques de Base qu'en commençant par une couche de transparence:

        CGContextBeginTransparencyLayer(gc, 0); {

J'ai mis une accolade gauche sur la fin de la ligne, parce que j'aime avoir un niveau d'indentation pour me rappeler d'appeler CGContextEndTransparencyLayer plus tard.

Puisque nous allons modifier le contexte du clip de la région pour le remplissage, mais nous ne voulons pas de saturation lorsque nous caresser le contour plus tard, nous avons besoin d'enregistrer l'état graphique:

            CGContextSaveGState(gc); {

J'ai mis une accolade gauche sur la fin de la ligne, parce que j'aime avoir un niveau d'indentation pour me rappeler d'appeler CGContextRestoreGState plus tard.

Pour combler le chemin avec un dégradé, nous avons besoin de créer un objet de dégradé:

                CGColorSpaceRef rgb = CGColorSpaceCreateDeviceRGB();
                CGGradientRef gradient = CGGradientCreateWithColors(rgb, (__bridge CFArrayRef)@[
                    (__bridge id)[UIColor grayColor].CGColor,
                    (__bridge id)[UIColor whiteColor].CGColor
                ], (CGFloat[]){ 0.0f, 1.0f });
                CGColorSpaceRelease(rgb);

Nous avons également besoin de trouver un point de départ et un point de fin du dégradé. Nous allons utiliser le chemin d'accès de la boîte englobante:

                CGRect bbox = CGContextGetPathBoundingBox(gc);
                CGPoint start = bbox.origin;
                CGPoint end = CGPointMake(CGRectGetMaxX(bbox), CGRectGetMaxY(bbox));

et nous allons la force du gradient à tirer horizontalement ou verticalement, selon la plus longue:

                if (bbox.size.width > bbox.size.height) {
                    end.y = start.y;
                } else {
                    end.x = start.x;
                }

Maintenant, enfin, nous avons tout ce dont nous avons besoin pour tracer le dégradé. Nous avons d'abord clip pour le chemin d'accès:

                CGContextClip(gc);

Puis nous tracer le dégradé:

                CGContextDrawLinearGradient(gc, gradient, start, end, 0);

Ensuite, nous pouvons libérer le gradient et de restaurer les sauvés de l'état graphique:

                CGGradientRelease(gradient);
            } CGContextRestoreGState(gc);

Lorsque nous les avons appelés CGContextClip, Graphiques de Base réinitialiser le contexte du chemin. Le chemin n'est pas une partie de l'sauvé de l'état graphique; c'est pourquoi nous avons fait une copie plus tôt. Il est temps maintenant d'utiliser cette copie pour définir le chemin d'accès dans le contexte nouveau:

            CGContextAddPath(gc, path);
            CGPathRelease(path);

Maintenant, nous pouvons avc le chemin, pour dessiner le contour noir du segment:

            CGContextSetLineWidth(gc, kLineWidth);
            CGContextSetLineJoin(gc, kCGLineJoinMiter);
            [[UIColor blackColor] setStroke];
            CGContextStrokePath(gc);

Ensuite nous dire Graphiques de Base pour mettre fin à la transparence de la couche. Cela permettra de regarder ce que nous avons dessiné et ajouter de l'ombre en-dessous:

        } CGContextEndTransparencyLayer(gc);

Maintenant, nous sommes tous fait de dessin. Nous demandons UIKit pour créer un UIImage de l'image contexte, détruire le contexte et le retour de l'image:

    UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
    UIGraphicsEndImageContext();
    return image;
}

Vous pouvez trouver le code tous ensemble dans ce résumé.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X