【问题标题】:How can I draw a line with CoreGraphics, whose trail will begin to disappear at a certain length?如何使用 CoreGraphics 绘制一条线,其轨迹将在一定长度处开始消失?
【发布时间】:2016-01-19 12:13:46
【问题描述】:

从 1:04 开始,可以在这里看到我所说的内容。 5-10 秒后,你就会明白我所说的“痕迹将开始消失”的意思。

我当前的应用程序在用户在屏幕上移动时画出了一条微弱的线条。然而,这条线一直持续到-touchesEnded:withEvent:,当我设置imageView.image = nil时。

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

我怎样才能做到这一点?

【问题讨论】:

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


    【解决方案1】:

    我不知道你现在是怎么做的,但我会这样做......

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

    3. -drawRect: 方法中绘制所有子路径及其给定的alpha。

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

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

    /// 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);
        }
    
    }
    

    看起来有很多代码,但我敢肯定你现在已经设置了一半来画线!

    请注意,根据长度使试用淡出的一种简单方法是将CADisplayLink 更新方法中对setNeedsDisplay 的调用移至-touchesMoved: 方法,并使显示链接无效-touchesEnded:.

    呸。结束了……我做过的最长的回答。


    完成结果

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

    【讨论】:

    • 你能做出这样的大头小尾吗? github.com/angelXwind/iSwipe/raw/master/img/animate.gif
    • @TomSawyer 当然,您可以改变每个线段的线宽来创建这种效果。 See here 来自开源项目的示例,您链接到的 gif 来自。 (我知道我评论了您现在已删除的帖子之一,我将其留在这里以供参考)。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2016-12-19
    • 2015-02-22
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-10-17
    相关资源
    最近更新 更多