此问题从previous answer继续。
我有以下CAShapeLayer
:
- (CAShapeLayer *)gaugeCircleLayer {
if (_gaugeCircleLayer == nil) {
_gaugeCircleLayer = [CAShapeLayer layer];
_gaugeCircleLayer.lineWidth = self.gaugeWidth;
_gaugeCircleLayer.fillColor = [UIColor clearColor].CGColor;
_gaugeCircleLayer.strokeColor = self.gaugeTintColor.CGColor;
_gaugeCircleLayer.strokeStart = 0.0f;
_gaugeCircleLayer.strokeEnd = self.value;
_gaugeCircleLayer.lineCap = kCALineCapRound;
_gaugeCircleLayer.path = [self insideCirclePath].CGPath;
CAShapeLayer *cap = [CAShapeLayer layer];
cap.shadowColor = [UIColor blackColor].CGColor;
cap.shadowRadius = 8.0;
cap.shadowOpacity = 0.9;
cap.shadowOffset = CGSizeMake(0, 0);
cap.fillColor = [UIColor grayColor].CGColor;
[_gaugeCircleLayer addSublayer:cap];
}
return _gaugeCircleLayer;
}
然后我有以下UIBezierPath
:
- (UIBezierPath *)insideCirclePath {
CGPoint arcCenter = CGPointMake(CGRectGetMidX(self.bounds), CGRectGetMidY(self.bounds));
UIBezierPath *path = [UIBezierPath bezierPathWithArcCenter:arcCenter
radius:CGRectGetWidth(self.bounds) / 2.0f
startAngle:(3.0f * M_PI_2)
endAngle:(3.0f * M_PI_2) + (2.0f * M_PI)
clockwise:YES];
return path;
}
这产生如下内容:
我想用cap
子图层制作的是最后的阴影,我也有兴趣知道如何使GradientLayer的工作方式与下图类似:
问题是子层没有出现在任何地方,我不太清楚为什么。我也不能100%确定这是否是产生预期效果的最佳方式。
更新
以下代码creates a cap,虽然我不太确定如何将其正确添加到UIBezierPath
:
let cap = CAShapeLayer()
cap.shadowColor = UIColor.blackColor().CGColor
cap.shadowRadius = 8.0
cap.shadowOpacity = 0.9
cap.shadowOffset = CGSize(width: 0, height: 0)
cap.path = UIBezierPath(ovalInRect: CGRectMake(0, 40, 20, 20)).CGPath
cap.fillColor = UIColor.grayColor().CGColor
layer.addSublayer(cap)
答案 0 :(得分:2)
这应该可以解决问题。
唯一剩下的问题是动画,因此上限不是动画。
诀窍是将盖子添加到仪表的末端,并在仪表的值发生变化时更新它。为了计算位置,使用了一点数学魔法。它需要在衡量标准之下,因此在trackCircleLayer
//
// CHCircleGaugeView.m
//
// Copyright (c) 2014 ChaiOne <http://www.chaione.com/>
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
#import "CHCircleGaugeView.h"
#import "CHCircleGaugeViewDebugMacros.h"
#import <CoreText/CoreText.h>
#import <QuartzCore/QuartzCore.h>
static CGFloat const CHKeyAnimationDuration = 0.5f;
static CGFloat const CHKeyDefaultValue = 0.0f;
static CGFloat const CHKeyDefaultFontSize = 32.0f;
static CGFloat const CHKeyDefaultTrackWidth = 0.5f;
static CGFloat const CHKeyDefaultGaugeWidth = 2.0f;
static NSString * const CHKeyDefaultNAText = @"n/a";
static NSString * const CHKeyDefaultNoAnswersText = @"%";
#define CHKeyDefaultTrackTintColor [UIColor blackColor]
#define CHKeyDefaultGaugeTintColor [UIColor blackColor]
#define CHKeyDefaultTextColor [UIColor blackColor]
@interface CHCircleGaugeView ()
@property (nonatomic, strong) CAShapeLayer *trackCircleLayer;
@property (nonatomic, strong) CAShapeLayer *gaugeCircleLayer;
// ADDED
@property (nonatomic, strong) CAShapeLayer *capLayer;
// END ADDED
@property (nonatomic, strong) UILabel *valueTextLabel;
@end
@implementation CHCircleGaugeView
#pragma mark - View Initialization
- (instancetype)init {
return [self initWithFrame:CGRectZero];
}
- (instancetype)initWithFrame:(CGRect)frame {
self = [super initWithFrame:frame];
if (self) {
[self initSetup];
}
return self;
}
- (instancetype)initWithCoder:(NSCoder *)aDecoder {
self = [super initWithCoder:aDecoder];
if (self) {
[self initSetup];
}
return self;
}
- (void)initSetup {
_state = CHCircleGaugeViewStateNA;
_value = CHKeyDefaultValue;
_trackTintColor = CHKeyDefaultTrackTintColor;
_gaugeTintColor = CHKeyDefaultGaugeTintColor;
_textColor = CHKeyDefaultTextColor;
_font = [UIFont systemFontOfSize:CHKeyDefaultFontSize];
_trackWidth = CHKeyDefaultTrackWidth;
_gaugeWidth = CHKeyDefaultGaugeWidth;
_notApplicableString = CHKeyDefaultNAText;
_noDataString = CHKeyDefaultNoAnswersText;
[self createGauge];
}
- (void)createGauge {
[self.layer addSublayer:self.trackCircleLayer];
[self.layer addSublayer:self.gaugeCircleLayer];
[self addSubview:self.valueTextLabel];
[self setupConstraints];
}
- (void)setupConstraints {
NSDictionary *viewDictionary = @{@"valueText" : self.valueTextLabel};
[self addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|[valueText]|" options:0 metrics:nil views:viewDictionary]];
[self addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|[valueText]|" options:0 metrics:nil views:viewDictionary]];
}
#pragma mark - Property Setters
- (void)setState:(CHCircleGaugeViewState)state {
if (_state != state) {
_state = state;
switch (state) {
case CHCircleGaugeViewStateNA: {
[self updateGaugeWithValue:0 animated:NO];
break;
}
case CHCircleGaugeViewStatePercentSign: {
[self updateGaugeWithValue:0 animated:NO];
break;
}
case CHCircleGaugeViewStateScore: {
[self updateGaugeWithValue:self.value animated:NO];
break;
}
default: {
ALog(@"Missing gauge state.");
break;
}
}
}
}
- (void)setValue:(CGFloat)value {
[self setValue:value animated:NO];
}
- (void)setValue:(CGFloat)value animated:(BOOL)animated {
self.state = CHCircleGaugeViewStateScore;
if (value != _value) {
[self willChangeValueForKey:NSStringFromSelector(@selector(value))];
value = MIN(1.0f, MAX(0.0f, value));
[self updateGaugeWithValue:value animated:animated];
_value = value;
[self didChangeValueForKey:NSStringFromSelector(@selector(value))];
}
}
- (void)setUnitsString:(NSString *)unitsString {
if ([_unitsString isEqualToString:unitsString] == NO) {
_unitsString = [unitsString copy];
self.valueTextLabel.attributedText = [self formattedStringForValue:self.value];
}
}
- (void)updateGaugeWithValue:(CGFloat)value animated:(BOOL)animated {
self.valueTextLabel.attributedText = [self formattedStringForValue:value];
BOOL previousDisableActionsValue = [CATransaction disableActions];
[CATransaction setDisableActions:YES];
self.gaugeCircleLayer.strokeEnd = value;
// ADDED
_capLayer.path = [self capPathForValue:value].CGPath;
// END ADDED
if (animated) {
self.gaugeCircleLayer.strokeEnd = value;
CABasicAnimation *pathAnimation = [CABasicAnimation animationWithKeyPath:@"strokeEnd"];
pathAnimation.duration = CHKeyAnimationDuration;
pathAnimation.fromValue = [NSNumber numberWithFloat:self.value];
pathAnimation.toValue = [NSNumber numberWithFloat:value];
pathAnimation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
[self.gaugeCircleLayer addAnimation:pathAnimation forKey:@"strokeEndAnimation"];
}
[CATransaction setDisableActions:previousDisableActionsValue];
}
- (void)setTrackTintColor:(UIColor *)trackTintColor {
if (_trackTintColor != trackTintColor) {
_trackTintColor = trackTintColor;
self.trackCircleLayer.strokeColor = trackTintColor.CGColor;
}
}
- (void)setGaugeTintColor:(UIColor *)gaugeTintColor {
if (_gaugeTintColor != gaugeTintColor) {
_gaugeTintColor = gaugeTintColor;
self.gaugeCircleLayer.strokeColor = gaugeTintColor.CGColor;
// ADDED
self.capLayer.fillColor = gaugeTintColor.CGColor;
// END ADDED
}
}
- (void)setTrackWidth:(CGFloat)trackWidth {
if (_trackWidth != trackWidth) {
_trackWidth = trackWidth;
self.trackCircleLayer.lineWidth = trackWidth;
[self.layer layoutSublayers];
}
}
- (void)setGaugeWidth:(CGFloat)gaugeWidth {
if (_gaugeWidth != gaugeWidth) {
_gaugeWidth = gaugeWidth;
self.gaugeCircleLayer.lineWidth = gaugeWidth;
[self.layer layoutSublayers];
}
}
- (void)setTextColor:(UIColor *)textColor {
if (_textColor != textColor) {
_textColor = textColor;
self.valueTextLabel.textColor = textColor;
}
}
- (void)setFont:(UIFont *)font {
if (_font != font) {
_font = font;
self.valueTextLabel.font = font;
}
}
- (void)setGaugeStyle:(CHCircleGaugeStyle)gaugeStyle {
if (_gaugeStyle != gaugeStyle) {
_gaugeStyle = gaugeStyle;
[self.layer layoutSublayers];
}
}
#pragma mark - Circle Shapes
- (CAShapeLayer *)trackCircleLayer {
if (_trackCircleLayer == nil) {
_trackCircleLayer = [CAShapeLayer layer];
_trackCircleLayer.lineWidth = self.trackWidth;
_trackCircleLayer.fillColor = [UIColor clearColor].CGColor;
_trackCircleLayer.strokeColor = self.trackTintColor.CGColor;
_trackCircleLayer.path = [self insideCirclePath].CGPath;
// ADDED
_capLayer = [CAShapeLayer layer];
_capLayer.shadowColor = [UIColor blackColor].CGColor;
_capLayer.shadowRadius = 8.0;
_capLayer.shadowOpacity = 0.9;
_capLayer.shadowOffset = CGSizeMake(0, 0);
_capLayer.fillColor = self.gaugeTintColor.CGColor;
_capLayer.path = [self capPathForValue:self.value].CGPath;
[_trackCircleLayer addSublayer:_capLayer];
// END ADDED
}
return _trackCircleLayer;
}
- (CAShapeLayer *)gaugeCircleLayer {
if (_gaugeCircleLayer == nil) {
_gaugeCircleLayer = [CAShapeLayer layer];
_gaugeCircleLayer.lineWidth = self.gaugeWidth;
_gaugeCircleLayer.fillColor = [UIColor clearColor].CGColor;
_gaugeCircleLayer.strokeColor = self.gaugeTintColor.CGColor;
_gaugeCircleLayer.strokeStart = 0.0f;
_gaugeCircleLayer.strokeEnd = self.value;
_gaugeCircleLayer.path = [self circlPathForCurrentGaugeStyle].CGPath;
}
return _gaugeCircleLayer;
}
// ADDED
- (UIBezierPath *)capPathForValue:(float)value {
CGPoint arcCenter = CGPointMake(CGRectGetMidX(self.bounds), CGRectGetMidY(self.bounds));
CGFloat radius = CGRectGetWidth(self.bounds) / 2.0f;
float angle = value * 360.0;
float x = radius * sin(angle*M_PI/180.0);
float y = radius * cos(angle*M_PI/180.0);
CGPoint capArcCenter = CGPointMake(arcCenter.x + x, arcCenter.y - y);
UIBezierPath *path = [UIBezierPath bezierPathWithArcCenter:capArcCenter
radius:self.gaugeWidth*_capLayer.shadowRadius / 2.0f
startAngle:(3.0f * M_PI_2)
endAngle:(3.0f * M_PI_2) + (2.0f * M_PI)
clockwise:YES];
return path;
}
// END ADDED
- (UIBezierPath *)circlPathForCurrentGaugeStyle {
switch (self.gaugeStyle) {
case CHCircleGaugeStyleInside: {
return [self insideCirclePath];
}
case CHCircleGaugeStyleOutside: {
return [self outsideCirclePath];
}
default: {
return nil;
}
}
}
- (UIBezierPath *)insideCirclePath {
CGPoint arcCenter = CGPointMake(CGRectGetMidX(self.bounds), CGRectGetMidY(self.bounds));
UIBezierPath *path = [UIBezierPath bezierPathWithArcCenter:arcCenter
radius:CGRectGetWidth(self.bounds) / 2.0f
startAngle:(3.0f * M_PI_2)
endAngle:(3.0f * M_PI_2) + (2.0f * M_PI)
clockwise:YES];
return path;
}
- (UIBezierPath *)outsideCirclePath {
CGPoint arcCenter = CGPointMake(CGRectGetMidX(self.bounds), CGRectGetMidY(self.bounds));
CGFloat radius = (CGRectGetWidth(self.bounds) / 2.0f) + (self.trackWidth / 2.0f) + (self.gaugeWidth / 2.0f);
UIBezierPath *path = [UIBezierPath bezierPathWithArcCenter:arcCenter
radius:radius
startAngle:(3.0f * M_PI_2)
endAngle:(3.0f * M_PI_2) + (2.0f * M_PI)
clockwise:YES];
return path;
}
#pragma mark - Text Label
- (UILabel *)valueTextLabel {
if (_valueTextLabel == nil) {
_valueTextLabel = [[UILabel alloc] init];
[_valueTextLabel setTranslatesAutoresizingMaskIntoConstraints:NO];
_valueTextLabel.textAlignment = NSTextAlignmentCenter;
_valueTextLabel.attributedText = [self formattedStringForValue:self.value];
}
return _valueTextLabel;
}
- (NSAttributedString *)formattedStringForValue:(CGFloat)value {
NSAttributedString *valueString;
NSDictionary *stringAttributes = @{
NSForegroundColorAttributeName : self.textColor,
NSFontAttributeName : self.font
};
switch (self.state) {
case CHCircleGaugeViewStateNA: {
valueString = [[NSAttributedString alloc] initWithString:self.notApplicableString attributes:stringAttributes];
break;
}
case CHCircleGaugeViewStatePercentSign: {
valueString = [[NSAttributedString alloc] initWithString:self.noDataString attributes:stringAttributes];
break;
}
case CHCircleGaugeViewStateScore: {
NSString *suffix = self.unitsString ? self.unitsString : @"";
valueString = [[NSAttributedString alloc] initWithString:[NSString stringWithFormat:@"%.0f %@", value * 100.0f, suffix]
attributes:stringAttributes];
break;
}
default: {
ALog(@"Missing gauge state.");
break;
}
}
return valueString;
}
#pragma mark - KVO
// Handling KVO notifications for the value property, since
// we're proxying with the setValue:animated: method.
+ (BOOL)automaticallyNotifiesObserversForKey:(NSString *)key {
if ([key isEqualToString:NSStringFromSelector(@selector(value))]) {
return NO;
} else {
return [super automaticallyNotifiesObserversForKey:key];
}
}
#pragma mark - CALayerDelegate
- (void)layoutSublayersOfLayer:(CALayer *)layer {
[super layoutSublayersOfLayer:layer];
if (layer == self.layer) {
self.trackCircleLayer.path = [self insideCirclePath].CGPath;
self.gaugeCircleLayer.path = [self circlPathForCurrentGaugeStyle].CGPath;
}
}
@end
答案 1 :(得分:2)
我不知道这对你有用,因为它没有使用CHCircleGaugeView。我正在处理与此问题相关的几个项目,因此我将它们混合在一起并进行了一些更改以生成具有颜色渐变背景的进度视图,其尖端与100%的末端重叠。我还没有达到让圆形尖端消失在0%的程度,但我最终会到达那里。以下是两个不同进度水平的观点,
使用drawRect绘制的极坐标创建视图,并用环面遮盖。圆形尖端是一个单独的层,在连接到圆心的线的末端是半圆,围绕其中心旋转,并基于进度水平进行变换。这是代码,
@implementation AnnulusProgressView{ // subclass of UIView
int slices;
CGFloat circleRadius;
CAShapeLayer *maskLayer;
CGFloat segmentAngle;
CGFloat startAngle;
CAShapeLayer *tipView;
NSMutableArray *colors;
int sign;
}
-(instancetype)initWithFrame:(CGRect)frame wantsBackgroundRing:(BOOL)wantsBackground backgroundRingColor:(UIColor *)ringColor {
if (self = [super initWithFrame:frame]) {
slices = 360;
_ringThickness = 12;
circleRadius = self.bounds.size.width/2;
_startColor = [UIColor colorWithHue:0.24 saturation:1 brightness:0.8 alpha:1];
_endColor = [UIColor colorWithHue:0.03 saturation:1 brightness:1 alpha:1];
[self setupColorArray];
_backgroundRing = wantsBackground? [self createBackgroundRingWithColor:ringColor] : nil;
self.layer.mask = [self createAnnulusMask];
}
return self;
}
-(void)setStartColor:(UIColor *)startColor {
_startColor = startColor;
[self setupColorArray];
}
-(void)setEndColor:(UIColor *)endColor {
_endColor = endColor;
[self setupColorArray];
}
-(void)setupColorArray {
colors = [NSMutableArray new];
CGFloat startHue, startSaturation, startBrightness, startAlpha, endHue, endSaturation, endBrightness, endAlpha;
[self.startColor getHue:&startHue saturation:&startSaturation brightness:&startBrightness alpha:&startAlpha];
[self.endColor getHue:&endHue saturation:&endSaturation brightness:&endBrightness alpha:&endAlpha];
for(int i=0;i<slices;i++){
CGFloat hue = startHue + (endHue - startHue)*i/slices;
CGFloat brightness = startBrightness + (endBrightness - startBrightness)*i/slices;
CGFloat saturation = startSaturation + (endSaturation - startSaturation)*i/slices;
CGFloat alpha = startAlpha + (endAlpha - startAlpha)*i/slices;
UIColor *color = [UIColor colorWithHue:hue saturation:saturation brightness:brightness alpha:alpha];
[colors addObject:color];
}
self.progress = _progress;
}
-(UIView *)createBackgroundRingWithColor:(UIColor *) color {
UIView *bgRing = [[UIView alloc] initWithFrame:self.frame];
bgRing.backgroundColor = color;
bgRing.layer.mask = [self createAnnulusMask];
[(CAShapeLayer *)bgRing.layer.mask setStrokeEnd:startAngle + 2*M_PI ];
return bgRing;
}
-(void)didMoveToSuperview {
if (self.backgroundRing) [self.superview insertSubview:self.backgroundRing belowSubview:self];
tipView = [self tipView];
[self.superview.layer addSublayer:tipView];
}
-(CAShapeLayer *)tipView {
CAShapeLayer *tip = [CAShapeLayer layer];
tip.frame = self.frame;
tip.fillColor = [UIColor redColor].CGColor;
UIBezierPath *shape = [UIBezierPath bezierPath];
CGPoint center = CGPointMake(CGRectGetMidX(self.bounds), CGRectGetMidY(self.bounds));
[shape moveToPoint:center];
CGPoint bottomPoint = CGPointMake(center.x, center.y + circleRadius - self.ringThickness*2);
CGFloat tipCenterY = bottomPoint.y + self.ringThickness - 1;
[shape addLineToPoint: bottomPoint];
[shape addLineToPoint:CGPointMake(bottomPoint.x+2, bottomPoint.y)];
double fractionAlongTangent = self.ringThickness;
[shape addCurveToPoint:CGPointMake(center.x, center.y + circleRadius) controlPoint1:CGPointMake(center.x - self.ringThickness*1.5, tipCenterY - fractionAlongTangent) controlPoint2:CGPointMake(center.x - self.ringThickness*1.5, tipCenterY + fractionAlongTangent)];
[shape closePath];
tip.path = shape.CGPath;
tip.shadowColor = [UIColor darkGrayColor].CGColor;
tip.shadowOpacity = 0.8;
tip.shadowOffset = CGSizeMake(-6, 0);
tip.shadowRadius = 2;
return tip;
}
- (void)setProgress:(CGFloat)progress{
sign = (progress >= _progress)? 1 : -1;
[self animateProgressTo:@(progress)];
}
-(void)animateProgressTo:(NSNumber *) newValueNumber{
float newValue = newValueNumber.floatValue;
_progress = (_progress + (sign * 0.1) > 1)? 1 : _progress + (sign * 0.1);
if ((_progress > newValue && sign == 1) || (_progress < newValue && sign == -1)) {
_progress = newValue;
}
maskLayer.strokeEnd = _progress;
tipView.transform = CATransform3DMakeRotation(-(1 - _progress + 0.002) * M_PI*2, 0, 0, 1); //the 0.002 closes a small gap between the tip and the annulus strokeEnd
int i = (int)(_progress*(slices - 1));
tipView.fillColor = ((UIColor *)colors[i]).CGColor;
if (sign == 1) {
if (_progress < newValue) {
[self performSelector:@selector(animateProgressTo:) withObject:@(newValue) afterDelay:0.05];
}
}else{
if (_progress > newValue) {
[self performSelector:@selector(animateProgressTo:) withObject:@(newValue) afterDelay:0.05];
}
}
NSLog(@"%f",_progress);
}
- (CAShapeLayer *)createAnnulusMask {
maskLayer = [CAShapeLayer layer];
maskLayer.frame = self.bounds;
segmentAngle = 2*M_PI/(slices);
startAngle = M_PI_2;
CGFloat endAngle = startAngle + 2*M_PI;
maskLayer.path = [UIBezierPath bezierPathWithArcCenter:CGPointMake(CGRectGetMidX(self.bounds), CGRectGetMidY(self.bounds)) radius:circleRadius - self.ringThickness startAngle:startAngle endAngle:endAngle clockwise:YES].CGPath;
maskLayer.fillColor = [UIColor clearColor].CGColor;
maskLayer.strokeColor = [UIColor blackColor].CGColor;
maskLayer.lineWidth = self.ringThickness * 2;
maskLayer.strokeEnd = self.progress;
return maskLayer;
}
-(void)drawRect:(CGRect)rect{
CGContextRef ctx = UIGraphicsGetCurrentContext();
CGContextSetAllowsAntialiasing(ctx, NO);
CGPoint center = CGPointMake(CGRectGetMidX(self.bounds), CGRectGetMidY(self.bounds));
for(int i=0;i<slices;i++){
CGContextSaveGState(ctx);
UIBezierPath *path = [UIBezierPath bezierPath];
[path moveToPoint:center];
[path addArcWithCenter:center radius:circleRadius startAngle:startAngle endAngle:startAngle+segmentAngle clockwise:YES];
[path addClip];
[colors[i] setFill];
[path fill];
CGContextRestoreGState(ctx);
startAngle += segmentAngle;
}
}