在SwiftUI:View clipped
中闺阱,我們已經(jīng)探索了所有可以將剪輯蒙版應(yīng)用到視圖的方法诡壁。雖然剪輯功能強(qiáng)大,但它有兩個顯著的限制:
- 它要求
shape
作為mask
- 內(nèi)容要么被遮罩阅束,要么被修剪掉;沒有灰色地帶
讓我們探索一下超越剪輯的SwiftUI遮罩mask
践美。
Mask
SwiftUI提供的最后一個蒙版視圖修飾符是mask(alignment:_:)
:
extension View {
@inlinable public func mask<Mask: View>(
alignment: Alignment = .center,
@ViewBuilder _ mask: () -> Mask
) -> some View
}
除了命名之外茴丰,這個修飾符聲明和其他一些我們可能非常熟悉的視圖修飾符是一樣的困介,overlay(alignment:_:)
和background(alignment:_:)
:
extension View {
@inlinable public func overlay<V: View>(
alignment: Alignment = .center,
@ViewBuilder content: () -> V
) -> some View
@inlinable public func background<V: View>(
alignment: Alignment = .center,
@ViewBuilder content: () -> V
) -> some View
}
這不是巧合.mask(alignment:_:)
定位它的蒙版就像一個overlay
覆蓋或background
背景修改器一樣:
- 修飾符將它應(yīng)用到的視圖的自然大小建議給它的mask
- 一旦mask大小已知,它將根據(jù)指定的
alignment
對齊方式放置在視圖上
Mask alignment
當(dāng)蒙版和原始視圖有不同的尺寸時铃诬,對齊參數(shù)特別有用祭陷。在下面的例子中,蒙版是它應(yīng)用到的視圖的30%大小:
struct FSView: View {
private let alignments: [Alignment] = [
.center, .leading, .trailing, .top, .bottom, .topLeading, .topTrailing, .bottomLeading, .bottomTrailing
]
@State var alignment: Alignment = .center
var body: some View {
VStack {
Color.yellow
.frame(width: 200, height: 200)
.mask(alignment: alignment) {
Rectangle()
.frame(width: 60, height: 60) // ???? 60 x 60 is smaller than 200x200
}
.border(.red)
Button("Random alignment") {
withAnimation {
alignment = alignments.filter { $0 != alignment } .randomElement()!
}
}
}
}
}
紅色邊框顯示了原始視圖的邊界趣席,以提供視覺幫助:否則兵志,我們只能看到一個小矩形。
視圖作為蒙版
clipping剪輯修飾符的真正力量在于有機(jī)會使用任何View
視圖作為遮罩吩坝。比如說Text
呢?
Color.yellow
.frame(width: 200, height: 200)
.mask {
Text("MASK")
.fontWeight(.black)
.font(.system(size: 60))
}
.border(Color.red)
不像shape
形狀毒姨,視圖不會停留在它們所應(yīng)用的視圖的自然大小內(nèi)。因此钉寝,遮罩會導(dǎo)致內(nèi)容溢出弧呐。
在下面的例子中:
- 視圖內(nèi)容擴(kuò)展到300x300的矩形
- 視圖大小設(shè)置200x200
- 應(yīng)用的遮罩超出了視圖邊界,允許內(nèi)容溢出
Color.yellow
.frame(width: 300, height: 300)
.frame(width: 200, height: 200)
.mask {
Text("MASK")
.fontWeight(.black)
.font(.system(size: 80))
.fixedSize() // ???? 忽略建議的200x200的大小
}
.border(Color.red)
Opacity
mask(alignment:_:)
使用蒙版不透明度來確定從原始視圖中顯示的內(nèi)容嵌纲,例如:
Color.yellow
.frame(width: 200, height: 200)
.mask {
LinearGradient(colors: [.clear, .black, .clear], startPoint: .leading, endPoint: .trailing)
}
.border(Color.red)
在這里俘枫,我們使用帶有三個顏色的線性梯度。中間的漸變顏色并不重要逮走。它的顏色不透明度:我們可以用.white
鸠蚪,.red
等等來替換。結(jié)果是一樣的。
Blending混合
當(dāng)我們將視圖堆疊在其他視圖之上時茅信,底下的視圖被上面的視圖所隱藏盾舌。我們可以通過不透明度或使用不同大小的視圖來影響這一點(diǎn),但這個想法仍然有缺陷:底部的視圖總是會有一些部分被上面的視圖隱藏(或半隱藏)蘸鲸。
一種打破這種標(biāo)準(zhǔn)行為的強(qiáng)大技術(shù)是混合blending
妖谴,它允許我們使用不同的視圖屬性(色值、不透明度酌摇、亮度等等)來組成最終的堆棧外觀膝舅。
混合模式有很多種,目前窑多,讓我們更多地關(guān)注目標(biāo)輸出混合模式仍稀,即BlendMode.destinationOut
。
在混合模式下埂息,source是頂部視圖技潘,而destination是底部視圖。
輸出后千康,最終視圖是底部視圖(destination)的位崭篡,它不與頂部視圖(source)重疊。
這里有一個例子吧秕,目標(biāo)destination是一個Rectangle
琉闪,源source是一個Circle
,兩者的大小相同:
ZStack {
Rectangle() // destination
Circle() // source
.blendMode(.destinationOut)
}
.compositingGroup()
.border(.red)
如果我們現(xiàn)在反轉(zhuǎn)兩個視圖砸彬,ZStack
將是空白不會繪制任何東西颠毙,因?yàn)?code>Rectangle(source)完全重疊在Circle
(目標(biāo)destination)上
ZStack {
Circle() // destination
Rectangle() // source
.blendMode(.destinationOut)
}
.compositingGroup()
.border(.red)
與mask(alignment:_:)
視圖修改器類似,blendMode(.destinationout)
使用每個視圖不透明度來決定最終輸出砂碉。下面是和之前一樣的例子蛀蜜,我們用漸淡漸變替換Rectangle
矩形:
ZStack {
LinearGradient(
colors: [.clear, .black],
startPoint: .leading, endPoint: .trailing
) // destination
Circle() // source
.blendMode(.destinationOut)
}
.compositingGroup()
.border(.red)
ZStack {
Circle() // destination
LinearGradient(
colors: [.clear, .black],
startPoint: .leading, endPoint: .trailing
) // source
.blendMode(.destinationOut)
}
.compositingGroup()
.border(.red)
我們可以使用目標(biāo)輸出混合技術(shù)來創(chuàng)建反向蒙版。
構(gòu)建反向蒙版修飾器
首先增蹭,我們想保持與mask(alignment:_:)
修飾符相同的API滴某,這是它看起來的樣子:
extension View {
@inlinable
public func reverseMask<Mask: View>(
alignment: Alignment = .center,
@ViewBuilder _ mask: () -> Mask
) -> some View
}
接下來,我們知道mask(alignment:_:)
的工作原理是只顯示蒙版不透明度的原始視圖滋迈,現(xiàn)在我們想做相反的事霎奢。讓我們先用一個普通的蒙版:
extension View {
@inlinable
public func reverseMask<Mask: View>(
alignment: Alignment = .center,
@ViewBuilder _ mask: () -> Mask
) -> some View {
self.mask {
Rectangle()
}
}
}
通過在蒙版修改器中傳入Rectangle()
,我們獲得了與.clipped()
相同的效果:我們不再允許內(nèi)容溢出饼灿,但原始內(nèi)容仍然在視圖范圍內(nèi)可見幕侠。
接下來,我們想要應(yīng)用.destinationOut
混合模式碍彭,我們的蒙版作為源晤硕,clipping rectangle剪切矩形作為目標(biāo):
// ?? this is not the final code.
extension View {
@inlinable
public func reverseMask<Mask: View>(
alignment: Alignment = .center,
@ViewBuilder _ mask: () -> Mask
) -> some View {
self.mask {
ZStack {
Rectangle() // destination
mask() // source
.blendMode(.destinationOut)
}
}
}
}
多虧了ZStack
悼潭,我們應(yīng)用了相同的效果在上面的混合章節(jié)中覆蓋,然后使用結(jié)果作為常規(guī)蒙版的輸入舞箍,獲得一個反向蒙版舰褪。
最后,我們想要遵守alignment
參數(shù)疏橄,最好的方法抵知,就是用一個比他們應(yīng)用到視圖的尺寸更大的視圖處理蒙版,可以運(yùn)用overlay
疊加到我們的Rectangle
:
extension View {
@inlinable
public func reverseMask<Mask: View>(
alignment: Alignment = .center,
@ViewBuilder _ mask: () -> Mask
) -> some View {
self.mask {
Rectangle()
.overlay(alignment: alignment) {
mask()
.blendMode(.destinationOut)
}
}
}
}
把它應(yīng)用到我們之前的代碼中:
HStack {
Color.yellow
.frame(width: 200, height: 200)
.mask {
Star()
}
.border(.red)
Color.yellow
.frame(width: 200, height: 200)
.reverseMask {
Star()
}
.border(.red)
}
HStack {
Color.yellow
.frame(width: 200, height: 200)
.mask {
Text("MASK")
.font(.system(size: 60).weight(.black))
}
.border(.red)
Color.yellow
.frame(width: 200, height: 200)
.reverseMask {
Text("MASK")
.font(.system(size: 60).weight(.black))
}
.border(.red)
}
HStack {
Color.yellow
.frame(width: 200, height: 200)
.mask {
LinearGradient(
colors: [.clear, .black],
startPoint: .leading, endPoint: .trailing
)
}
.border(.red)
Color.yellow
.frame(width: 200, height: 200)
.reverseMask {
LinearGradient(
colors: [.clear, .black],
startPoint: .leading, endPoint: .trailing
)
}
.border(.red)
}