import 'package:flutter/material.dart';
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
import 'package:tiktok_clone/constants/gaps.dart';
import 'package:tiktok_clone/constants/sizes.dart';
import 'package:tiktok_clone/features/users/widgets/persistent_tab_bar.dart';
class UserProfileScreen extends StatefulWidget {
const UserProfileScreen({super.key});
State<UserProfileScreen> createState() => _UserProfileScreenState();
}
class _UserProfileScreenState extends State<UserProfileScreen> {
Widget build(BuildContext context) {
return SafeArea(
child: DefaultTabController(
length: 2,
child: NestedScrollView(
headerSliverBuilder: (context, innerBoxIsScrolled) {
return [
SliverAppBar(
title: const Text('니꼬'),
actions: [
IconButton(
onPressed: () {},
icon: const FaIcon(
FontAwesomeIcons.gear,
size: Sizes.size20,
),
)
],
),
SliverToBoxAdapter(
child: Column(
children: [
const CircleAvatar(
radius: 50,
foregroundImage: NetworkImage(
"https://avatars.githubusercontent.com/u/3612017"),
child: Text("니꼬"),
),
Gaps.v20,
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
const Text(
"@니꼬",
style: TextStyle(
fontWeight: FontWeight.w600,
fontSize: Sizes.size18,
),
),
Gaps.h5,
FaIcon(
FontAwesomeIcons.solidCircleCheck,
size: Sizes.size16,
color: Colors.blue.shade500,
)
],
),
Gaps.v24,
SizedBox(
height: Sizes.size48,
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Column(
children: [
const Text(
"97",
style: TextStyle(
fontWeight: FontWeight.bold,
fontSize: Sizes.size18,
),
),
Gaps.v1,
Text("Following",
style: TextStyle(
color: Colors.grey.shade500,
))
],
),
VerticalDivider(
width: Sizes.size32,
thickness: Sizes.size1,
color: Colors.grey.shade400,
indent: Sizes.size14,
endIndent: Sizes.size14,
),
Column(
children: [
const Text(
"10M",
style: TextStyle(
fontWeight: FontWeight.bold,
fontSize: Sizes.size18,
),
),
Gaps.v1,
Text(
"Followers",
style: TextStyle(
color: Colors.grey.shade500,
),
)
],
),
VerticalDivider(
width: Sizes.size32,
thickness: Sizes.size1,
color: Colors.grey.shade400,
indent: Sizes.size14,
endIndent: Sizes.size14,
),
Column(
children: [
const Text(
"194.3M",
style: TextStyle(
fontWeight: FontWeight.bold,
fontSize: Sizes.size18,
),
),
Gaps.v1,
Text(
"Likes",
style: TextStyle(
color: Colors.grey.shade500,
),
)
],
)
],
),
),
Gaps.v14,
FractionallySizedBox(
widthFactor: 0.33,
child: Container(
padding: const EdgeInsets.symmetric(
vertical: Sizes.size12,
),
decoration: BoxDecoration(
color: Theme.of(context).primaryColor,
borderRadius: const BorderRadius.all(
Radius.circular(Sizes.size4),
),
),
child: const Text(
'Follow',
style: TextStyle(
color: Colors.white,
fontWeight: FontWeight.w600,
),
textAlign: TextAlign.center,
),
),
),
Gaps.v14,
const Padding(
padding: EdgeInsets.symmetric(
horizontal: Sizes.size32,
),
child: Text(
"All highlights and where to watch live matches on FIFA+ I wonder how it would loook",
textAlign: TextAlign.center,
),
),
Gaps.v14,
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: const [
FaIcon(
FontAwesomeIcons.link,
size: Sizes.size12,
),
Gaps.h4,
Text(
"https://nomadcoders.co",
style: TextStyle(
fontWeight: FontWeight.w600,
),
),
],
),
Gaps.v20,
],
),
),
SliverPersistentHeader(
delegate: PersistentTabBar(),
pinned: true,
),
];
},
body: TabBarView(
children: [
GridView.builder(
itemCount: 20,
padding: EdgeInsets.zero,
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 3,
crossAxisSpacing: Sizes.size2,
mainAxisSpacing: Sizes.size2,
childAspectRatio: 9 / 14,
),
itemBuilder: (context, index) => Column(
children: [
AspectRatio(
aspectRatio: 9 / 14,
child: FadeInImage.assetNetwork(
fit: BoxFit.cover,
placeholder: "assets/images/placeholder.jpg",
image:
"https://images.unsplash.com/photo-1673844969019-c99b0c933e90?ixlib=rb-4.0.3&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=1480&q=80",
),
),
],
),
),
const Center(
child: Text('Page two'),
),
],
),
),
),
);
}
}
NestedScrollView
는 헤더와 본문 모두에 스크롤 동작이 있는 복잡한 스크롤 뷰를 만들 때 유용한 위젯입니다. UserProfileScreen
에서는 사용자 프로필의 다양한 정보와 함께 그리드 뷰를 보여주기 위해 NestedScrollView
를 사용하고 있습니다.
headerSliverBuilder: 이 함수는 Sliver
위젯의 리스트를 반환하며, SliverAppBar
, SliverToBoxAdapter
, SliverPersistentHeader
등의 위젯들을 반환할 수 있습니다.
SliverAppBar
: 이 앱바는 사용자가 스크롤할 때 축소되고 확장됩니다. 여기서는 사용자 이름을 제목으로 표시하며, 우측에는 설정 아이콘을 가진 액션 버튼이 있습니다.
SliverToBoxAdapter
: 일반 위젯을 슬리버 형태로 변환할 수 있게 해줍니다. 여기서는 사용자의 프로필 사진, 사용자 이름, 팔로잉/팔로워/좋아요 숫자, 프로필 설명 등의 정보가 포함되어 있습니다.
SliverPersistentHeader
: 스크롤 시 상단에 고정되는 헤더를 구현할 수 있습니다. 여기서는 PersistentTabBar
라는 커스텀 위젯을 사용하여 탭 바를 표시합니다.
body: 여기에는 TabBarView
가 위치해 있어, 사용자가 탭을 변경할 때 다양한 뷰를 보여줄 수 있습니다.
첫 번째 탭에는 GridView.builder
를 사용하여 그리드 형식의 목록을 표시합니다. 각 그리드 항목은 Unsplash에서 가져온 이미지를 보여줍니다.
두 번째 탭에는 텍스트 위젯만 포함되어 있어, "Page two"라는 텍스트를 표시합니다.
즉, NestedScrollView
를 사용하여 사용자 프로필의 상세 정보와 함께 스크롤 가능한 이미지 그리드를 보여주는 UI를 구현하고 있습니다. 이를 통해 사용자는 프로필 정보와 함께 게시한 이미지 목록을 확인할 수 있습니다.
NestedScrollView
는 Flutter에서 복잡한 스크롤 뷰를 구현할 수 있게 해주는 위젯입니다. 그것은 특히 헤더와 본문의 스크롤 동작을 함께 조절해야 할 때 유용합니다. 이 위젯의 주요 기능과 특징에 대해 자세히 알아보겠습니다.
headerSliverBuilder:
SliverAppBar
가 여기에 포함됩니다. SliverAppBar
는 사용자가 스크롤할 때 축소되거나 확장될 수 있습니다.body:
NestedScrollView
의 본문에 해당하며, 일반적으로 ListView
, GridView
, CustomScrollView
등의 스크롤 가능한 위젯이 위치합니다.headerSliverBuilder
에서 정의된 헤더 아래에 위치하며, 헤더와 함께 스크롤됩니다.NestedScrollView
는 두 개의 스크롤 뷰를 "중첩"하는 방식으로 동작합니다. 첫 번째 스크롤 뷰는 헤더를 처리하며, 두 번째 스크롤 뷰는 본문을 처리합니다.NestedScrollView
는 중첩된 스크롤 동작을 처리하기 위해 특별한 논리를 포함하고 있습니다. 그렇기 때문에 ListView.builder
나 GridView.builder
같은 빌더 패턴을 사용할 때는 SliverChildBuilderDelegate
와 함께 CustomScrollView
를 본문에 사용하는 것이 좋습니다.
NestedScrollView
내에서 스크롤 뷰의 controller
에 직접 접근하려면, NestedScrollView.controller
를 사용해야 합니다.
SliverOverlapAbsorber
및 SliverOverlapInjector
는 중첩된 스크롤에서 영역 겹침 문제를 해결하는 데 도움이 될 수 있습니다.
NestedScrollView
는 복잡한 스크롤 동작을 가진 UI를 구현할 때 매우 유용합니다. 그러나 그것의 동작 원리와 특징을 잘 이해하고 사용해야 원하는 대로 동작하는 UI를 만들 수 있습니다.