如何在Flutter中实现点击继续后页面水平拖拽入场的动画效果
点击按钮触发视图水平滑入交互实现
核心实现思路
- 初始状态下,待进入的下一个视图默认放置在屏幕右侧可视区域外,当前视图占满整个可视区域
- 给「继续」按钮绑定点击事件,触发后给两个视图同步添加水平方向的平移动画
- 动画过程中,当前视图从当前位置向左平移至屏幕左侧外,下一个视图从屏幕右侧向左平移至完全占满屏幕
- 动画时长建议设为280ms-320ms,搭配easeInOut缓动曲线,手感和常见原生转场、Figma演示效果对齐
- 动画结束后及时移除移出屏幕的上一个视图节点,避免不必要的性能开销
- 如果需要实现跟手拖拽效果,额外绑定水平方向手势监听,实时根据手势位移修改视图偏移量,手势结束时按滑动距离判断是完成切换还是回弹到初始状态
常见端侧实现代码
iOS SwiftUI 实现
import SwiftUI struct SlideTransitionDemo: View { @State private var isPushed = false private let screenWidth = UIScreen.main.bounds.width var body: some View { ZStack { // 首页视图 HomeView(onContinue: { withAnimation(.easeInOut(duration: 0.3)) { isPushed = true } }) .offset(x: isPushed ? -screenWidth : 0) // 下一级视图 NextPageView() .offset(x: isPushed ? 0 : screenWidth) } } } // 首页 struct HomeView: View { let onContinue: () -> Void var body: some View { VStack { Text("首页") Button("继续", action: onContinue) .padding() } .frame(maxWidth: .infinity, maxHeight: .infinity) .background(.white) } } // 下一级页面 struct NextPageView: View { var body: some View { Text("下一个页面") .frame(maxWidth: .infinity, maxHeight: .infinity) .background(.cyan.opacity(0.2)) } }
Android Jetpack Compose 实现
import androidx.compose.foundation.background import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.material3.Button import androidx.compose.material3.Text import androidx.compose.runtime.* import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.platform.LocalConfiguration import androidx.compose.ui.unit.dp @Composable fun SlideTransitionDemo() { var isPushed by remember { mutableStateOf(false) } val screenWidth = LocalConfiguration.current.screenWidthDp.dp Box(modifier = Modifier.fillMaxSize()) { // 首页视图 Column( modifier = Modifier .fillMaxSize() .background(Color.White) .offset(x = if (isPushed) -screenWidth else 0.dp) ) { Text("首页") Button(onClick = { isPushed = true }) { Text("继续") } } // 下一级视图 Box( modifier = Modifier .fillMaxSize() .background(Color(0xFFE3F2FD)) .offset(x = if (isPushed) 0.dp else screenWidth) ) { Text("下一个页面") } } }
Web 原生实现
<!DOCTYPE html> <html lang="zh-CN"> <head> <style> * { margin: 0; padding: 0; box-sizing: border-box; } .view-wrap { position: relative; width: 100vw; height: 100vh; overflow: hidden; } .view { position: absolute; inset: 0; transition: transform 0.3s ease-in-out; } .home-view { background: #fff; padding: 20px; } .next-view { background: #f0f9ff; transform: translateX(100vw); padding: 20px; } /* 触发转场的类 */ .slide .home-view { transform: translateX(-100vw); } .slide .next-view { transform: translateX(0); } </style> </head> <body> <div class="view-wrap" id="viewWrap"> <div class="view home-view"> <p>首页</p> <button id="continueBtn">继续</button> </div> <div class="view next-view"> <p>下一个页面</p> </div> </div> <script> const viewWrap = document.getElementById('viewWrap') document.getElementById('continueBtn').addEventListener('click', () => { viewWrap.classList.add('slide') }) </script> </body> </html>
如果用各端官方的导航组件(比如iOS的NavigationStack、Android的NavController、Web的Vue Router/React Router),直接开启原生的水平滑动转场配置即可,不需要手动写offset逻辑,官方组件已经处理了内存管理、手势跟手、生命周期联动的逻辑。
内容的提问来源于stack exchange,提问作者Matheus Melo
相关产品推荐
相关产品推荐

