如何改变不同比例的拖动量?

问题描述 投票:0回答:1

我正在尝试完成一个自定义滑块,这不是一个普通的滑块。它涉及通过在屏幕上拖动圆圈来按内部和外部。圆圈的拖动量缩小了 10 倍。我已经完成了圆圈拖动部分,并且自定义滑块在拖动圆圈时按照我的预期工作。

但是,当我尝试通过拖动自定义滑块本身来反转该过程时,问题就出现了。除了对 CustomSliderView 中的 .onChange(of: PercentageDX) 部分进行逆向工程之外,我已经完成了所有部分。我已经为此苦苦挣扎了几个小时,尝试了大约 50 种不同的试错尝试,但我无法理解它。我需要帮助来完成 CustomSliderView 中旋钮手势缺失的部分。

以下是有关代码工作原理的一些背景信息:我在应用程序中有一个屏幕,可用作测量工具来找出圆圈阻力的百分比。然后我将此百分比发送到 CustomSliderView 并将其缩小 10 倍。此操作会将滑块上的位移量减少 10 倍,使其能够与圆的拖动一起正常工作。您可以尝试拖动圆圈,看看当圆圈向左或向右拖动或位移为零时,滑块处于 100%。当圆圈开始向左或向右移动时,滑块开始向相反方向按压。

现在,我想拖动滑块来反转这个按压过程,使其在圆圈返回到原始位置时逐渐压回 100%。

enter image description here enter image description here enter image description here

import SwiftUI

struct ContentView: View {
    
    @State private var lastCircleOffset: CGSize = CGSize()
    @State private var currentCircleOffset: CGSize = CGSize()
    @State private var circleIsDragging: Bool = Bool()
    @State private var dxKnobIsDragging: Bool = Bool()
    @State private var percentageDX: CGFloat? = nil
    
    private let maxScreenVisibilitySize: CGSize = CGSize(width: 400.0, height: 300.0)
    
    var body: some View {
        
        ZStack {
            
            ZStack {
                
                Color.clear
                
                Color.white
                    .frame(width: maxScreenVisibilitySize.width, height: maxScreenVisibilitySize.height)
                
                Circle()
                    .fill(.red)
                    .frame(width: 50.0, height: 50.0)
                    .offset(x: currentCircleOffset.width, y: currentCircleOffset.height)
                    .gesture(circleGesture)
                
                if let unwrappedPercentageDX: CGFloat = percentageDX {
                    Text("percentageDX: \(String(format: "%.2f", unwrappedPercentageDX*100.0))%").bold().monospaced().foregroundStyle(.black).offset(y: 50)
                }
                
                
            }
            .clipped()
            .onChange(of: currentCircleOffset) { (oldValue, newValue) in
                
                if (!dxKnobIsDragging) && (oldValue != newValue) {
                    
                    percentageDX = (newValue.width/(maxScreenVisibilitySize.width/2.0))
                    
                }
                
            }
            .onChange(of: percentageDX) { (oldValue, newValue) in
                
                if (!circleIsDragging) && (dxKnobIsDragging) {
                    
                    if let unwrappedOldValue: CGFloat = oldValue, let unwrappedNewValue: CGFloat = newValue, (unwrappedOldValue != unwrappedNewValue) {
                        
                        // I may made mistake here!
                        
                        currentCircleOffset.width -= (unwrappedNewValue*maxScreenVisibilitySize.width)/2.0
                        
                    }
                    
                }
                
            }
            
            VStack {
                
                Spacer()
                
                CustomSliderView(percentageDX: $percentageDX, dxKnobIsDragging: $dxKnobIsDragging)
                
            }
            
        }
        .padding()
        
    }
    
    private var circleGesture: some Gesture {
        
        return DragGesture(minimumDistance: CGFloat.zero, coordinateSpace: .local)
            .onChanged() { value in
                
                circleIsDragging = true
                
                currentCircleOffset = CGSize(width: value.translation.width + lastCircleOffset.width,
                                             height: value.translation.height + lastCircleOffset.height)
                
            }
            .onEnded() { value in
                lastCircleOffset = currentCircleOffset
                circleIsDragging = false
            }
        
    }
    
}

struct CGSizePreferenceKey: PreferenceKey {
    static var defaultValue: CGSize { get { return CGSize() } }
    static func reduce(value: inout CGSize, nextValue: () -> CGSize) { value = nextValue() }
}


struct CustomSliderView: View {
    
    @Binding var percentageDX: CGFloat?
    @Binding var dxKnobIsDragging: Bool
    
    @State private var lastKnobOffset: CGFloat = CGFloat()
    @State private var currentKnobOffset: CGFloat = CGFloat()
    
    @State private var currentOffsetPercentage: CGFloat = CGFloat()
    
    @State private var maxWidthSize: CGFloat = CGFloat()
    @State private var knobWidth: CGFloat = CGFloat()
    private let knobHeight: CGFloat = 25.0
    
    
    var body: some View {
        
        GeometryReader { proxy in
            
            ZStack {
                
                Color.gray
                
                Color.white.opacity(knobWidth.isZero ? .zero : 0.85)
                    .frame(width: knobWidth)
                    .shadow(radius: 10)
                    .offset(x: currentKnobOffset)
                    .gesture(knobGesture)
                
            }
            .preference(key: CGSizePreferenceKey.self, value: proxy.size)
            
        }
        .frame(height: knobHeight)
        .onPreferenceChange(CGSizePreferenceKey.self) { newValue in
            
            maxWidthSize = newValue.width
            
            if percentageDX == nil {
                knobWidth = newValue.width
            }
            
            
        }
        .onChange(of: percentageDX) { (oldValue, newValue) in
            
            // This part is okay; it works as expected.
            
            if (!dxKnobIsDragging) {
                
                if let unwrappedOldValue: CGFloat = oldValue, let unwrappedNewValue: CGFloat = newValue, (unwrappedOldValue != unwrappedNewValue) {
                    
                    if let unwrappedNewValue: CGFloat = newValue {
                        
                        let newPercentageDX: CGFloat = abs(unwrappedNewValue)
                        
                        let knobScale: CGFloat = 10.0
                        
                        knobWidth = (maxWidthSize - (newPercentageDX*maxWidthSize)/knobScale)
                        
                        
                        if (unwrappedNewValue >= .zero) {
                            currentKnobOffset = -(newPercentageDX*maxWidthSize)/(2.0*knobScale)
                            lastKnobOffset = currentKnobOffset
                        }
                        else {
                            currentKnobOffset = (newPercentageDX*maxWidthSize)/(2.0*knobScale)
                            lastKnobOffset = currentKnobOffset
                        }
                        
                    }
                    
                }
                
            }
            
        }
        
    }
    
    private var knobGesture: some Gesture {
        
        return DragGesture(minimumDistance: CGFloat.zero, coordinateSpace: .local)
            .onChanged() { value in
                
                dxKnobIsDragging = true
                
                // I need to finish this part with the help of reverse coding on .onChange(of: percentageDX)
                
                
            }
            .onEnded() { value in
                
                lastKnobOffset = currentKnobOffset
                dxKnobIsDragging = false
                
            }
        
    }
}
swift swiftui
1个回答
0
投票

感谢您在评论中提供额外信息和答案。

据我从您的回答中了解到,滑块不需要改变宽度。它可以简单地占据整个可用宽度,然后按照百分比值进行偏移。

所以,我认为可以用更简单的方式来解决,用更少的状态变量和更少的逻辑。

PreferenceKey
也不需要。

  • 当前百分比存储在状态变量中,就像您已经拥有它一样。
  • 一个
    GestureState
    变量用于记录基于当前拖动运动的百分比变化。
  • 同样的两个变量用于圆和滑块。
  • 圆的偏移量是根据当前百分比计算的。
  • 同样,滑块的偏移量是根据当前百分比计算的。

使用

GestureState
的优点是,当手势结束时,它会自动重置回初始值。

这是您的示例的更新版本。我希望它能按照您想要的方式工作。

struct ContentView: View {
    @State private var percentageDX: CGFloat = 0
    @GestureState private var dragPercent: CGFloat = 0
    private let maxScreenVisibilitySize: CGSize = CGSize(width: 400.0, height: 200.0)

    var body: some View {
        VStack(spacing: 40) {
            Spacer()

            ZStack {
                Color.white
                Circle()
                    .fill(.red)
                    .frame(width: 50.0, height: 50.0)
                    .offset(x: xOffsetCircle)
                    .gesture(circleGesture)
            }
            .frame(maxWidth: maxScreenVisibilitySize.width, maxHeight: maxScreenVisibilitySize.height)

            Text(String(format: "%.0f%%", (percentageDX + dragPercent) * 100))
                .font(.title2)

            CustomSliderView(percentageDX: $percentageDX, dragPercent: $dragPercent)

            Spacer()
        }
    }

    private var xOffsetCircle: CGFloat {
        (percentageDX + dragPercent) * (maxScreenVisibilitySize.width / 2.0)
    }

    private var circleGesture: some Gesture {
        DragGesture(minimumDistance: CGFloat.zero, coordinateSpace: .local)
            .updating($dragPercent) { value, state, trans in
                state = value.translation.width / (maxScreenVisibilitySize.width / 2.0)
            }
            .onEnded { value in
                let dPercent = value.translation.width / (maxScreenVisibilitySize.width / 2.0)
                percentageDX += dPercent
            }
    }
}

struct CustomSliderView: View {
    @Binding var percentageDX: CGFloat
    var dragPercent: GestureState<CGFloat>
    private let scalingFactor: CGFloat = 10.0
    private let sliderHeight: CGFloat = 25.0

    var body: some View {
        GeometryReader { proxy in
            ZStack {
                Color.gray
                Color.white
                    .shadow(radius: 10)
                    .offset(x: xOffsetSlider(maxWidthSize: proxy.size.width))
                    .gesture(
                        DragGesture(minimumDistance: CGFloat.zero, coordinateSpace: .local)
                            .updating(dragPercent) { value, state, trans in
                                state = -(value.translation.width / proxy.size.width) * 10
                            }
                            .onEnded { value in
                                let dPercent = -(value.translation.width / proxy.size.width) * 10
                                percentageDX += dPercent
                            }
                    )
            }
        }
        .frame(height: sliderHeight)
    }

    private func xOffsetSlider(maxWidthSize: CGFloat) -> CGFloat {
        -((percentageDX + dragPercent.wrappedValue) / scalingFactor ) * maxWidthSize
    }
}

Animation

© www.soinside.com 2019 - 2024. All rights reserved.