Skip to content

Commit 7e9305d

Browse files
committed
♻️ [kmp/ios] 优化手势返回
1. 提供了震动反馈 2. 返回图层现在更加跟手 3. 返回动画现在更加自然 4. 在返回可用的时候,返回图标会亮起,否则暗淡
1 parent 1e04595 commit 7e9305d

2 files changed

Lines changed: 66 additions & 38 deletions

File tree

next/kmp/app/iosApp/DwebBrowser/DwebBrowser/Desktop/EdgeAnimationView.swift

Lines changed: 65 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -52,23 +52,28 @@ class EdgeAnimationView: UIView {
5252
if edge == .right {
5353
NSLayoutConstraint.activate([
5454
iconView.centerYAnchor.constraint(equalTo: centerYAnchor),
55-
iconView.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 8),
55+
iconView.leftAnchor.constraint(equalTo: leftAnchor, constant: 8),
5656
])
5757
} else {
5858
NSLayoutConstraint.activate([
5959
iconView.centerYAnchor.constraint(equalTo: centerYAnchor),
60-
iconView.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -8),
60+
iconView.rightAnchor.constraint(equalTo: rightAnchor, constant: -8),
6161
])
6262
}
6363
}
6464

6565
override func layoutSubviews() {
6666
super.layoutSubviews()
67+
68+
drawShapeLayer()
69+
}
70+
71+
private func drawShapeLayer(){
72+
73+
if cachedPaths.isEmpty { return }
74+
6775
// 更新layer的路径
6876
var indexWidth = Int(self.frame.width)
69-
if indexWidth == 0 {
70-
return
71-
}
7277
if indexWidth >= cachedPaths.count{
7378
indexWidth = cachedPaths.count-1
7479
}
@@ -89,75 +94,98 @@ class EdgeAnimationView: UIView {
8994

9095
@objc func handlePanGesture(_ sender: UIPanGestureRecognizer) {
9196
let translation = sender.translation(in: self.superview)
92-
97+
98+
switch sender.state {
99+
case .began, .changed:
100+
let gesPoint = sender.location(in: sender.view)
101+
let newY = gesPoint.y - initFrame.height/2
102+
initFrame = CGRect(x: initFrame.minX, y: newY, width: initFrame.width, height: initFrame.height)
103+
default:
104+
break
105+
}
93106
switch sender.state {
94107
case .began:
95108
// 如果有正在进行的动画,停止并完成它
96109
if let animator = animator, animator.isRunning {
97-
animator.stopAnimation(true)
98-
animator.finishAnimation(at: .current)
110+
stopAnimation()
99111
}else{
100112
//这是每次新触发的手势。 否则如果有动画在进行,二次拖拽动画则建立在前一个动画位置之上
101-
let gesPoint = sender.location(in: sender.view)
102-
initFrame = CGRect(x: initFrame.minX, y: gesPoint.y - initFrame.height/2, width: initFrame.width, height: initFrame.height)
113+
self.frame = initFrame
103114
}
104-
stopAnimation()
105115
originalWidth = self.bounds.width
106-
initialOriginX = self.frame.origin.x // 记录初始的 x 坐标
107-
self.frame = initFrame
116+
initialOriginX = translation.x // self.frame.origin.x // 记录初始的 x 坐标
117+
drawShapeLayer()
108118
case .changed:
109-
// 根据手势的水平移动调整宽度
110-
let newWidth = min(originalWidth + abs(translation.x), CGFloat(fullWidth))
111-
if edge == .left{
112-
self.frame = CGRect(x: self.frame.origin.x, y: self.frame.origin.y, width: newWidth, height: self.bounds.height)
113-
}else{
114-
let newX = initialOriginX - (newWidth - originalWidth)
115-
self.frame = CGRect(x: newX, y: self.frame.origin.y, width: newWidth, height: self.bounds.height)
119+
if edge == .left {
120+
let newWidth = max(0,min(translation.x - initialOriginX + originalWidth,CGFloat(fullWidth)))
121+
self.frame = CGRect(x: initFrame.minX, y: initFrame.minY, width: newWidth, height: initFrame.height)
122+
} else {
123+
let newWidth = max(0,min(initialOriginX - translation.x + originalWidth,CGFloat(fullWidth)))
124+
let newX = initFrame.minX - newWidth
125+
self.frame = CGRect(x: newX, y: initFrame.minY, width: newWidth, height: initFrame.height)
116126
}
127+
self.iconView.alpha = ((self.frame.width / CGFloat(fullWidth)) > 0.7) ? 1.0 : 0.5
128+
117129
case .ended, .cancelled:
118-
let offsetX = self.frame.width
119-
if (offsetX / CGFloat(fullWidth)) > 0.7 {
130+
if (self.frame.width / CGFloat(fullWidth)) > 0.7 {
131+
// 执行用户动作
120132
triggerAction()
133+
// 轻微的震动
134+
let impactFeedbackGenerator = UIImpactFeedbackGenerator(style: .light)
135+
impactFeedbackGenerator.prepare()
136+
impactFeedbackGenerator.impactOccurred()
121137
}
122-
animateShapes()
123-
animateBackToOriginalWidth()
138+
startAnimation()
124139
default:
125140
break
126141
}
127142
}
128143

129-
private func animateBackToOriginalWidth() {
130-
animator = UIViewPropertyAnimator(duration: 0.6, curve: .linear) {
144+
private func animateBackToOriginalWidth(_ duration: Double) {
145+
let uianimator = UIViewPropertyAnimator(
146+
duration: duration,
147+
controlPoint1: CGPoint(x: 0, y: 0.95),
148+
controlPoint2: CGPoint(x: 0.5, y: 1)
149+
) {
131150
self.frame = self.initFrame
132151
}
152+
self.animator = uianimator
133153

134154
// 确保动画完成后才释放
135-
animator?.addCompletion { position in
136-
if position == .end && self.animator == animator {
155+
uianimator.addCompletion { position in
156+
if position == .end && self.animator == uianimator {
137157
self.animator = nil
138158
}
139159
}
140160

141-
animator?.startAnimation()
161+
uianimator.startAnimation()
142162
}
143163

144-
private func animateShapes() {
164+
private func animateShapes(_ duration: Double) {
145165
let currentWidth = Int(self.frame.width)
146166
let index = min(currentWidth, cachedPaths.count - 1)
147167
let subArray = Array(cachedPaths[0...index].reversed())
148168
let animation = CAKeyframeAnimation(keyPath: "path")
149169
animation.values = subArray
150-
animation.duration = 0.6 // 动画持续时间
151-
animation.timingFunction = CAMediaTimingFunction(name: .linear)
170+
animation.duration = duration // 动画持续时间
171+
let timingFunction = CAMediaTimingFunction(controlPoints: 0, 0.95, 0.5, 1)
172+
animation.timingFunction = timingFunction
152173
shapeLayer.add(animation, forKey: "shapeAnimation")
153174
}
154175

176+
func startAnimation(){
177+
let duration:Double = 0.6;
178+
animateShapes(duration)
179+
animateBackToOriginalWidth(duration)
180+
}
181+
155182
func stopAnimation() {
156-
if let presentationLayer = shapeLayer.presentation(),
157-
let currentPath = presentationLayer.path {
158-
// 在停止动画时,将当前路径设置为 shapeLayer 的路径,避免一闪现象
159-
shapeLayer.path = currentPath
160-
}
183+
self.animator?.stopAnimation(true)
184+
self.animator?.finishAnimation(at: .current)
185+
// if let currentPath = shapeLayer.presentation()?.path {
186+
// // 在停止动画时,将当前路径设置为 shapeLayer 的路径,避免一闪现象
187+
// shapeLayer.path = currentPath
188+
// }
161189
// 二次拖拽时,需要停止前一次的回原动画,通过 key 来移除动画,这里使用添加动画时的 key
162190
shapeLayer.removeAnimation(forKey: "shapeAnimation")
163191
}

next/kmp/buildSrc/src/main/kotlin/Multiplatform.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -285,7 +285,7 @@ fun KotlinMultiplatformExtension.kmpCommonTarget(
285285
dsl.provides(sourceSets.commonMain, sourceSets.commonTest)
286286
sourceSets.all {
287287
kotlin {
288-
languageSettings.enableLanguageFeature("ExplicitBackingFields")
288+
// languageSettings.enableLanguageFeature("ExplicitBackingFields")
289289
}
290290
}
291291

0 commit comments

Comments
 (0)