어떻게 원형 진도표 단추를 실현합니까

7087 단어
효과 전시.gif

예비 지식

  • CADisplayLink
  • UIBezierPath

  • 실현 분석

  • 전체 애니메이션의 실현은 두 부분으로 나눌 수 있다.
  • 원환이 확대되는 과정은 원환이 확대될 때 너비가 변하지 않도록 어떻게 보장합니까
  • 진행률 막대를 그리는 과정
  • 전체 과정은 주로 CADisplaylink가 터치되었을 때 호출된 방법에서 다시 그려서 이루어진다.1단계: CADisplaylink는 애플리케이션의 그리기와 표시 새로 고침을 동기화하는 데 사용할 수 있는 타이머 객체입니다.만약에 displaylink 귀속 방법의 이름이 A 방법이라고 가정하면 카드가 없을 때 iOS 장치 화면이 초당 60회 갱신되고 displaylink의 속성인 프레임 인터벌은 기본값일 때 초당 60회 A 방법으로 리셋되며 프레임 인터벌은 2로 바뀔 때 초당 30(60/2)회 A 방법으로 리셋된다.확대 프로세스를 0.2초로 설정하면 전체 확대 프로세스에서 호출되는 A 방법의 횟수는 12회입니다.원환의 최대 반지름은 x이고 초기 반지름은 y이며, 호출 방법마다 원환의 반지름은 r=(x-y)/12+y이다.A 방법에서 베셀 곡선으로 반경 r,linewidth가 필요한 너비의 원환을 그리면 효과를 볼 수 있다.단계 2: 두 번째 단계는 진도표를 그리는 과정입니다.진행률 막대 그리기는 주로 CAShapeLayer의 strokeStart와 strokeEnd 속성에 의존합니다.우선 진도표 레이더의 경로, 즉 빨간색 동그라미가 있는 경로를 미리 그려야 한다.strokeStart와 strokeEnd는 각각 path의 start 위치와end 위치를 표시하고 수치 범위는 [0,1]이다.strokeStart가 0이고 strokeEnd가 1이라고 가정하면 완전한 경로가 그려지고 strokeStart가 0이고 strokeEnd가 0.5이면 완전한 경로의 절반이 그려진다.동시에 이 두 속성은 모두 애니메이션을 지원한다.하지만 여기서는 CADisPlayLink를 사용합니다. 값을 직접 바꾸기만 하면 됩니다.맞아요. 초기화할 때 경로를 잘 그리고 strokeStart와 strokeEnd 속성에 0을 부여하면 진도표를 그리지 않습니다.그리고 displayLink에서 A 방법을 호출할 때 strokeEnd에delta,delta=1/(진도표 지속시간*60)를 점차적으로 추가하면 진도표의 애니메이션을 실현할 수 있다.

  • 구체적 실현

    // 
    static float const maxRecordTime = 10.f;
    // 
    static float const startRadius = 35.f;
    // 
    static float const endRadius = 50.f;
    // 
    static float const lineWidth = 6.f;
    
    @interface KiraCircleButton ()
    
    @property (nonatomic, strong) CAShapeLayer *circleLayer;// 
    @property (nonatomic, strong) CAShapeLayer *maskLayer;// 
    @property (nonatomic, strong) CAShapeLayer *drawLayer;// 
    
    @property (nonatomic, strong) CADisplayLink *displayLink;
    @property (nonatomic, assign) CGFloat currentRadius; // 
    @property (nonatomic, strong) UIVisualEffectView* effectView;
    @property (nonatomic, assign) CGPoint centerPoint;
    @property (nonatomic, assign) float currentRecordTime;
    
    @end
    
    
  • 먼저 View를 초기화하고view에 제스처를 더하면 본고의 애니메이션은 길게 누르는 제스처를 통해 촉발된다.진도조 애니메이션의 경로를 계산하고view에 레이더를 미리 추가하여 strokeStart와 strokeEnd 속성을 0으로 설정합니다.
  • - (void)initUI {
        self.centerPoint = CGPointMake(self.frame.size.width / 2, self.frame.size.height / 2);
        [self setUserInteractionEnabled:YES];
    // 
        UITapGestureRecognizer * tap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(doSomeThingWhenTap)];
        [self addGestureRecognizer:tap];
        
        UILongPressGestureRecognizer *longPress = [[UILongPressGestureRecognizer alloc] initWithTarget:self action:@selector(doSomeThingWhenLongTap:)];
        longPress.minimumPressDuration = 0.2;
        longPress.delegate = self;
        [self addGestureRecognizer:longPress];
        
        // 
        UIBlurEffect *effect = [UIBlurEffect effectWithStyle:UIBlurEffectStyleRegular];
        self.effectView = [[UIVisualEffectView alloc] initWithEffect:effect];
        self.effectView.userInteractionEnabled = NO;
        [self addSubview:self.effectView];
        [self.effectView setFrame:CGRectMake(startRadius - endRadius, startRadius - endRadius, 2 * endRadius, 2 * endRadius)];
        
        UIBezierPath *backPath = [UIBezierPath bezierPathWithArcCenter:self.centerPoint radius:endRadius - lineWidth/2 startAngle:- M_PI_2 endAngle:3 * M_PI_2 clockwise:YES];
        
        CAShapeLayer *secondLayer = [CAShapeLayer layer];
        secondLayer.strokeColor = [UIColor colorWithRed:1 green:64.f/255.f blue:64.f/255.f alpha:1].CGColor;
        secondLayer.lineWidth = lineWidth;
        secondLayer.fillColor = [UIColor clearColor].CGColor;
        secondLayer.path = backPath.CGPath;
        secondLayer .strokeStart = 0;
        secondLayer.strokeEnd = 0;
    
        _drawLayer = secondLayer;
        _circleLayer = [CAShapeLayer layer];
        _maskLayer = [CAShapeLayer layer];
        [self resetCaptureButton];
        [self.layer addSublayer:_circleLayer];
        [self.layer setMask:_maskLayer];
        [self.layer addSublayer:secondLayer];
    }
    
  • 그리고 길게 호출하는 방법에서displaylink를 초기화하고changeRedius 방법을 displayLink에 연결합니다.displaylink를 현재 runloop에 추가하고 displaylink의paused를 NO로 설정합니다.
  • - (void)doSomeThingWhenLongTap:(UILongPressGestureRecognizer *)gesture {
        if (gesture.state == UIGestureRecognizerStateBegan) {
            NSLog(@"Im long tapped start");
            self.displayLink = [CADisplayLink displayLinkWithTarget:self
                                                           selector:@selector(changeRadius)];
            [self.displayLink addToRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
            self.displayLink.paused = NO;
            if (self.delegate && [self.delegate respondsToSelector:@selector(startProgress)]) {
                [self.delegate startProgress];
            }
        } else if (gesture.state == UIGestureRecognizerStateEnded) {
            NSLog(@"Im long tapped end");
            //end
            self.displayLink.paused = YES;
            if (self.delegate && [self.delegate respondsToSelector:@selector(endProgress)]) {
                [self.delegate endProgress];
            }
            [self resetCaptureButton];
        }
    }
    
  • 마지막으로 관건적인 change Radius 방법, 즉 A 방법입니다.displaylink가 터치될 때마다changeRadius 방법을 사용하여currentRadius가 최대치로 증가하기 전에 확대 애니메이션을 실행합니다. 최대치에 도달한 후에 진도표가 그려진 애니메이션입니다.
  • - (void)changeRadius {
        CGFloat toValue = endRadius - lineWidth/2;
        CGFloat fromValue = startRadius - lineWidth/2;
        CGFloat duration = 0.2;
        CGFloat times = 60 * duration;
        CGFloat delta = (toValue - fromValue) / times;
        _currentRecordTime += 1.f/60;
        _currentRadius += delta;
        if (_currentRadius <= toValue) {
            UIBezierPath *path = [UIBezierPath bezierPathWithArcCenter:self.centerPoint radius: _currentRadius startAngle:0 endAngle:M_PI * 2 clockwise:YES];
            _circleLayer.path = path.CGPath;
            _circleLayer.lineWidth = lineWidth;
    
            UIBezierPath *maskPath = [UIBezierPath bezierPathWithArcCenter:self.centerPoint radius:_currentRadius + 2 startAngle:0 endAngle:M_PI * 2 clockwise:YES];
            _maskLayer = [CAShapeLayer layer];
            _maskLayer.path = maskPath.CGPath;
            [self.layer setMask:_maskLayer];
        } else {
            CGFloat delta = 1 / (maxRecordTime * 60);
            self.drawLayer.strokeEnd += delta;
            if (self.drawLayer.strokeEnd >= 1) {
                self.displayLink.paused = YES;
                if (self.delegate && [self.delegate respondsToSelector:@selector(endProgress)]) {
                    [self.delegate endProgress];
                }
            }
        }
        
    }
    

    최후


    데모 링크를 첨부하면 데모는 상술한 효과를 실현했지만 복용성이 비교적 높은 컨트롤러로 봉인되지 않았고 주로 실현 방향을 제공하는 것을 위주로 한다.어떤 의문이 있으면 지적해 주십시오
    보완 업데이트: 2019/02/17
  • 오류: CADisplayLink의 리셋 주파수는 60회/초 정도이지만 고정되지 않습니다. 매번 CADisplayLink를 호출하는 시간 간격이 평균적이지 않기 때문에 우리는 호출 횟수에 따라 1/60의 시간 간격을 곱해서 현재 경험한 시간을 얻을 수 없습니다.현재 경과 시간을 정확하게 계산하는 방법은 현재 시간을 얻고 시작 시간을 줄이는 것이다.
  • 확장: KiraCircleButton Demo의 용이성, 유지보수성, 확장성을 고려하여 코드를 업데이트했습니다. 애니메이션은 서로 다른 완동 함수를 설정하고 Demo 주소는 변하지 않습니다. 첫 번째 버전의 코드를 보려면 최초의 제출과 읽기를 할 수 있습니다.새로운 KiraCircleButton의 디자인 실현에 대한 소개는 CADisplayLink 애니메이션 진급
  • 을 클릭하세요.

    좋은 웹페이지 즐겨찾기