層層遞進_層層陰影解釋

層層遞進

UI編程 (UI PROGRAMMING)

At Bending Spoons, we use shadows in almost all our applications.Shadows are helpful to draw lightweight-looking contours around views.They give depth to the apps, making them look less flat and more pleasing.

在Bending Spoons中,我們幾乎在所有應用程序中都使用陰影,陰影有助於在視圖周圍繪製輕量級的輪廓,它們使應用程序具有深度,使它們看起來不那麼平整,更令人愉悅。

I used to struggle to figure out how all the shadows parameters work and how we can use them to implement what the designer prepared.I more than once found my self wondering: 「Where is the shadow I implemented? I can’t see it!」.

我過去一直在努力弄清所有陰影參數是如何工作的,以及如何使用它們來實現設計人員準備的東西。我不止一次地發現自己想知道: 在哪裏實現陰影? 我看不到!」。

Thus, I’d like to walk through them together to understand them better. I’d also like to share a small playground I prepared to play with the shadows and see how they update when we change their parameters. You can download the code at this link: just copy and paste it in a new playground and it should work!

因此,我想一起瀏覽它們以更好地瞭解它們。 我還想分享一個我準備與陰影一起玩耍的小操場,看看當我們更改其參數時它們如何更新。 您可以在此鏈接下載代碼:只需將其複製並粘貼到新的遊樂場中,它應該可以工作!

CALayer的Shadow屬性 (CALayer’s Shadow property)

First of all, let’s see what are the shadow’s properties.

首先,讓我們看看陰影的屬性是什麼。

Note: Before we start, keep in mind that shadows are rendered outside the bounds of the view. So, if you set either the view.clipToBounds or the view.layer.maskToBounds property to true, you won’t be able to see your shadows rendered.

注意:在開始之前,請記住,陰影是在視圖範圍之外渲染的。 因此,如果將view.clipToBoundsview.layer.maskToBounds屬性設置爲true ,則將無法看到渲染的陰影。

All the shadow’s properties belong to the CALayer object and every view has a hidden CALayer that can be accessed by the .layer property.

所有陰影的屬性都屬於CALayer對象,並且每個視圖都有一個隱藏的CALayer ,可以通過.layer屬性進行訪問。

From the .layer, we can type .shadow and see what the Xcode’s auto-completion feature suggests:

.layer ,我們可以鍵入.shadow然後查看Xcode的自動.shadow全功能建議:

  • shadowOpacity: it ranges between 0 and 1 and works as the .opacity property of every UIView.

    shadowOpacity :介於01之間,用作每個UIView.opacity屬性。

  • shadowColor: it accepts a CGColor and sets the color of the shadow. If you wonder what a CGColor is, it is a color that belongs to the CoreGraphics framework instead of the UIKit one. Luckily, every UIColor has a .cgColor property that performs the conversion.

    shadowColor :它接受CGColor並設置陰影的顏色。 如果您想知道CGColor是什麼,那麼它是屬於CoreGraphics框架而不是UIKit一種顏色。 幸運的是,每個UIColor都有一個.cgColor屬性來執行轉換。

  • shadowRadius: it accepts a Float. It defines how 「spread」 the shadow should be.

    shadowRadius :接受Float 。 它定義了陰影應該如何「擴散」。

  • shadowOffset: it accepts a CGSize object. Its .width property defines a horizontal translation for the shadow where positive values move the shadow to the right and negative values move the shadow to the left. Its .height property defines a vertical translation where positive values move the shadows to the bottom while negative values push it to the top.

    shadowOffset :它接受CGSize對象。 其.width屬性定義了陰影的水平平移,其中正值將陰影向右移動,負值將陰影向左移動。 其.height屬性定義了垂直平移,其中正值將陰影移至底部,而負值將陰影移至頂部。

  • shadowPath: this is the most complex property and we will explore it separately. It allows us to do pretty powerful things.

    shadowPath :這是最複雜的屬性,我們將分別進行探討。 它使我們能夠做非常強大的事情。

Most of these properties are pretty self-explanatory. I prepared a video to show how the shadow changes when we modify some values.

這些屬性大部分都是不言而喻的。 我準備了一個視頻,展示了當我們修改某些值時陰影如何變化。

Image for post
Video of how the shadow changes when we change the basic parameters.
更改基本參數時陰影如何變化的視頻。

As you can see, the more I increase the radius, the more blurred and extended the shadow is. We can play with all the other parameters to make it more or less intense, or to make the view pop out in a specific direction.For this example, I decided to use -10 and +10 as limits for both the radius and the offset. However, the shadow is not limited to them: we can push them further. This is how the view looks like with a shadow of radius == 45 and opacity == 0.5

如您所見,半徑增加得越多,陰影就越模糊和擴展。 我們可以使用所有其他參數來增加或減少強度,或使視圖朝特定方向彈出。在此示例中,我決定使用-10+10作爲半徑和偏移量的限制。 但是,陰影不僅限於它們:我們可以進一步推動它們。 這是radius == 45opacity == 0.5的陰影時的視圖外觀

Image for post
A shadow with a large radius’ value.
具有大半徑值的陰影。

There are a couple of issues with the current implementation:1. the shadow around the corners of the view is kind of weird, unprecise;2. when we set some negative value for the radius, something weird happens:

當前的實現有兩個問題:1。 視圖拐角處的陰影有點怪異,不精確; 2。 當我們爲半徑設置一些負值時,會發生一些奇怪的事情:

Image for post
shadowRadius is negative. shadowRadius爲負數時發生的情況。

This happens because our shadow is applied to the whole frame of the UIView. The frame is a simple CGRect and it does not know that the actual view has rounded corners.To fix this, we need the .shadowPath property, so… Let’s see how it works.

發生這種情況是因爲我們的陰影應用於UIView的整個框架。 該框架是一個簡單的CGRect ,它不知道實際的視圖是否具有圓角。要解決此問題,我們需要.shadowPath屬性,因此……讓我們看一下它是如何工作的。

CALayer.shadowPath (CALayer.shadowPath)

The shadow path property is pretty well documented by Apple. It also presents an interesting example of how to use it, so I suggest reading the full documentation for this field.For the sake of this article, this is the most relevant part:

Apple很好地記錄了陰影路徑屬性。 它還提供了一個有趣的用法示例,因此,我建議閱讀該領域的完整文檔 。就本文而言,這是最相關的部分:

If you specify a value for this property, the layer creates its shadow using the specified path instead of the layer’s composited alpha channel. The path you provide defines the outline of the shadow. It is filled using the non-zero winding rule and the current shadow color, opacity, and blur radius.

如果爲此屬性指定一個值,則圖層將使用指定的路徑而不是圖層的合成Alpha通道創建陰影。 您提供的路徑定義了陰影的輪廓。 使用非零纏繞規則以及當前陰影顏色,不透明度和模糊半徑填充該圖像。

Therefore, we can define any custom path and draw our shadow as we please. To create the rounded corner path, we can leverage one of the predefined initializers of the CGPath object: init(roundedRect:cornerWidth:cornerHeight:transform:). This initializer requires 4 parameters:

因此,我們可以定義任何自定義路徑並根據需要繪製陰影。 要創建圓角路徑,我們可以利用CGPath對象的預定義初始化程序之一: init(roundedRect:cornerWidth:cornerHeight:transform:) 。 此初始化程序需要4個參數:

  • roundedRect: the enclosing CGRect.

    roundedRectCGRect

  • cornerWidth: the width of the corners.

    cornerWidth :角的寬度。

  • cornerHeight: the height of the corners.

    cornerHeight :角的高度。

  • transform: an optional transformation.

    transform :可選的轉換。

cornerWidth and cornerHeight are separated to allow the creation of non-square corners.

cornerWidthcornerHeight分開以允許創建非正方形的角。

There are a couple of caveats on how to use this:

有關如何使用此方法的一些注意事項:

  • passing the view.frame would end up in something different from what you expect. Remember that the frame 「describes the view’s location and size in its superview’s coordinate system」. So yes, it will create a shadow with the proper size and corners but it won’t be behind the view as you expect. Instead, we can use the view.bounds which 「describes the view’s location and size in its own coordinate system」. And this is exactly what we need.

    通過view.frame最終會得到與您期望的不同的結果。 請記住, frame 「在其超級視圖的座標系中描述視圖的位置和大小」。 因此,是的,它將創建具有適當大小和角的陰影,但不會像您期望的那樣位於view後面。 相反,我們可以使用view. bounds 「描述視圖在其自己的座標系中的位置和大小」的 view. bounds 。 這正是我們所需要的。

Image for post
Image for post
view.frameview.frame view.bounds is used. view.bounds時。
  • If you use some properties of the view that depends on the layout, the view must be laid out before the shadow is set. Otherwise, you won’t see the shadow until the view is laid out a second time. At creation time, the view’s frame and bounds are set to 0 and the shadow will be drawn using that reference. A simple solution to this is to create a shadow after the layout:

    如果您使用取決於佈局的視圖的某些屬性, 則必須在設置陰影之前對視圖進行佈局 。 否則,直到第二次佈局視圖後,您才能看到陰影。 在創建時,視圖的framebounds設置爲0,並且將使用該引用繪製陰影。 一個簡單的解決方案是在佈局後創建陰影:

With this simple trick, the corners are now smooth and the shadow behaves well also with negative values of the radius.

通過這個簡單的技巧,拐角現在變得光滑,陰影在半徑爲負值的情況下也表現良好。

Image for post
Image for post
shadowPath. On the right, a version with a negative radius to show that the shadow is rendered correctly. shadowPath可以使角看起來不錯。 在右側,半徑爲負的版本顯示陰影已正確渲染。

You may have noticed that we left out the transform property. This is a powerful property that can give us much more freedom over the shadow of our view.

您可能已經注意到,我們省略了transform屬性。 這是一個強大的屬性,可以爲我們提供更大的視野範圍之外的自由。

CALayer.shadowPath.transform屬性 (CALayer.shadowPath.transform property)

This property allows transformations on the shadow. The theory of geometric transformations is complex and outside this article's scope. We will focus on the practical aspects of it.To use this property, we need to provide a CGAffineTransform. To create a valid transformation, we can start with the .identity, a transformation that does nothing, and build on top of it.

此屬性允許在陰影上進行變換。 幾何變換理論很複雜,不在本文討論範圍之內。 我們將重點關注它的實際方面。要使用此屬性,我們需要提供CGAffineTransform 。 要創建有效的轉換,我們可以從.identity開始,該轉換不執行任何操作,並在其之上進行構建。

For example, we can:

例如,我們可以:

  • rotate the view by 45 degrees: .rotated(by: CGFloat.pi/4)

    將視圖旋轉45度: .rotated(by: CGFloat.pi/4)

  • translate it by 100 pixels horizontally and -6 pixels vertically: .translatedBy(dx: 100, dy: -6). Notice that the translation works exactly as the shadowOffset property.

    將其水平平移100個像素,垂直.translatedBy(dx: 100, dy: -6) -6個像素: .translatedBy(dx: 100, dy: -6) 。 請注意,轉換的工作方式與shadowOffset屬性完全相同。

  • shrink it by 75%: .scaledBy(x: 0.75, y: 0.75).

    將其縮小75% .scaledBy(x: 0.75, y: 0.75)

Finally, the transform property requires an UnsafePointer. The UnsafeXXX are some weird Swift types that tell the compiler: 「don’t worry about memory management and nillability. I’ll do it for you」.This looks a little bit dangerous, and it is. However Swift is kind enough to give us some free functions to manage the unsafe API easily. In this case, we will use the withUnsafePointer function.This function takes a value of type T and transforms it into the UnsafePointer<T> we need. Finally, it gives us the unsafe reference as a parameter of a callback: memory management is handled by the withUnsafePointer function before and after the callback invocation, so we don’t have to worry about that.

最後,transform屬性需要一個UnsafePointerUnsafeXXX是一些奇怪的Swift類型,它們告訴編譯器: 「不用擔心內存管理和可移植性。 「我幫你做。」這看起來有點危險,確實如此。 但是,Swift非常友好,可以爲我們提供一些免費功能來輕鬆管理不安全的API。 在這種情況下,我們將使用withUnsafePointer函數,該函數採用類型T的值並將其轉換爲所需的UnsafePointer<T> 。 最後,它給我們提供了不安全引用作爲回調的參數:內存管理在回調調用之前和之後由withUnsafePointer函數處理,因此我們不必爲此擔心。

Here the resulting snippet:

這是結果片段:

And the final appearance of our shadow:

和我們的影子的最終外觀:

Image for post
Shadow with a complex transformation.
陰影具有複雜的轉換。

Today we explored an aspect of the UI that can give depth and a feeling of professionalism to our apps. Shadows are great, they can define views in a nice and not intrusive way and they make the apps more natural and polished. Let’s pick up your app and think about how to integrate shadows in it: you will be delighted by the result!

今天,我們探索了UI的一個方面,該方面可以使我們的應用程序具有深度和專業感。 陰影很棒,它們可以用一種不錯的方式來定義視圖,而不是侵入性,它們可以使應用程序更加自然和優美。 讓我們拿起您的應用程序並考慮如何在其中集成陰影:您將對結果感到滿意!

翻譯自: https://uxdesign.cc/calayers-shadow-explained-6f4e3f21c02a

層層遞進