import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:tiktok_clone/constants/gaps.dart';
import 'package:tiktok_clone/constants/sizes.dart';
import 'package:tiktok_clone/features/main_navigation/main_navigation_screen.dart';
enum Direction { right, left }
enum Page { first, second }
class TutorialScreen extends StatefulWidget {
const TutorialScreen({super.key});
State<TutorialScreen> createState() => _TutorialScreenState();
}
class _TutorialScreenState extends State<TutorialScreen> {
Direction _direction = Direction.right;
Page _showingPage = Page.first;
void _onPanUpdate(DragUpdateDetails details) {
setState(() {
_direction = details.delta.dx > 0 ? Direction.right : Direction.left;
});
}
void _onPanEnd(DragEndDetails detail) {
setState(() {
_showingPage = _direction == Direction.left ? Page.second : Page.first;
});
}
void _onEnterAppTap() {
Navigator.of(context).pushAndRemoveUntil(
MaterialPageRoute(
builder: (context) => const MainNavigationScreen(),
),
(route) => false,
);
}
Widget build(BuildContext context) {
return GestureDetector(
onPanUpdate: _onPanUpdate,
onPanEnd: _onPanEnd,
child: Scaffold(
body: Padding(
padding: const EdgeInsets.symmetric(horizontal: Sizes.size24),
child: SafeArea(
child: AnimatedCrossFade(
firstChild: const Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Gaps.v80,
Text(
"Watch cool videos!",
style: TextStyle(
fontSize: Sizes.size40,
fontWeight: FontWeight.bold,
),
),
Gaps.v16,
Text(
"Videos are personalized for you based on what you watch, like, and share.",
style: TextStyle(
fontSize: Sizes.size20,
),
)
],
),
secondChild: const Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Gaps.v80,
Text(
"Follow the rules",
style: TextStyle(
fontSize: Sizes.size40,
fontWeight: FontWeight.bold,
),
),
Gaps.v16,
Text(
"Videos are personalized for you based on what you watch, like, and share.",
style: TextStyle(
fontSize: Sizes.size20,
),
)
],
),
crossFadeState: _showingPage == Page.first
? CrossFadeState.showFirst
: CrossFadeState.showSecond,
duration: const Duration(milliseconds: 300),
),
),
),
bottomNavigationBar: BottomAppBar(
child: Padding(
padding: const EdgeInsets.symmetric(
vertical: Sizes.size24,
horizontal: Sizes.size24,
),
child: AnimatedOpacity(
duration: const Duration(milliseconds: 300),
opacity: _showingPage == Page.first ? 0 : 1,
child: CupertinoButton(
onPressed: _onEnterAppTap,
color: Theme.of(context).primaryColor,
child: const Text('Enter the app!'),
),
),
),
),
),
);
}
}
이 코드는 튜토리얼 화면을 만드는 Flutter 앱의 일부입니다. 사용자는 화면을 스와이프해서 다른 튜토리얼 페이지를 볼 수 있으며, 두 번째 페이지에서는 "앱 입장하기" 버튼을 통해 앱의 메인 부분으로 이동할 수 있습니다.
아래에 코드의 주요 부분을 설명하겠습니다.
enum Direction { right, left } / enum Page { first, second }:
두 개의 열거형(enum)을 선언하여 방향과 페이지를 나타냅니다.
Direction _direction = Direction.right; / Page _showingPage = Page.first;:
현재의 방향과 표시되고 있는 페이지를 저장하는 변수입니다.
_onPanUpdate(DragUpdateDetails details) / _onPanEnd(DragEndDetails detail):
사용자가 화면을 드래그했을 때와 드래그가 끝났을 때 발생하는 이벤트를 처리하는 메서드입니다.
_onEnterAppTap():
"앱 입장하기" 버튼이 눌렸을 때의 이벤트를 처리하는 메서드입니다.
AnimatedCrossFade:
현재 보여지고 있는 페이지(_showingPage
)에 따라 서로 다른 컨텐츠를 부드럽게 전환해주는 위젯입니다.
BottomAppBar:
하단에 버튼을 배치합니다. _showingPage
가 Page.first
일 경우 투명도를 0으로, Page.second
일 경우 투명도를 1로 설정하여 버튼을 보이거나 숨깁니다.
AnimatedOpacity:
_showingPage
에 따라 버튼의 투명도를 애니메이션으로 변경합니다.
CupertinoButton:
"앱 입장하기" 버튼입니다. 이 버튼을 누르면 _onEnterAppTap
메서드가 호출되어 앱의 메인 화면으로 이동합니다.
이러한 방식으로, 이 코드는 튜토리얼 화면에서 사용자의 인터랙션을 처리하고 다음 단계로 넘어갈 수 있는 방법을 제공합니다.
details.delta.dx > 0
이라는 코드 조건은 사용자가 터치나 드래그 동작을 수행할 때 x축의 움직임이 양의 방향(즉, 오른쪽으로) 있었는지를 판단합니다.
여기서 details
는 DragUpdateDetails
객체이고, delta
는 마지막 드래그 위치에서 현재 드래그 위치까지의 변화량(Offset
)을 나타냅니다. dx
는 그 Offset
내에서 x축 방향의 변화량을 의미합니다.
dx > 0
: 사용자가 오른쪽으로 드래그했습니다.dx < 0
: 사용자가 왼쪽으로 드래그했습니다.dx = 0
: 사용자가 수평 방향으로는 움직이지 않았습니다.이러한 정보는 특히 슬라이딩 메뉴, 이미지 캐러셀, 또는 카드 뒤집기 등의 UI 인터랙션을 구현할 때 유용하게 사용됩니다.