You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.28 21:36:26