3

我正在尝试以编程方式重新创建可以在 UINavigationBarButton 上看到的缩进按钮外观。不是闪亮的两色外观或渐变,只是周边阴影:

在此处输入图像描述

它看起来像整个视野周边的内部暗阴影,顶部稍微暗一些?然后是围绕下视图周边的外部突出显示阴影。

我用 Core Graphics 玩了一些,并尝试了 QuartzCore 和 view.layer.shadowRadius 和 .shadowOffset 的阴影,但甚至无法让较低的突出显示看起来正确。我也不确定从哪里开始实现带有内部偏移的暗阴影和带有外部偏移的浅阴影。

4

2 回答 2

6

似乎您想要一个看起来像阴影的边框。由于阴影呈现出某种渐变,乍一看无法将边框设置为渐变。但是,可以创建一个表示边界的路径,然后用渐变填充它。Apple 提供了一个似乎鲜为人知的功能,称为CGPathCreateCopyByStrokingPath。这需要一条路径(例如,圆角矩形)并创建一个新路径,该路径将是旧路径的笔划,给定您传递给函数的设置(如线宽、连接/上限设置、斜接限制等)。因此,假设您定义了一条路径(这不完全是 Apple 提供的,但它是相似的):

+ (UIBezierPath *) bezierPathForBackButtonInRect:(CGRect)rect withRoundingRadius:(CGFloat)radius{
    UIBezierPath *path = [UIBezierPath bezierPath];
    CGPoint mPoint = CGPointMake(CGRectGetMaxX(rect) - radius, rect.origin.y);
    CGPoint ctrlPoint = mPoint;
    [path moveToPoint:mPoint];

    ctrlPoint.y += radius;
    mPoint.x += radius;
    mPoint.y += radius;
    if (radius > 0) [path addArcWithCenter:ctrlPoint radius:radius startAngle:M_PI + M_PI_2 endAngle:0 clockwise:YES];

    mPoint.y = CGRectGetMaxY(rect) - radius;
    [path addLineToPoint:mPoint];

    ctrlPoint = mPoint;
    mPoint.y += radius;
    mPoint.x -= radius;
    ctrlPoint.x -= radius;
    if (radius > 0) [path addArcWithCenter:ctrlPoint radius:radius startAngle:0 endAngle:M_PI_2 clockwise:YES];

    mPoint.x = rect.origin.x + (10.0f);
    [path addLineToPoint:mPoint];

    [path addLineToPoint:CGPointMake(rect.origin.x, CGRectGetMidY(rect))];

    mPoint.y = rect.origin.y;
    [path addLineToPoint:mPoint];

    [path closePath];
    return path;
}

这将返回类似于 Apple 的后退按钮的路径(我在我的应用程序中使用它)。我已将此方法(以及更多方法)作为一个类别添加到 UIBezierPath。

现在让我们在绘图例程中添加该内部阴影:

- (void) drawRect:(CGRect)rect{
    UIBezierPath *path = [UIBezierPath bezierPathForBackButtonInRect:rect withRoundingRadius:5.0f];
    //Just fill with blue color, do what you want here for the button
    [[UIColor blueColor] setFill]; 
    [path fill];

    [path addClip]; //Not completely necessary, but borders are actually drawn 'around' the path edge, so that half is inside your path, half is outside adding this will ensure the shadow only fills inside the path

    //This strokes the standard path, however you might want to might want to  inset the rect, create a new 'back button path' off the inset rect and create the inner shadow path off that.  
    //The line width of 2.0f will actually show up as 1.0f with the above clip: [path addClip];, due to the fact that borders are drawn around the edge 
    UIBezierPath *innerShadow = [UIBezierPath bezierPathWithCGPath: CGPathCreateCopyByStrokingPath(path.CGPath, NULL, 2.0f, path.lineCapStyle, path.lineJoinStyle, path.miterLimit)];
    //You need this, otherwise the center (inside your path) will also be filled with the gradient, which you don't want
    innerShadow.usesEvenOddFillRule = YES;
    [innerShadow addClip];

    //Now lets fill it with a vertical gradient
    CGContextRef context = UIGraphicsGetCurrentContext();
    CGPoint start = CGPointMake(0, 0);
    CGPoint end = CGPointMake(0, CGRectGetMaxY(rect));
    CGFloat locations[2] = { 0.0f, 1.0f};
    NSArray *colors =  [NSArray arrayWithObjects:(id)[UIColor colorWithWhite:.7f alpha:.5f].CGColor, (id)[UIColor colorWithWhite:.3f alpha:.5f].CGColor, nil];
    CGGradientRef gradRef = CGGradientCreateWithColors(CGColorSpaceCreateDeviceRGB(), (__bridge CFArrayRef)colors, locations);
    CGContextDrawLinearGradient(context, gradRef, start, end, 0);
    CGGradientRelease(gradRef);
}

现在这只是一个简单的例子。我不保存/恢复上下文或任何你可能想要做的事情。有些事情你可能仍然想做让它变得更好,比如如果你想使用普通边框,可能会插入“阴影”路径。您可能想要使用更多/不同的颜色和位置。但这应该让你开始。

更新

您可以使用另一种方法来创建此效果。我编写了一个算法来斜切核心图形中的任意贝塞尔路径。这可用于创建您正在寻找的效果。这是我如何在我的应用程序中使用它的示例:

斜面后退按钮

您将 CGContextRef、CGPathRef、斜角的大小以及您希望它用于高亮/阴影的颜色传递给例程。

我用于此的代码可以在这里找到:Github - Beveling Algorithm

我还在这里解释了代码和我的方法:核心图形中的斜切形状

于 2012-06-20T17:51:40.717 回答
2

使用图层的阴影不会这样做。你需要一个浅色的外阴影和一个深色的内阴影来获得这种效果。一个图层只能有一个(外)阴影。(此外,图层阴影会动态重绘,并强制基于 CPU 的渲染,这会降低性能。)

您需要在视图的drawRect:方法或层的drawInContext:方法中使用 CoreGraphics 进行自己的绘图。(或者您绘制到图像上下文中,然后重用图像。)所述绘图将主要使用CGContext functions. (我会在下面列出一些,但这个链接有他们所有的文档。)

对于圆形矩形按钮,您可能会发现创建适当的 CGPath 很繁琐——相反,您可以使用+[UIBezierPath bezierPathWithRoundedRect:cornerRadius:]和 path 的CGPath属性来设置上下文的当前路径CGContextAddPath

您可以通过将剪切路径(seeCGContextClip和相关函数)设置为按钮的形状,设置阴影(seeCGContextSetShadowWithColor和相关函数),然后在要添加阴影的形状的外部绘制来创建内部阴影。对于内部阴影,描边 ( CGContextStrokePath) 是一个比您的按钮大一点的圆形矩形,使用粗描边宽度 ( CGContextSetLineWidth) 所以有大量的“墨水”来生成阴影(请记住,这个描边将不可见,因为剪切路径)。

您可以以几乎相同的方式创建外部阴影——这次不要使用剪切路径,因为您希望阴影位于形状之外,并填充 ( CGContextFillPath) 按钮的形状而不是抚摸它。请注意,绘制阴影是一种“模式”:您保存图形状态 ( CGContextSaveGState),设置阴影,然后绘制您想要查看阴影的形状(当您处于模式),最后恢复状态(CGContextRestoreGState)以退出“影子模式”。由于该模式不绘制形状,只绘制阴影,因此您需要单独绘制形状本身。

也有一个命令要做这一切。如果您考虑一下使用物理媒体绘制这些东西的顺序,这应该是显而易见的:首先绘制外阴影,然后是按钮的填充,然后是内阴影。如果内部阴影没有给你一个足够明显的轮廓,你可以在那之后添加一个笔触。


有一些绘图工具可以输出 CoreGraphics 的源代码:Opacity是我使用的一种。但是要小心这些,因为它们生成的代码可能效率不高。

于 2012-06-15T07:01:58.380 回答