如何用CoreGraphics绘制一条线,它的线索将开始以一定的长度消失?

时间:2016-01-19 12:13:47

标签: objective-c cocoa-touch core-graphics

从1:04开始,我可以在这里看到我在说什么。 5-10秒后,您将了解我的意思"线索将开始消失"。

我当前的应用程序绘制了一条微弱的线条,用户在屏幕上移动。但是,当我设置-touchesEnded:withEvent:

时,这些线会一直延伸到imageView.image = nil

我想要实现的是一条正在被绘制的线条,当您绘制线条时,线条的最旧部分将变得更加透明,直到它最终消失。线条绘制可以是基于时间的,也可以基于当前线条长度的长度。

我怎么能实现这个目标?

1 个答案:

答案 0 :(得分:8)

我不知道你现在是怎么做的,但这就是我要去做的事情......

  1. 创建自定义对象以存储一小部分路径,以及alphadelay
  2. touchesMoved:中计算用户触摸位置的变化并根据该位置生成新的子路径,然后将其包装在自定义对象中。

    < / LI>
  3. 使用给定的alpha格式绘制-drawRect:方法中的所有子路径。

  4. 设置CADisplayLink以更新子路径的alpha和延迟。

  5. 首先,让我们定义我们的自定义对象......

    /// Represents a small portion of a trail. 
    @interface trailSubPath : NSObject
    
    /// The subpath of the trail.
    @property (nonatomic) CGPathRef path;
    
    /// The alpha of this section.
    @property (nonatomic) CGFloat alpha;
    
    /// The delay before the subpath fades
    @property (nonatomic) CGFloat delay;
    
    @end
    

    让我们给它一个方便的初始化器,让它看起来光滑......

    @implementation trailSubPath
    
    +(instancetype) subPathWithPath:(CGPathRef)path alpha:(CGFloat)alpha delay:(CGFloat)delay {
        trailSubPath* subpath = [[self alloc] init];
        subpath.path = path;
        subpath.alpha = alpha;
        subpath.delay = delay;
        return subpath;
    }
    
    @end
    

    让我们在UIView 的顶部定义一些常量(如果你还没有,那么创建一个子类,因为我们将使用-drawRect:绘图)

    /// How long before a subpath starts to fade.
    static CGFloat const pathFadeDelay = 5.0;
    
    /// How long the fading of the subpath goes on for.
    static CGFloat const pathFadeDuration = 1.0;
    
    /// The stroke width of the path.
    static CGFloat const pathStrokeWidth = 3.0;
    

    UIView中,您希望存储NSMutableArraytrailSubPath个对象,以及我们稍后需要的其他变量。

    我决定使用CADisplayLink来处理trailSubPath对象的更新。这样,代码将在所有设备上以相同的速度运行(以较慢设备上较低的FPS为代价)​​。

    @implementation view {
    
        UIColor* trailColor; // The stroke color of the trail
        NSMutableArray* trailSubPaths; // The array of trailSubPaths
    
        CGPoint lastPoint; // Last point the user touched
        BOOL touchedDown; // Whether the user is touching the screen
    
        CADisplayLink* displayLink; // A display link in order to allow the code to run at the same speed on different devices
    }
    

    -initWithFrame:方法中,我们将进行一些基本设置......

    -(instancetype) initWithFrame:(CGRect)frame {
        if (self = [super initWithFrame:frame]) {
    
            trailSubPaths = [NSMutableArray array];
            trailColor = [UIColor redColor];
    
            self.backgroundColor = [UIColor whiteColor];
        }
        return self;
    }
    

    现在让我们设置UIResponder触摸方法......

    -(void) touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
        lastPoint = [[[event allTouches] anyObject] locationInView:self];
        touchedDown = YES;
    
        [displayLink invalidate]; // In case it's already running.
        displayLink = [CADisplayLink displayLinkWithTarget:self selector:@selector(displayLinkDidFire)];
        [displayLink addToRunLoop:[NSRunLoop mainRunLoop] forMode:NSRunLoopCommonModes];
    }
    
    -(void) touchesMoved:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
        if (touchedDown) {
    
            CGPoint p = [[[event allTouches] anyObject] locationInView:self];
    
            CGMutablePathRef mutablePath = CGPathCreateMutable(); // Create a new subpath
            CGPathMoveToPoint(mutablePath, nil, lastPoint.x, lastPoint.y);
            CGPathAddLineToPoint(mutablePath, nil, p.x, p.y);
    
            // Create new subpath object
            [trailSubPaths addObject:[trailSubPath subPathWithPath:CGPathCreateCopy(mutablePath) alpha:1.0 delay:pathFadeDelay]];
    
            CGPathRelease(mutablePath);
    
            lastPoint = p;
        }
    }
    
    -(void) touchesEnded:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
        touchedDown = NO;
    }
    
    -(void) touchesCancelled:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
        [self touchesEnded:touches withEvent:event];
    }
    

    那里没什么太复杂的,它只计算-touchesMoved:上触摸位置的变化,并根据此生成一个新的直线子路径。然后将其包装在我们的trailSubPath中并添加到数组中。

    现在,我们需要在CADisplayLink更新方法中设置逻辑。这只会计算alpha路径的变化和子路径的延迟,并删除已经淡出的所有子路径:

    -(void) displayLinkDidFire {
    
        // Calculate change in alphas and delays.
        CGFloat deltaAlpha = displayLink.duration/pathFadeDuration;
        CGFloat deltaDelay = displayLink.duration;
    
        NSMutableArray* subpathsToRemove = [NSMutableArray array];
    
        for (trailSubPath* subpath in trailSubPaths) {
    
            if (subpath.delay > 0) subpath.delay -= deltaDelay;
            else subpath.alpha -= deltaAlpha;
    
            if (subpath.alpha < 0) { // Remove subpath
                [subpathsToRemove addObject:subpath];
                CGPathRelease(subpath.path);
            }
        }
    
        [trailSubPaths removeObjectsInArray:subpathsToRemove];
    
        // Cancel running if nothing else to do.
        if (([trailSubPaths count] == 0) && !touchedDown) [displayLink invalidate];
        else [self setNeedsDisplay];
    }
    

    最后,我们只想覆盖drawRect:方法,以便在Core Graphics中绘制所有trailSubPath个对象:

    - (void)drawRect:(CGRect)rect {
    
        CGContextRef ctx = UIGraphicsGetCurrentContext();
        CGContextSetStrokeColorWithColor(ctx, trailColor.CGColor);
        CGContextSetLineWidth(ctx, pathStrokeWidth);
    
        for (trailSubPath* subpath in trailSubPaths) {
            CGContextAddPath(ctx, subpath.path);
            CGContextSetAlpha(ctx, subpath.alpha);
            CGContextStrokePath(ctx);
        }
    
    }
    

    它看起来像很多代码,但我相信你现在已经有一半设置来绘制你的线了!

    请注意,根据长度调整试用次数的一种简单方法是将setNeedsDisplay更新方法中的CADisplayLink调用转移到-touchesMoved:方法,并使-touchesEnded:上的显示链接无效。

    呼。它结束了......我做过的最长的答案。

    完成的结果

    Le finished result

    完整项目:https://github.com/hamishknight/Fading-Trail-Path