import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
import 'package:tiktok_clone/constants/breakpoints.dart';
import 'package:tiktok_clone/constants/gaps.dart';
import 'package:tiktok_clone/constants/sizes.dart';
final tabs = [
"Top",
"Users",
"Videos",
"Sounds",
"LIVE",
"Shopping",
"Brands",
];
class DiscoverScreen extends StatefulWidget {
const DiscoverScreen({super.key});
State<DiscoverScreen> createState() => _DiscoverScreenState();
}
class _DiscoverScreenState extends State<DiscoverScreen> {
final TextEditingController _textEditingController =
TextEditingController(text: "Initial Text");
void _onSearchChanged(String value) {
print("Searching form $value");
}
void _onSearchSubmitted(String value) {
print("Submitted $value");
}
void dispose() {
_textEditingController.dispose();
super.dispose();
}
Widget build(BuildContext context) {
final width = MediaQuery.of(context).size.width;
return DefaultTabController(
length: tabs.length,
child: Scaffold(
resizeToAvoidBottomInset: false,
appBar: AppBar(
elevation: 1,
title: CupertinoSearchTextField(
controller: _textEditingController,
onChanged: _onSearchChanged,
onSubmitted: _onSearchSubmitted,
),
bottom: TabBar(
splashFactory: NoSplash.splashFactory,
padding: const EdgeInsets.symmetric(
horizontal: Sizes.size16,
),
isScrollable: true,
labelStyle: const TextStyle(
fontWeight: FontWeight.w600,
fontSize: Sizes.size16,
),
indicatorColor: Colors.black,
labelColor: Colors.black,
unselectedLabelColor: Colors.grey.shade500,
tabs: [
for (var tab in tabs)
Tab(
text: tab,
),
],
),
),
body: TabBarView(
children: [
GridView.builder(
keyboardDismissBehavior: ScrollViewKeyboardDismissBehavior.onDrag,
itemCount: 20,
padding: const EdgeInsets.all(
Sizes.size10,
),
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: width > Breakpoints.lg ? 5 : 2,
crossAxisSpacing: Sizes.size10,
mainAxisSpacing: Sizes.size10,
childAspectRatio: 9 / 20,
),
itemBuilder: (context, index) => LayoutBuilder(
builder: (context, constraints) => Column(
children: [
Container(
clipBehavior: Clip.hardEdge,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(Sizes.size4),
),
child: AspectRatio(
aspectRatio: 9 / 16,
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",
),
),
),
Gaps.v10,
Text(
"${constraints.maxWidth} This is a very long caption for my tiktok that im upload just now currently.",
overflow: TextOverflow.ellipsis,
maxLines: 2,
style: const TextStyle(
fontSize: Sizes.size16 + Sizes.size2,
fontWeight: FontWeight.bold,
),
),
Gaps.v8,
if (constraints.maxWidth < 200 ||
constraints.maxWidth > 250)
DefaultTextStyle(
style: TextStyle(
color: Colors.grey.shade600,
fontWeight: FontWeight.w600,
),
child: Row(
children: [
const CircleAvatar(
radius: 12,
backgroundImage: NetworkImage(
"https://avatars.githubusercontent.com/u/3612017",
),
),
Gaps.h4,
const Expanded(
child: Text(
"My avatar is going to be very long",
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
),
Gaps.h4,
FaIcon(
FontAwesomeIcons.heart,
size: Sizes.size16,
color: Colors.grey.shade600,
),
Gaps.h2,
const Text(
"2.5M",
)
],
),
)
],
),
),
),
for (var tab in tabs.skip(1))
Center(
child: Text(
tab,
style: const TextStyle(
fontSize: 28,
),
),
)
],
),
),
);
}
}
이 코드에서 LayoutBuilder
는 GridView.builder
내부에서 사용되어 각 그리드 아이템의 레이아웃을 동적으로 조정하는 역할을 합니다. LayoutBuilder
는 부모 위젯의 제약 조건에 기반하여 자식 위젯의 레이아웃을 결정할 수 있게 해주는 위젯입니다. 자식 위젯은 부모로부터 전달받은 공간의 크기(constraints
)를 바탕으로 어떻게 자신을 그려낼지 결정할 수 있습니다.
LayoutBuilder
는 builder
함수를 통해 위젯을 반환합니다. 이 함수는 현재 LayoutBuilder
위젯의 부모가 제공하는 공간의 제약 조건(BoxConstraints
)을 인자로 받습니다.GridView.builder
를 사용하여 타일 형태의 아이템을 그리드로 배열합니다. 각 타일은 사진과 캡션으로 구성됩니다.width
)에 따라 동적으로 결정됩니다. 화면 너비가 Breakpoints.lg
보다 큰 경우 5열로, 그렇지 않으면 2열로 배열합니다.LayoutBuilder
를 사용하여 타일의 최대 너비(constraints.maxWidth
)를 기준으로 조건부 레이아웃 로직을 적용합니다.FadeInImage.assetNetwork
를 사용하여 네트워크 이미지를 비동기로 로드하고, AspectRatio
위젯을 사용하여 사진의 종횡비를 9:16로 유지합니다.LayoutBuilder
를 사용함으로써, 이 코드는 다양한 화면 크기와 해상도에서도 일관된 사용자 경험을 제공할 수 있는 동적 레이아웃을 구현합니다. 특히, GridView
내부에서 개별 아이템의 레이아웃을 화면 크기에 따라 조정함으로써, 앱의 반응형 디자인을 향상시킬 수 있습니다.
이 코드 조각은 LayoutBuilder
위젯 내부에서 사용되는 문자열 표현식입니다. LayoutBuilder
는 부모 위젯으로부터 받은 공간의 제약 조건(constraints
)에 따라 자식 위젯의 레이아웃을 구성할 때 사용됩니다. 여기서 constraints.maxWidth
는 LayoutBuilder
에 의해 제공된 최대 너비 값을 참조하며, 이 값은 부모 위젯이 자식 위젯에게 할당할 수 있는 최대 너비를 나타냅니다.
"${constraints.maxWidth} This is a very long caption for my tiktok that im upload just now currently."
constraints.maxWidth
: LayoutBuilder
의 현재 제약 조건에서 제공된 최대 너비 값입니다. 이 값을 문자열 내에 직접 삽입하여 동적으로 표시합니다." This is a very long caption for my tiktok that im upload just now currently."
: 사용자가 업로드한 TikTok 비디오에 대한 설명을 나타내는 정적인 텍스트 부분입니다.LayoutBuilder
를 사용하여 동적으로 결정되는 최대 너비 값(constraints.maxWidth
)과 함께 비디오의 캡션을 표시하는 데 사용됩니다."${constraints.maxWidth}"
를 통해 실제로 텍스트가 표시될 때 해당 위치에서의 최대 너비를 시각적으로 확인할 수 있으며, 이는 디버깅이나 레이아웃 조정에 유용할 수 있습니다.constraints.maxWidth
값을 직접 보여주는 것은 일반적으로 바람직하지 않습니다. 이 코드는 주로 개발 과정에서 레이아웃을 테스트하고 조정하는 데 사용될 수 있으며, 최종 사용자에게는 최적화된 콘텐츠와 UI만을 제공해야 합니다.