具有多个彩色边框的圆形UIView像饼图一样工作

时间:2016-01-29 17:25:48

标签: objective-c uiview uiimageview core-graphics pie-chart

我正在尝试在化身的圆形边框上设置具有饼图表示的游戏玩家的圆形化身。

玩家1 - 胜率25% 失去了70% 拉5%

cell.selectedPhoto.frame = CGRectMake(cell.selectedPhoto.frame.origin.x, cell.selectedPhoto.frame.origin.y, 75, 75);
cell.selectedPhoto.clipsToBounds = YES;
cell.selectedPhoto.layer.cornerRadius = 75/2.0f;

cell.selectedPhoto.layer.borderColor=[UIColor orangeColor].CGColor;
cell.selectedPhoto.layer.borderWidth=2.5f;
cell.selectedBadge.layer.cornerRadius = 15;

我将UIImageView作为一个已经有单边框颜色的圆圈。

首先猜测也许我需要清除我的UIImageView的边框,而是让UIView坐在我的UIImageView后面,这是一个标准的饼图,但是有更聪明的方法吗?

提前谢谢。

2 个答案:

答案 0 :(得分:1)

我建议您为此创建一个自定义UIView子类,管理各种CALayer对象以创建此效果。我打算在Core Graphics中做这个,但如果你想为此添加一些不错的动画,你会想要坚持使用Core Animation。

所以让我们首先定义我们的界面。

/// Provides a simple interface for creating an avatar icon, with a pie-chart style border.
@interface AvatarView : UIView

/// The avatar image, to be displayed in the center.
@property (nonatomic) UIImage* avatarImage;

/// An array of float values to define the values of each portion of the border.
@property (nonatomic) NSArray* borderValues;

/// An array of UIColors to define the colors of the border portions.
@property (nonatomic) NSArray* borderColors;

/// The width of the outer border.
@property (nonatomic) CGFloat borderWidth;

/// Animates the border values from their current values to a new set of values.
-(void) animateToBorderValues:(NSArray*)borderValues duration:(CGFloat)duration;

@end

在这里,我们可以设置头像图像,边框宽度,并提供颜色和值的数组。接下来,让我们来实现这个。首先,我们要定义一些我们想要跟踪的变量。

@implementation AvatarView {
    CALayer* avatarImageLayer; // the avatar image layer
    NSMutableArray* borderLayers; // the array containing the portion border layers
    UIBezierPath* borderLayerPath; // the path used to stroke the border layers
    CGFloat radius; // the radius of the view
}

接下来,让我们设置avatarImageLayer以及initWithFrame方法中的其他几个变量:

-(instancetype) initWithFrame:(CGRect)frame {
    if (self = [super initWithFrame:frame]) {

        radius = frame.size.width*0.5;

        // create border layer array
        borderLayers = [NSMutableArray array];

        // create avatar image layer
        avatarImageLayer = [CALayer layer];
        avatarImageLayer.frame = frame;
        avatarImageLayer.contentsScale = [UIScreen mainScreen].nativeScale; // scales the layer to the screen scale
        [self.layer addSublayer:avatarImageLayer];
    }
    return self;
}

接下来让我们定义我的方法,在borderValues属性更新时填充边框图层,允许视图具有动态数量的边框图层。

-(void) populateBorderLayers {

    while (borderLayers.count > _borderValues.count) { // remove layers if the number of border layers got reduced
        [(CAShapeLayer*)[borderLayers lastObject] removeFromSuperlayer];
        [borderLayers removeLastObject];
    }

    NSUInteger colorCount = _borderColors.count;
    NSUInteger borderLayerCount = borderLayers.count;

    while (borderLayerCount < _borderValues.count) { // add layers if the number of border layers got increased

        CAShapeLayer* borderLayer = [CAShapeLayer layer];

        borderLayer.path = borderLayerPath.CGPath;
        borderLayer.fillColor = [UIColor clearColor].CGColor;
        borderLayer.lineWidth = _borderWidth;
        borderLayer.strokeColor = (borderLayerCount < colorCount)? ((UIColor*)_borderColors[borderLayerCount]).CGColor : [UIColor clearColor].CGColor;

        if (borderLayerCount != 0) { // set pre-animation border stroke positions.

            CAShapeLayer* previousLayer = borderLayers[borderLayerCount-1];
            borderLayer.strokeStart = previousLayer.strokeEnd;
            borderLayer.strokeEnd = previousLayer.strokeEnd;

        } else borderLayer.strokeEnd = 0.0; // default value for first layer.

        [self.layer insertSublayer:borderLayer atIndex:0]; // not strictly necessary, should work fine with `addSublayer`, but nice to have to ensure the layers don't unexpectedly overlap.
        [borderLayers addObject:borderLayer];

        borderLayerCount++;
    }
}

接下来,我们想要创建一个方法,可以在borderValues更新时更新图层的笔画开始和结束值。这可以合并到以前的方法中,但如果你想设置动画,你会想要将它分开。

-(void) updateBorderStrokeValues {
    NSUInteger i = 0;
    CGFloat cumulativeValue = 0;
    for (CAShapeLayer* s in borderLayers) {

        s.strokeStart = cumulativeValue;
        cumulativeValue += [_borderValues[i] floatValue];
        s.strokeEnd = cumulativeValue;

        i++;
    }
}

接下来,我们只需要覆盖设置器,以便在值更改时更新边框和头像图像的某些方面:

-(void) setAvatarImage:(UIImage *)avatarImage {
    _avatarImage = avatarImage;
    avatarImageLayer.contents = (id)avatarImage.CGImage; // update contents if image changed
}

-(void) setBorderWidth:(CGFloat)borderWidth {
    _borderWidth = borderWidth;

    CGFloat halfBorderWidth = borderWidth*0.5; // we're gonna use this a bunch, so might as well pre-calculate

    // set the new border layer path
    borderLayerPath = [UIBezierPath bezierPathWithArcCenter:(CGPoint){radius, radius} radius:radius-halfBorderWidth startAngle:-M_PI*0.5 endAngle:M_PI*1.5 clockwise:YES];

    for (CAShapeLayer* s in borderLayers) { // apply the new border layer path
        s.path = borderLayerPath.CGPath;
        s.lineWidth = borderWidth;
    }

    // update avatar masking
    CAShapeLayer* s = [CAShapeLayer layer];
    avatarImageLayer.frame = CGRectMake(halfBorderWidth, halfBorderWidth, self.frame.size.width-borderWidth, self.frame.size.height-borderWidth); // update avatar image frame
    s.path = [UIBezierPath bezierPathWithArcCenter:(CGPoint){radius-halfBorderWidth, radius-halfBorderWidth} radius:radius-borderWidth startAngle:0 endAngle:M_PI*2.0 clockwise:YES].CGPath;
    avatarImageLayer.mask = s;
}

-(void) setBorderColors:(NSArray *)borderColors {
    _borderColors = borderColors;

    NSUInteger i = 0;
    for (CAShapeLayer* s in borderLayers) {
        s.strokeColor = ((UIColor*)borderColors[i]).CGColor;
        i++;
    }
}

-(void) setBorderValues:(NSArray *)borderValues {
    _borderValues = borderValues;
    [self populateBorderLayers];
    [self updateBorderStrokeValues];
}

最后,我们甚至可以通过设置层的动画来更进一步!我们只需添加一个可以为我们处理此问题的方法。

-(void) animateToBorderValues:(NSArray *)borderValues duration:(CGFloat)duration {

    _borderValues = borderValues; // update border values

    [self populateBorderLayers]; // do a 'soft' layer update, making sure that the correct number of layers are generated pre-animation. Pre-sets stroke positions to a pre-animation state.

    // define stroke animation
    CABasicAnimation* strokeAnim = [CABasicAnimation animation];
    strokeAnim.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
    strokeAnim.duration = duration;

    CGFloat cumulativeValue = 0;
    for (int i = 0; i < borderLayers.count; i++) {

        cumulativeValue += [borderValues[i] floatValue];

        CAShapeLayer* s = borderLayers[i];

        if (i != 0) [s addAnimation:strokeAnim forKey:@"startStrokeAnim"];

        // define stroke end animation
        strokeAnim.keyPath = @"strokeEnd";
        strokeAnim.fromValue = @(s.strokeEnd);
        strokeAnim.toValue = @(cumulativeValue);
        [s addAnimation:strokeAnim forKey:@"endStrokeAnim"];

        strokeAnim.keyPath = @"strokeStart"; // re-use the previous animation, as the values are the same (in the next iteration).
    }

    // update presentation layer values
    [CATransaction begin];
    [CATransaction setDisableActions:YES];
    [self updateBorderStrokeValues]; // sets stroke positions.
    [CATransaction commit];
}

那就是它!以下是用法示例:

AvatarView* v = [[AvatarView alloc] initWithFrame:CGRectMake(50, 50, 200, 200)];
v.avatarImage = [UIImage imageNamed:@"photo.png"];
v.borderWidth = 10;
v.borderColors = @[[UIColor colorWithRed:122.0/255.0 green:108.0/255.0 blue:255.0/255.0 alpha:1],
                   [UIColor colorWithRed:100.0/255.0 green:241.0/255.0 blue:183.0/255.0 alpha:1],
                   [UIColor colorWithRed:0 green:222.0/255.0 blue:255.0/255.0 alpha:1]];

// because the border values default to 0, you can add this without even setting the border values initially!
[v animateToBorderValues:@[@(0.4), @(0.35), @(0.25)] duration:2];

结果

enter image description here enter image description here enter image description here

完整项目:https://github.com/hamishknight/Pie-Chart-Avatar

答案 1 :(得分:1)

实际上,您可以直接从CALayer创建自己的图层。这是我自己项目中的动画图层示例。

AnimationLayer.h

#import <QuartzCore/QuartzCore.h>

@interface AnimationLayer : CALayer
@property (nonatomic,assign ) float percent;
@property (nonatomic, strong) NSArray *percentValues;
@property (nonatomic, strong) NSArray *percentColours;
@end

percentValues是你得到哪部分的价值观。 赢率为@[@(35),@(75),@(100)]:%35,宽松:%40,平局:%25。 percentColorsUIColor对象,用于获胜,宽松和平局。

in `AnimationLayer.m`

#import "AnimationLayer.h"
#import <UIKit/UIKit.h>
@implementation AnimationLayer
@dynamic percent,percentValues,percentColours;

+ (BOOL)needsDisplayForKey:(NSString *)key{
    if([key isEqualToString:@"percent"]){
        return YES;
    }else
        return [super needsDisplayForKey:key];
}
- (void)drawInContext:(CGContextRef)ctx
{

    CGFloat arcStep = (M_PI *2) / 100 * (1.0-self.percent); // M_PI*2 is equivalent of full cirle
    BOOL clockwise = NO;
    CGFloat x = CGRectGetWidth(self.bounds) / 2; // circle's center
    CGFloat y = CGRectGetHeight(self.bounds) / 2; // circle's center
    CGFloat radius = MIN(x, y);
    UIGraphicsPushContext(ctx);
    // draw colorful circle
    CGContextSetLineWidth(ctx, 12);//12 is the width of circle.

    CGFloat toDraw = (1-self.percent)*100.0f;
    for (CGFloat i = 0; i < toDraw; i++)
    {
        UIColor *c;
        for (int j = 0; j<[self.percentValues count]; j++)
        {
            if (i <= [self.percentValues[j] intValue]) {
                c = self.percentColours[j];
                break;
            }
        }

        CGContextSetStrokeColorWithColor(ctx, c.CGColor);

        CGFloat startAngle = i * arcStep;
        CGFloat endAngle = startAngle + arcStep+0.02;

        CGContextAddArc(ctx, x, y, radius-6, startAngle, endAngle, clockwise);//set the radius as radius-(half of your line width.)

        CGContextStrokePath(ctx);

    }
    UIGraphicsPopContext();
}
@end

并且在某些你将使用此效果的地方,你应该将其称为

+(void)addAnimationLayerToView:(UIView *)imageOfPlayer withColors:(NSArray *)colors andValues:(NSArray *)values
{
    AnimationLayer *animLayer = [AnimationLayer layer];
    animLayer.frame = imageOfPlayer.bounds;
    animLayer.percentColours = colors;
    animLayer.percentValues = values;
    [imageOfPlayer.layer insertSublayer:animLayer atIndex:0];

    CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:@"percent"];
    [animation setFromValue:@1];
    [animation setToValue:@0];

    [animation setTimingFunction:[CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut]];
    [animation setDuration:6];
    [animLayer addAnimation:animation forKey:@"imageAnimation"];
}

sample gif