从另一个视图下方滑动一个SwiftUI视图

Mat*_*tie 5 swift swiftui

我正在尝试使用SwiftUI构建动画。

Start: [ A ][ B ][ D ]
End:   [ A ][ B ][    C    ][ D ]
Run Code Online (Sandbox Code Playgroud)

动画的关键元素是:

  • C应该看起来从B下方滑出(不从零宽度扩展)
  • 所有视图的宽度由子视图定义,并且未知
  • 在动画期间或之后,所有子视图的宽度均不应更改(因此,处于结束状态时,总视图宽度较大)

我现在很难用SwiftUI满足所有这些要求,但是过去能够通过自动布局实现类似的效果。

我的第一次尝试是使用HStack带有layoutPriorities 的过渡。这并没有真正接近,因为它会影响动画过程中C的宽度。

我的第二个尝试是保留HStack,但使用带有非对称移动动画的过渡。这确实很接近,但是动画中B和C的移动不会产生C恰好位于B下方的效果。

我最近的尝试是放弃对HStack两个动画视图的依赖,而使用a ZStack代替。通过此设置,我可以结合使用offset和和来使动画完美padding但是,只有将B和C的帧大小设为已知值,我才能正确解决问题。

是否有人对要求B和C的帧大小固定的情况有任何想法?

kon*_*iki 6

自从我最初回答这个问题以来,我一直在研究GeometryReader,View Preferences和Anchor Preferences。我整理了详细的解释,以作进一步阐述。您可以在以下网址阅读它:https : //swiftui-lab.com/communicating-with-the-view-tree-part-1/

在此处输入图片说明

一旦将CCCCCCCC视图几何放入textRect变量中,其余的操作就很容易了。您只需使用.offset(x :)修饰符和clipped()。

import SwiftUI

struct RectPreferenceKey: PreferenceKey {
    static var defaultValue = CGRect()

    static func reduce(value: inout CGRect, nextValue: () -> CGRect) {
        value = nextValue()
    }

    typealias Value = CGRect
}

struct ContentView : View {
    @State private var textRect = CGRect()
    @State private var slideOut = false

    var body: some View {

        return VStack {
            HStack(spacing: 0) {
                Text("AAAAAA")
                    .font(.largeTitle)
                    .background(Color.yellow)
                    .zIndex(4)


                Text("BBBB")
                    .font(.largeTitle)
                    .background(Color.red)
                    .zIndex(3)

                Text("I am a very long text")
                    .zIndex(2)
                    .font(.largeTitle)
                    .background(GeometryGetter())
                    .background(Color.green)
                    .offset(x: slideOut ? 0.0 : -textRect.width)
                    .clipped()
                    .onPreferenceChange(RectPreferenceKey.self) { self.textRect = $0 }

                Text("DDDDDDDDDDDDD").font(.largeTitle)
                    .zIndex(1)
                    .background(Color.blue)
                    .offset(x: slideOut ? 0.0 : -textRect.width)

            }.offset(x: slideOut ? 0.0 : +textRect.width / 2.0)

            Divider()
            Button(action: {
                withAnimation(.basic(duration: 1.5)) {
                    self.slideOut.toggle()
                }
            }, label: {
                Text("Animate Me")
            })
        }

    }
}

struct GeometryGetter: View {
    var body: some View {
        GeometryReader { geometry in
            return Rectangle()
                .fill(Color.clear)
                .preference(key: RectPreferenceKey.self, value:geometry.frame(in: .global))
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

  • 我更新了我的答案,因为我解决了C视图大于A和B之和的问题。事实证明我们只需要在.offset()之后添加.clipped()即可。 (2认同)

Ole*_*ann 4

很难说清楚你到底想要什么或什么不起作用。如果您展示了您想出的“错误”动画或分享了您的代码,那么帮助您会更容易。

无论如何,这是一个。我认为它有点像你指定的那样,尽管它肯定不完美:

我的解决方案的动画 GIF

观察结果:

  • 该动画基于 (A) 和 (B) 一起比 (C) 宽的假设。否则,(C) 的部分将在动画开始时出现在 A 的左侧。

  • 同样,动画依赖于视图之间没有间距的事实。否则,当 (C) 比 (B) 宽时,它会出现在 (B) 的左侧。

    通过在层次结构中放置不透明底层视图,使其位于 (A)、(B) 和 (D) 下方,但位于 (C) 上方,可以解决这两个问题。但我还没有想清楚这一点。

  • 膨胀的速度似乎HStack比 (C) 滑入的速度快一点,这就是为什么会短暂出现白色部分的原因。我没能消除这个。我尝试将相同的animation(.basic())修饰符添加到HStack、转换、withAnimation调用和VStack,但这没有帮助。

代码:

import SwiftUI

struct ContentView: View {
  @State var thirdViewIsVisible: Bool = false

  var body: some View {
    VStack(alignment: .leading, spacing: 20) {
      HStack(spacing: 0) {
        Text("Lorem ").background(Color.yellow)
          .zIndex(1)
        Text("ipsum ").background(Color.red)
          .zIndex(1)
        if thirdViewIsVisible {
          Text("dolor sit ").background(Color.green)
            .zIndex(0)
            .transition(.move(edge: .leading))
        }
        Text("amet.").background(Color.blue)
          .zIndex(1)
      }
        .border(Color.red, width: 1)
      Button(action: { withAnimation { self.thirdViewIsVisible.toggle() } }) {
        Text("Animate \(thirdViewIsVisible ? "out" : "in")")
      }
    }
      .padding()
      .border(Color.green, width: 1)
  }
}
Run Code Online (Sandbox Code Playgroud)