원 또는 도넛에서 선분 그리기
다음 이미지와 같이 세그먼트를 그리는 방법을 알아 내려고했습니다.
나는하고 싶어요:
- 세그먼트를 그리다
- 그라디언트 포함
- 그림자 포함
- 도면을 0에서 n 각도로 애니메이션
나는 CGContextAddArc
유사한 전화로 이것을 시도 했지만 그리 멀지 않았습니다.
누구든지 도울 수 있습니까?
귀하의 질문에는 많은 부분이 있습니다.
경로 얻기
이러한 세그먼트에 대한 경로를 만드는 것은 너무 어렵지 않아야합니다. 두 개의 호와 두 개의 직선이 있습니다. 나는 한 이전에 그와 같은 길을 깰 수있는 방법을 설명 내가 여기에 그것을하지 않도록. 대신 나는 화려하고 다른 경로를 쓰다듬어 경로를 만들 것입니다. 물론 고장을 읽고 경로를 직접 구성 할 수 있습니다. 내가 스트로 킹에 대해 이야기하고있는 호는 회색 점선 최종 결과 안의 주황색 호입니다.
경로를 시작하려면 먼저 경로가 필요합니다. 기본적으로 시작점 으로 이동 하고 현재 각도에서 세그먼트가 덮을 각도까지 중심 주위에 호를 그리는 것만 큼 간단 합니다.
CGMutablePathRef arc = CGPathCreateMutable();
CGPathMoveToPoint(arc, NULL,
startPoint.x, startPoint.y);
CGPathAddArc(arc, NULL,
centerPoint.x, centerPoint.y,
radius,
startAngle,
endAngle,
YES);
그런 다음 해당 경로 (단일 호)가 있으면 특정 너비로 스트로크하여 새 세그먼트를 만들 수 있습니다. 결과 경로에는 두 개의 직선과 두 개의 호가 있습니다. 스트로크는 중심에서 안쪽과 바깥쪽으로 동일한 거리에서 발생합니다.
CGFloat lineWidth = 10.0;
CGPathRef strokedArc =
CGPathCreateCopyByStrokingPath(arc, NULL,
lineWidth,
kCGLineCapButt,
kCGLineJoinMiter, // the default
10); // 10 is default miter limit
그림
다음은 드로잉이며 일반적으로 두 가지 주요 선택 사항이 있습니다. 코어 그래픽 drawRect:
또는 코어 애니메이션이있는 모양 레이어입니다. Core Graphics는 더 강력한 그림을 제공하지만 Core Animation은 더 나은 애니메이션 성능을 제공 할 것입니다. 경로가 포함되어 있기 때문에 순수한 Cora 애니메이션이 작동하지 않습니다. 이상한 유물로 끝날 것입니다. 그러나 레이어의 그래픽 컨텍스트를 그려 레이어와 Core Graphics의 조합을 사용할 수 있습니다.
세그먼트 채우기 및 쓰다듬 기
이미 기본 모양이 있지만 그라디언트와 그림자를 추가하기 전에 기본 채우기 및 획을 수행합니다 (이미지에 검은 색 획이 있음).
CGContextRef c = UIGraphicsGetCurrentContext();
CGContextAddPath(c, strokedArc);
CGContextSetFillColorWithColor(c, [UIColor lightGrayColor].CGColor);
CGContextSetStrokeColorWithColor(c, [UIColor blackColor].CGColor);
CGContextDrawPath(c, kCGPathFillStroke);
그러면 화면에 이와 같은 내용이 표시됩니다.
그림자 추가
순서를 변경하고 그라디언트 전에 그림자를 만들 것입니다. 그림자를 그리려면 컨텍스트에 대한 그림자를 구성하고 그림자로 그릴 모양을 채워야합니다. 그런 다음 컨텍스트를 (그림자 이전으로) 복원하고 모양을 다시 스트로크해야합니다.
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);
이 시점에서 결과는 다음과 같습니다.
그라디언트 그리기
그라디언트의 경우 그라디언트 레이어가 필요합니다. 여기서는 매우 간단한 2 색 그라데이션을 수행하고 있지만 원하는대로 사용자 지정할 수 있습니다. 그래디언트를 만들려면 색상과 적절한 색상 공간을 가져와야합니다. 그런 다음 채우기 위에 (하지만 획 이전에) 그라디언트를 그릴 수 있습니다. 또한 이전과 동일한 경로로 그라디언트를 마스크해야합니다. 이를 위해 경로를 자릅니다.
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);
현재이 결과가 있으므로 도면이 완료됩니다.
생기
모양의 애니메이션에 관해서는 모두 이전에 작성되었습니다 : 사용자 정의 CALayer를 사용하여 원형 조각 애니메이션 . 단순히 경로 속성에 애니메이션을 적용하여 그리기를 시도하면 애니메이션 중에 경로의 정말 펑키 한 뒤틀림을 볼 수 있습니다. 아래 이미지에서는 설명을 위해 그림자와 그라데이션이 그대로 유지되었습니다.
이 답변에 게시 한 드로잉 코드를 해당 기사의 애니메이션 코드에 채택하는 것이 좋습니다. 그러면 당신은 당신이 요구하는 것을 끝내야합니다.
참고 : Core Animation을 사용한 동일한 그림
일반 모양
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];
그림자 추가
레이어에는 사용자 정의 할 수있는 그림자 관련 속성이 있습니다. 그러나shadowPath
성능 향상을 위해 속성을 설정해야 합니다.
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
그라디언트 그리기
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);
그래디언트를 지금 그리면 모양 내부가 아닌 모양 위에있게됩니다. 아니, 우리는 모양의 그라데이션 채우기를 가질 수 없습니다 (나는 당신이 그것을 생각하고 있다는 것을 알고 있습니다). 그래디언트를 마스킹하여 세그먼트 밖으로 나가도록해야합니다. 이를 위해 해당 세그먼트의 마스크가 될 다른 레이어를 만듭니다 . 그것은 이어야 다른 레이어, 문서 마스크 레이어 계층 구조의 일부인 경우의 동작은 "정의되지 않은"것이 분명하다. 마스크의 좌표계는 그라데이션에 대한 하위 레이어의 좌표계와 동일하므로 설정하기 전에 세그먼트 모양을 변환해야합니다.
CAShapeLayer *mask = [CAShapeLayer layer];
CGAffineTransform translation = CGAffineTransformMakeTranslation(-CGRectGetMinX(gradient.frame),
-CGRectGetMinY(gradient.frame));
mask.path = CGPathCreateCopyByTransformingPath(segment.path,
&translation);
gradient.mask = mask;
필요한 모든 것은 Quartz 2D 프로그래밍 가이드 에서 다룹니다 . 나는 당신이 그것을 살펴 보는 것이 좋습니다.
그러나 모든 것을한데 모으는 것이 어려울 수 있으므로 단계별로 안내해 드리겠습니다. 크기를 가져와 세그먼트 중 하나와 비슷한 이미지를 반환하는 함수를 작성합니다.
다음과 같이 함수 정의를 시작합니다.
static UIImage *imageWithSize(CGSize size) {
세그먼트의 두께에 대한 상수가 필요합니다.
static CGFloat const kThickness = 20;
및 세그먼트를 나타내는 선의 너비에 대한 상수 :
static CGFloat const kLineWidth = 1;
그림자의 크기에 대한 상수 :
static CGFloat const kShadowWidth = 8;
다음으로 그릴 이미지 컨텍스트를 만들어야합니다.
UIGraphicsBeginImageContextWithOptions(size, NO, 0); {
UIGraphicsEndImageContext
나중에 전화하도록 상기시키는 추가 수준의 들여 쓰기를 좋아하기 때문에 그 줄 끝에 왼쪽 중괄호를 넣었 습니다.
호출해야하는 많은 함수가 UIKit 함수가 아닌 Core Graphics (Quartz 2D라고도 함) 함수이기 때문에 다음을 가져와야합니다 CGContext
.
CGContextRef gc = UIGraphicsGetCurrentContext();
이제 실제로 시작할 준비가되었습니다. 먼저 경로에 호를 추가합니다. 호는 우리가 그리려는 세그먼트의 중심을 따라 이어집니다.
CGContextAddArc(gc, size.width / 2, size.height / 2,
(size.width - kThickness - kLineWidth) / 2,
-M_PI / 4, -3 * M_PI / 4, YES);
이제 Core Graphics에 경로를 설명하는 "스트로크"버전으로 경로를 바꾸도록 요청합니다. 먼저 선의 두께를 세그먼트에 원하는 두께로 설정합니다.
CGContextSetLineWidth(gc, kThickness);
그리고 선 끝 스타일을 "butt"로 설정하여 끝을 제곱합니다 .
CGContextSetLineCap(gc, kCGLineCapButt);
그런 다음 Core Graphics에 경로를 스트로크 버전으로 대체하도록 요청할 수 있습니다.
CGContextReplacePathWithStrokedPath(gc);
이 경로를 선형 그래디언트로 채우려면 모든 작업을 경로 내부에 클리핑하도록 Core Graphics에 지시해야합니다. 이렇게하면 Core Graphics가 경로를 재설정하지만 나중에 가장자리 주위에 검은 선을 그리기 위해 경로가 필요합니다. 따라서 여기에 경로를 복사합니다.
CGPathRef path = CGContextCopyPath(gc);
세그먼트가 그림자를 드리 우기를 원하므로 그리기 전에 그림자 매개 변수를 설정합니다.
CGContextSetShadowWithColor(gc,
CGSizeMake(0, kShadowWidth / 2), kShadowWidth / 2,
[UIColor colorWithWhite:0 alpha:0.3].CGColor);
세그먼트를 (그라데이션으로) 채우고 획을 긋고 (검은 색 윤곽선을 그립니다). 두 작업 모두에 대해 단일 그림자를 원합니다. 투명 레이어를 시작하여 Core Graphics에 알립니다.
CGContextBeginTransparencyLayer(gc, 0); {
CGContextEndTransparencyLayer
나중에 전화하도록 상기시켜 줄 추가 수준의 들여 쓰기를 좋아하기 때문에 그 줄 끝에 왼쪽 중괄호를 넣었 습니다.
채우기를 위해 컨텍스트의 클립 영역을 변경할 것이지만 나중에 윤곽선을 획을 그을 때 자르고 싶지 않기 때문에 그래픽 상태를 저장해야합니다.
CGContextSaveGState(gc); {
CGContextRestoreGState
나중에 전화하도록 상기시켜 줄 추가 수준의 들여 쓰기를 좋아하기 때문에 그 줄 끝에 왼쪽 중괄호를 넣었 습니다.
경로를 그라디언트로 채우려면 그라디언트 개체를 만들어야합니다.
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);
그라디언트의 시작점과 끝 점도 알아 내야합니다. 경로 경계 상자를 사용합니다.
CGRect bbox = CGContextGetPathBoundingBox(gc);
CGPoint start = bbox.origin;
CGPoint end = CGPointMake(CGRectGetMaxX(bbox), CGRectGetMaxY(bbox));
그래디언트를 가로 또는 세로 중 더 긴 방향으로 강제로 그려야합니다.
if (bbox.size.width > bbox.size.height) {
end.y = start.y;
} else {
end.x = start.x;
}
이제 마침내 그라디언트를 그리는 데 필요한 모든 것이 있습니다. 먼저 경로에 클립합니다.
CGContextClip(gc);
그런 다음 그라디언트를 그립니다.
CGContextDrawLinearGradient(gc, gradient, start, end, 0);
그런 다음 그라디언트를 해제하고 저장된 그래픽 상태를 복원 할 수 있습니다.
CGGradientRelease(gradient);
} CGContextRestoreGState(gc);
를 호출 CGContextClip
하면 Core Graphics가 컨텍스트의 경로를 재설정합니다. 경로는 저장된 그래픽 상태의 일부가 아닙니다. 그것이 우리가 더 일찍 사본을 만든 이유입니다. 이제 해당 사본을 사용하여 컨텍스트에서 경로를 다시 설정할 시간입니다.
CGContextAddPath(gc, path);
CGPathRelease(path);
이제 경로를 스트로크하여 세그먼트의 검은 색 윤곽선을 그릴 수 있습니다.
CGContextSetLineWidth(gc, kLineWidth);
CGContextSetLineJoin(gc, kCGLineJoinMiter);
[[UIColor blackColor] setStroke];
CGContextStrokePath(gc);
다음으로 Core Graphics에 투명 레이어를 종료하도록 지시합니다. 그러면 우리가 그린 것을보고 그 아래에 그림자를 추가합니다.
} CGContextEndTransparencyLayer(gc);
이제 우리는 모두 그리기를 마쳤습니다. UIKit에 UIImage
이미지 컨텍스트에서 생성 한 다음 컨텍스트를 삭제하고 이미지를 반환하도록 요청합니다.
UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return image;
}
이 요점에서 코드를 모두 찾을 수 있습니다 .
이것은 Rob Mayoff의 답변의 Swift 3 버전입니다. 이 언어가 얼마나 더 효율적인지보십시오! 이것은 MView.swift 파일의 내용 일 수 있습니다 :
import UIKit
class MView: UIView {
var size = CGSize.zero
override init(frame: CGRect) {
super.init(frame: frame)
size = frame.size
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
var niceImage: UIImage {
let kThickness = CGFloat(20)
let kLineWidth = CGFloat(1)
let kShadowWidth = CGFloat(8)
UIGraphicsBeginImageContextWithOptions(size, false, 0)
let gc = UIGraphicsGetCurrentContext()!
gc.addArc(center: CGPoint(x: size.width/2, y: size.height/2),
radius: (size.width - kThickness - kLineWidth)/2,
startAngle: -45°,
endAngle: -135°,
clockwise: true)
gc.setLineWidth(kThickness)
gc.setLineCap(.butt)
gc.replacePathWithStrokedPath()
let path = gc.path!
gc.setShadow(
offset: CGSize(width: 0, height: kShadowWidth/2),
blur: kShadowWidth/2,
color: UIColor.gray.cgColor
)
gc.beginTransparencyLayer(auxiliaryInfo: nil)
gc.saveGState()
let rgb = CGColorSpaceCreateDeviceRGB()
let gradient = CGGradient(
colorsSpace: rgb,
colors: [UIColor.gray.cgColor, UIColor.white.cgColor] as CFArray,
locations: [CGFloat(0), CGFloat(1)])!
let bbox = path.boundingBox
let startP = bbox.origin
var endP = CGPoint(x: bbox.maxX, y: bbox.maxY);
if (bbox.size.width > bbox.size.height) {
endP.y = startP.y
} else {
endP.x = startP.x
}
gc.clip()
gc.drawLinearGradient(gradient, start: startP, end: endP,
options: CGGradientDrawingOptions(rawValue: 0))
gc.restoreGState()
gc.addPath(path)
gc.setLineWidth(kLineWidth)
gc.setLineJoin(.miter)
UIColor.black.setStroke()
gc.strokePath()
gc.endTransparencyLayer()
let image = UIGraphicsGetImageFromCurrentImageContext()!
UIGraphicsEndImageContext()
return image
}
override func draw(_ rect: CGRect) {
niceImage.draw(at:.zero)
}
}
다음과 같이 viewController에서 호출하십시오.
let vi = MView(frame: self.view.bounds)
self.view.addSubview(vi)
각도를 라디안으로 변환하기 위해 ° 접미사 연산자를 만들었습니다 . 따라서 이제 예를 들어 45도를 사용할 수 있습니다. 그러면 45도에서 라디안으로 변환됩니다. 이 예제는 Ints에 대한 것이며 필요한 경우 Float 유형에 대해서도 확장하십시오.
postfix operator °
protocol IntegerInitializable: ExpressibleByIntegerLiteral {
init (_: Int)
}
extension Int: IntegerInitializable {
postfix public static func °(lhs: Int) -> CGFloat {
return CGFloat(lhs) * .pi / 180
}
}
이 코드를 유틸리티 신속한 파일에 넣으십시오.
참고 URL : https://stackoverflow.com/questions/15866179/draw-segments-from-a-circle-or-donut
'programing tip' 카테고리의 다른 글
jQuery에서 div 내부의 모든 HTML을 제거하고 싶습니다. (0) | 2020.10.28 |
---|---|
SqlConnection 시간 제한 변경 (0) | 2020.10.28 |
Rails Select Drop Down for States? (0) | 2020.10.28 |
자바 스크립트에서 xml 엔티티를 이스케이프하는 방법은 무엇입니까? (0) | 2020.10.28 |
Pandas 데이터 프레임에서 모두 0이있는 행 삭제 (0) | 2020.10.28 |