创建 UINavigationBarButton 中的缩进外观 - 以编程方式

Posted

技术标签:

【中文标题】创建 UINavigationBarButton 中的缩进外观 - 以编程方式【英文标题】:Create the indented look found in UINavigationBarButton - programmatically 【发布时间】:2012-06-12 08:55:04 【问题描述】:

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

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

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

【问题讨论】:

你看过PaintCode吗?您应该能够使用深色内阴影和浅色阴影重新创建缩进外观。 尝试在这里挖掘:***.com/search?q=inner+shadow+core+graphics 它不是“顶部稍暗”;很可能它有一个垂直偏移(来自上方的光,阴影距离!在 Photoshop 中为 0)。看起来阴影半径约为2px,垂直距离约为1px。 (这就是为什么它的上部也会“流血”) 【参考方案1】:

似乎您想要一个看起来像阴影的边框。由于阴影呈现出某种渐变,乍一看无法将边框设置为渐变。但是,可以创建一个表示边界的路径,然后用渐变填充它。 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。

我也在这里解释代码和我的方法:Beveling-Shapes in Core Graphics

【讨论】:

【参考方案2】:

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

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

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

您可以通过将剪切路径(参见CGContextClip 和相关函数)设置为按钮的形状,设置阴影(参见CGContextSetShadowWithColor 和相关函数),然后围绕外部绘制来创建内部阴影你想要阴影的形状。对于内部阴影,描边 (CGContextStrokePath) 一个比按钮大一点的圆形矩形,使用较粗的描边宽度 (CGContextSetLineWidth) 所以有足够的“墨水”来生成阴影(记住,这个描边赢了'由于剪切路径不可见)。

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

这也有一个命令。如果您考虑使用物理介质绘制这些东西的顺序,应该很明显:首先绘制外阴影,然后是按钮的填充,然后是内阴影。如果内部阴影没有为您提供足够明显的轮廓,您可以在此之后添加笔触。


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

【讨论】:

以上是关于创建 UINavigationBarButton 中的缩进外观 - 以编程方式的主要内容,如果未能解决你的问题,请参考以下文章

创建表的时候创建索引

Spark SQL怎么创建编程创建DataFrame

要用MFC创建一个窗口,如何创建?

oracle 安装完了,怎么创建用户和数据库???

oracle 安装完了,怎么创建用户和数据库???

mysql在创建表的时候可以创建字段那么创建新数据库的时候可以顺便创建表吗