말랑한 하루
[Flutter] (Proejct) MapleApp: 13. Equipment Page 제작 본문
🐇 TabBar/TabBarView Make
아이템 페이지는 상단에 탭이 존재하는데, DefaultTabController와 TabBar/TabBarView를 사용하지 않고 직접 구현하려 했다.
내가 원하는 상단의 Tab이 Custom하기도 하며, 다른 페이지에서도 사용하기 때문이다.
그래서 추후 공용으로 사용될 SelectTab은 위젯으로 빼놓고, 사용될 tabList에 equipment제작 시 필요한 이름, 속성 또한 글자 수에 따른 font크기와 dime size, 마지막으로 selectTab를 담았고, 마지막으로 관리할 provider와 함께 위젯 파라미터로 넘겼다. 그 구현은 다음과 같다.
class DetailSelectTabWidget extends ConsumerWidget {
const DetailSelectTabWidget(
{super.key, required this.tabList, required this.provider});
final List tabList;
final StateProvider<String> provider;
Widget build~
}
tabList는 정적 값이기 때문에 static config에 추가하였다.
static final List detailStatTab = [
{
'name': '기본',
'text': '기본 스탯',
'fontSize': FontConfig.commonSize,
'dimenSize': DimenConfig.commonDimen,
},
...
];
name을 가져가는 이유는, selectTab을 선택했을 때 비교하여 onTap Gesture와 색상 반전을 적용시키기 위해서이다.
🐇 Equipment Table/GridView/Wrap
이제 Item이 들어갈 Table을 만들 것이다. 같은 모습의 Item pot이 25개정도 들어가고, 중간 중간 Empty한 공간이 있기 때문에, static config에 tableList를 추가하고 Table 속성은 다음과 같이 사용하였다.
🥕 Table
※ reference : https://api.flutter.dev/flutter/widgets/Table-class.html
Table(
border: ~,
columnWidths {
0: IntrinsicColumnWidth(),
1: FlexColumnWidth(),
2: FixedColumnWidth(),
},
defaultVerticalAlignment: TableCellVerticalAligment.center,
children: [
TableRow(
children: [
Container(),
TableCell(),
Container(),
]
)
],
)
행이 하나만 있는 경우 Row Widget이, 열이 하나만 있는 경우 SliverList/Column Widget이 더 효과적이니 참고하자. 테이블 Width를 사용할 때 주의할 점은 다음과 같다.
내용에 따라 행의 세로 크기가 결정됩니다. 개별 열 너비를 제어하려면 columnWidth속성을 사용해 각 열에 대한 TableColumnWidth를 지정합니다. 만약, columnWidths가 null이거나, 지정된 열에 대해 null항목이 있는 경우 테이블에서는 대신 defaultColumnWidth를 사용합니다.
기본적으로 defaultColumnWidth는 FlexColumnWidth입니다. TableColumnWidth는 가로축의 남은 공간을 나누어 결정합니다. 가로 ScrollView에서 Table을 rapping하는 경우, FixColumnWidth와 같은 TableColumnWidth를 선택하세요.
그렇다면 현재 columnWidth에서 사용하는 함수들은 무엇인지 궁금해서 정리해보았다.
🍒 IntrinsicColumnWidth
열의 고유 크기를 결정하기 위해 열의 각 셀을 측정해야 하기 때문에 비용이 많이 듭니다.
🍒 FlexColumnWidth
나머지 세로 줄이 차지하고 남은 공간 전체를 가로 길이로 설정한다. 만약 여러 줄이 사용한다면 남은 공간은 동일하게 나누어 사용한다.
🍒 FixedColumnWidth
특정 너비를 픽셀 단위로 지정할 수 있습니다.
이 모습을 보아하니, Table간 간격을 조정할 수 없다 느끼면서, Table내 Container의 높이를 Tabled의 defaultColumnWidth와 연계하여 지정할 수 있을까?라는 생각을 했다.
그래서 다른 방안으로 GridView를 찾아보았다.
🥕 GridView
※ reference : https://api.flutter.dev/flutter/widgets/GridView-class.html
그리드의 주축 방향은 그리드가 스크롤되는 방향입니다. 가장 일반적으로는 교차 축에 고정된 수의 타일이 있는 레이아웃을 생성하는 GridView.count와 최대 교차 축 범위가 있는 타일의 레이아웃을 생성하는 GridView.extent가 있습니다. 사용자 정의 SliverGridDelegate는 정렬되지 않거나 겹치는 배열을 포함하여 하위 항목의 임의 2D배열을 생성할 수 있습니다.
만약, Infinity한 Gird를 얻고 싶으면 SliverGirdDelegateWithFixedCrossAxisCount나 GridDelegate에 SliverGridDelegateWithMaxCrossAxisExtent와 함께 GridView.builder 생성자를 사용해보세요.
이 프로젝트에서 GirdView가 스크롤 할 일은 없으므로 관련 내용은 기술하지 않겠다. 가장 기본적인 GirdView.count 생성자를 활용해 구현하면 다음과 같이 제작할 수 있다.
GridView.count(
crossAxisCount: 5,
crossAxisSpacing: DimenConfig.commonDimen,
mainAxisSpacing: DimenConfig.commonDimen,
children: [
Container(color: Colors.blue, child: Text('1')),
],
),
하지만 여기서 GridView의 단점이 나타난다. GridView는 요소와 상관없이 자신의 부모 크기의 최대 width/height를 지닌다. 즉, GridView내에 요소가 부족하여 여백이 생긴다고 해도, GirdView내 요소의 위치를 옮길 수 없음을 의미한다.
Application의 특성상 비어있는 부분을 어떻게 활용할 지 추후 모색하여 버전 업그레이드를 진행해야 한다 생가하고, 또한 프론트엔드 입장에서 여백은 남기고 싶지 않은 공간이라고 생각한다.
그래서 다른 방안으로 Wrap Widget을 찾아보았다.
🥕 Wrap
※ reference : https://api.flutter.dev/flutter/widgets/Wrap-class.html
Wrap은 여러 개의 가로/세로 방향으로 하위 항목을 표시하는 위젯으로, 지정된 주 축에서 이전 하위 항목에 인접하여 다른 항목을 배치하고 사이에 간격을 만들 수 있습니다. 하지만 자식을 수용할 공간이 충분하지 않은 경우, Wrap은 교차 축의 기존 자식 옆에 새로운 Wrapd을 생성합니다. 쉽게 말해서 줄바꿈을 하는 것입니다.
모든 하위 항목이 할당된 후, run 내의 항목은 기본 축의 정렬과 교차 축의 crossAxisAligment에 따라 배치됩니다. 이렇게 되면 runSpacing 및 runAlignment에 따라 교차 축에 배치됩니다.
구현은 다음과 같습니다.
Wrap(
runAligment: WrapAlignment.center
children: [],
)
하지만 우리는 전체 공간에 NxN 크기의 동적 테이블이 필요합니다. 그래서 FractionallySizedBox와 AspectRatio Widget을 사용할 겁니다.
🍒 FactionallySizedBox
widthFactor/heightFactor를 사용해 부모Widget 너비의 doubleValue 만큼 크기를 조정할 수 있습니다.
🍒 AspectRatio
aspectRatio 속성에 width/height Value를 할당하여 종횡비를 설정할 수 있습니다
위 두개 Widget과 외부 Container padding, 내부 Container margin을 사용하여 적절히 배치할 수 있습니다.
Wrap에는 spacing/runSpacing, 즉 가로 세로 내부요소의 사이 간격을 설정할 수 있습니다. 하지만 FactionallySizedBox가 동적으로 작동하지만, Wrap에 설정된 spacing value를 고려하여 width/height를 조절하려면 비용이 더 들어가기 때문에 사용하지 않았습니다.
🐇 static config equipmentList
우리는 Wrap그리고 FractionallySizedBox와 AspectRatio Widget을 활용하여 GridView를 만들어 냈습니다. 이제 그리드의 내가 원하는 곳에 고정된 slot을 할당하고 알맞은 데이터를 할당합니다.
그러기 위해서 늘 사용해왔던 static config에 equipmentItem과 관련된 List를 생성해주고, 내부 속성으로 ‘slot’: name을 할당하여 itemList 데이터와 맞물리도록 진행하였습니다.
🐇 equipment info
아이템 정보 창에 대한 내용을 업로드하기 전에, 아이템이 나오는 창의 입체 효과를 주고 싶었다. 인 게임에서 보여주던 모습처럼, 아이템 정보 칸이 눌려서 무언가 장착했다는 느낌을 주고 싶었기 때문이다.
🥕 parts create
해당 방법은 BoxShadow 2개와 blurStyle의 inner를 사용해서 만들 수 있을 것 같은데, Open CSS를 많이 뒤져보지 않았고, 다른 라이브러리를 먼저 찾았기 때문에 추후 문제가 되면 직접 구현해보려한다.
library 중 inset_box_shadow를 사용해보자. 사용 방법은 다음과 같다.
※ refrence : https://pub.dev/packages/flutter_inset_box_shadow
※ youtube : https://www.youtube.com/watch?v=A2Bbhr3DGd0
flutter pub add flutter_inset_box_shadow
flutter pub get
이후 원하는 Container의 boxDecoration을 다음과 같이 설정해준다.
import 'package:flutter/material.dart' hide BoxDecoration, BoxShadow;
import 'package:flutter_inset_box_shadow/flutter_inset_box_shadow.dart';
boxShadow: [
BoxShadow(
blurRadius: RadiusConfig.subRadius,
offset: Offset(-3, -3),
color: Colors.white,
inset: true),
BoxShadow(
blurRadius: RadiusConfig.subRadius,
offset: Offset(3, 3),
color: Colors.black,
inset: true)
]),
라이브러리를 사용하며 중요한건, 기존 material의 BoxDecoration과 BoxShadow를 잠시 숨겨놓고, inset_box_shadow의 BoxDeocoration, BoxShadow를 사용해야 된다는 것이다. 그 상태에서 inset option을 true로 설정하면 다음과 같은 결과를 얻을 수 있다.
이제 데이터와 이름, 테두리 등 다양한 정보를 넣어보자
🥕 parts info with stack
stack 정보에는 아이템 이미지, 등급(테두리)가 표시된다.
이미지의 경우 그냥 쌓아 올리면 되는데, 테두리의 경우 부여된 5가지 값에 대해 순서를 매겨야 한다. 그 이유는, 순서가 높은 등급의 테두리를 칸에 표시하기 때문이다. 등급 비교는 static config에 grade list를 만들어놓고, index를 부여하여 최고 등급부터 최저 등급까지 비교할 수 있게 만들었다.
🍒 color 속성
color 속성에 null을 주면 오류가 난다. 모든 경우 색상을 지정하고, 색을 지정하지 않고 싶은 경우 Colors.trasnparent를 통해 부모의 색을 가져오자
color: item?.potentialOptionGrade != null
? switch (item?.potentialOptionGrade) {
'레전드리' => ItemColor.legendaryPotentialBorder,
'유니크' => ItemColor.uniquePotentialBorder,
'에픽' => ItemColor.epicPotentialBorder,
'레어' => ItemColor.rarePotentialBorder,
_ => Colors.white,
}
: Colors.transparent,
'개발 > Flutter' 카테고리의 다른 글
[Flutter] (Project) MapleApp: 15. Equipment Detail Page 제작-2 (0) | 2024.01.10 |
---|---|
[Flutter] (Project) MapleApp: 14. Equipment Detail Page 제작 (0) | 2024.01.09 |
[Flutter] (Proejct) MapleApp: 12. Singleton Pattern 적용 (0) | 2024.01.07 |
[Flutter] (Project) MapleApp: 11. Character Page 제작 (0) | 2024.01.07 |
[Flutter] (Project) MapleApp: 10. intl, Date (0) | 2024.01.06 |