말랑한 하루

[Flutter] (Project) MapleApp: 17. Equipment Detail Page 제작-4 본문

개발/Flutter

[Flutter] (Project) MapleApp: 17. Equipment Detail Page 제작-4

지수는말랑이 2024. 1. 12. 19:36
반응형

이제 아이템 디테일 페이지 제작이 마무리 단계에 다다랐다. 

🐇 english font

※ reference : https://maplestory.nexon.com/Media/Font

영문이 아무리 봐도 인게임과 다르게 표시되는 것이 거슬렸다. 보통 인게임 내 글자는 Dodum, 돋움을 사용한다는 것으로 알려져 있었는데 영문은 찾을 수 없었다.

그런데 메이플스토리 공식 홈페이지 내 미디어/아트윅 부분에서 메이플스토리 서체 TTF를 받을 수 있었다. 이 서체를 적용하니 인게임 화면과 동일한 글씨체를 얻을 수 있었다.

메이플스토리 서체를 사용할 때는 다음과 같은 조건이 존재하니 조심하도록 하자.

 

🍒 메이플스토리 서체의 지적 재산권을 포함한 모든 권리는 ㈜넥슨코리아에 있습니다.

🍒 메이플스토리 서체는 개인 및 기업 사용자를 포함한 모든 사용자에게 무료로 제공되며 자유롭게 사용 및 배포하실 수 있습니다.단, 임의로 수정, 편집 등을 할 수 없으며 배포되는 형태 그대로 사용해야 합니다.

🍒 글꼴 자체를 유료로 판매하는 것은 금지되며,메이플스토리 서체의 본 저작권 안내를 포함해서 다른 소프트웨어와 번들하거나 임베디드 폰트로 사용하실 수 있습니다.

🍒 메이플스토리 서체의 출처 표기를 권장합니다.예) 이 페이지에는 메이플스토리가 제공한 메이플스토리 서체가 적용되어 있습니다.

🍒 메이플스토리 서체를 사용한 인쇄물, 광고물(온라인 포함)은 넥슨의 프로모션을 위해 활용될 수 있습니다.이를 원치 않는 사용자는 언제든지 넥슨에 요청하실 수 있습니다.

🐇 text shadow

너무 밝은 배경에서는 흰색 글자가 묻히거나 보이지 않는 경우가 많았다. 또한, 작은 글씨임에도 불구하고 아이콘 안에 들어가야 하는 경우가 있었기 때문에 text shadow를 찾아야했다.

※ reference : https://api.flutter.dev/flutter/dart-ui/Shadow-class.html

 

다행히 TextStyle에는 shadows속성이 있다. Shadow Widghet을 활용하여 offest, blurRadius, color를 지정해주면 간편하게 설정할 수 있다.

🐇 text background icon

Icon Widget이나 외부 파일을 사용하지 않은 Custom icon이 필요했다. 이 icon은 텍스트 한 글자에 알맞는 크기로 설정되어야 했기 때문에 다양한 Widget으로 시도를 했음에도 불구하고 성공하지 못했다.

그래서 나의 선생님에게 물어보았고, fontSize의 n배 만큼 크기를 설정하면, 텍스트 한 글자에 맞는 배경이 완성된다고 했다.

그래서 시도해 본 결과 아주 완벽한 square가 탄생했다! 텍스트에 알맞는 특정 크기의 배경을 원한다면 SizedBox의 width와 height에 fontSize * x 를 넣어서 해결하는 방법을 터득했다.

🐇 SingleChildScrollView

※ reference : https://api.flutter.dev/flutter/widgets/SingleChildScrollView-class.html

 

대부분의 데이터를 넣다 보니, 화면이 자연스레 길어지고 viewport를 벗어나는 모습을 보였다. 그래서 사용자로 하여금 스크롤 하여 확인할 수 있게 ScrollView를 넣으려했다.

 

SingleChildScrollView는 단일 상자를 사용할 때 유용합니다. 하지만 단일 상자인 특정 Container가 너무 작아지면 스크롤 할 수 있는지 확인해야 하는 것에 주의해야 합니다.

 

SingleChildScrollView를 구현하는 가장 쉬운 방법은 LayoutBuilder를 활용해 BuildContext에서 context와 BoxConstraints에서 viewportConstraints를 받아와 ConstrainedBox Widget과 BoxConstraints class를 활용하여 하위 컨테이너를 만들어가는 방법입니다.

 

이는 공식 문서에서도 다음과 같이 소개하고 있습니다.

return SafeArea(
	child: Scaffold(
		appBar: AppBar(),
		body: LayoutBuilder(
			builder: (BuildContext context, BoxConstraints viewportConstraints) {
				return SingleChildScrollView(
					child: ConstrainedBox(
						constraints: BoxConstraints(
							minHeight: viewportConstraints.maxHeight,
						),
						child: Container(
							margin: ~
							child: Column(~)
						)))
			}
		)))

🐇 splitMapJoin

RedExp를 활용하여 일치/불일치 항목에 대한 replace를 한 결과 값을 얻을 수 있는 method이다. 전체 문구에서 특정 구문을 지우고 싶어 찾아보았다. 사용 방법은 단순하다.

name.splitMapJoin(
	'싫어하는 문구',
	onMatch: (p0) => '',
	onMatch: (p1) => '${p1}',
)

위 구문을 실행하면 싫어하는 문구에 대해 매치 하는 부분은 ''로 매치 하지 않는 부분은 그대로 둠으로써 싫어하는 문구를 지울 수 있다.

🐇 spot line Widget

※ reference : https://api.flutter.dev/flutter/material/Divider-class.html

 

flutter에는 위젯을 구분할 수 있는 divider Widget을 제공합니다. 하지만, 나는 spot divider Widget이 필요했습니다.

※ reference : https://pub.dev/packages/dotted_border

 

그러나 flutter에서는 점선으로 된 테두리를 제공하는 Widget이 pub.dev에 존재했고 내가 원하는 형태의 Widget은 Custom해야 했습니다.

 

※ reference : https://blog.terry1213.com/flutter/flutter-dasheddivider/

 

멋진 flutter 개발자들은 이미 spot divder Widget을 구현해놨고, 나는 이것을 참조하여 나만의 Custom spot divider Widget을 구현할 수 있었습니다. 참고자료를 활용해 만들어낸 Widget은 다음과 같습니다.

class DashedDividerWidget extends ConsumerWidget {
  const DashedDividerWidget({super.key});

  @override
  Widget build(BuildContext context, WidgetRef ref) {
    double conatinerHeight = 4;
    return Container(
        height: conatinerHeight,
        margin: EdgeInsets.only(
            top: DimenConfig.commonDimen, bottom: DimenConfig.commonDimen),
        color: Colors.black87,
        child: LayoutBuilder(
          builder: (BuildContext context, BoxConstraints viewportConstraints) {
            double boxWidth = viewportConstraints.constrainWidth();
            double dashWidth = 4;
            double dashHeight = conatinerHeight / 2;
            int dashLenght = (boxWidth / (2 * dashWidth)).floor();
            return Flex(
              mainAxisAlignment: MainAxisAlignment.spaceBetween,
              direction: Axis.horizontal,
              children: List.generate(dashLenght, (_) {
                return Column(
                  children: [
                    SizedBox(
                        width: dashWidth,
                        height: dashHeight / 2,
                        child: DecoratedBox(
                          decoration: BoxDecoration(color: Colors.black),
                        )),
                    SizedBox(
                        width: dashWidth,
                        height: dashHeight / 2,
                        child: DecoratedBox(
                          decoration: BoxDecoration(
                              color: ItemColor.deactiveOptionText),
                        )),
                  ],
                );
              }),
            );
          },
        ));
  }
}

저는 dash divider의 활용이 고정되어 있기 때문에, 내부 변수를 동적으로 제어하지 않았습니다. 동적으로 제어하여 여러 곳에서 사용하고 싶은 사람은, reference를 참고하여 내부에서 사용되는 모든 변수를 매개변수로 넘겨주면 될 것 같습니다.


이제 대부분의 작업이 끝났고, 인게임과 스타일을 맞춰가기 위한 작업을 하려고 한다.

🐇 linear gradient

※ reference : https://api.flutter.dev/flutter/painting/LinearGradient-class.html

 

기본적으로 BoxDecoration의 gradient속성과 함께 LinearGradient가 사용된다. 주요 속성으로는 begin/end/colors/tileMode가 있다.

 

begin/end에 시작할 위치를 Alignment로 지정해주고, color에 최소 2가지 색상을 부여한다. 색상은 순서대로 begin~end까지 진행된다. tileMode의 예시를 보면서 내가 원하는 형태의 gradient를 선택하면 된다. 다음은 간단한 사용 예시이다.

Container(
  decoration: BoxDecoration(
    gradient: LinearGradient(
        begin: Alignment.topCenter,
        end: Alignment.bottomCenter,
        colors: [
          Colors.black,
					Colors.white,
        ],
        tileMode: TileMode.mirror),
    border: ~,
    borderRadius: ~,
  ),
  child: Container(),
)

🐇 Transform

※ reference : https://api.flutter.dev/flutter/widgets/Transform-class.html

 

아이템의 배경에 알 수 없는 흰색 하이라이트 표시가 있었다. 아무리 봐도 Conatiner에 색을 칠한 뒤 opacity를 조절하고 translate을 활용해서 움직이고 돌린 것 같았다.

 

그래서 flutter에도 그런 Widget이 있나 확인했고, Transform Widget을 찾을 수 있었다.

 

Transform Widget은 정말 다양한 3d animation의 기초가 되는 것을 관람하다가, 내가 원했던 2d 움직임을 간단하게 설정할 수 있는 옵션을 찾았다.

 

바로 translate과 rotate이다. translate을 이용해서 Container를 중심부로부터 일정 위치까지 움직이고, rotate를 이용해 돌려주었다. 공식 문서 영상을 통해 math class에 포함된 pi/4로 45도 회전한 Container를 얻을 수 있었고 구현은 다음과 같다.

Container(
	Transform.rotate(
		angle: pi / 4,
		child: Transform.translate(
			offset: Offset(-90, 0),
			child: Container(
				color:Colors.white,
			))))

하지만 이렇게 되면, 내가 움직였던 white색상의 Container가 부모의 Container 밖으로 삐져나온 것처럼 보인다. 그럴 때 유용한 것이 바로 clipBehavior이다.

 

🥕 clipBehavior

※ reference : https://docs.flutter.dev/release/breaking-changes/clip-behavior

 

ClipBehavior는 내가 원하는 모양대로 현재 내 Container 크기에 맞게 하위 자식 요소의 overflow를 잘라주는 역할을 한다.

 

※ reference : https://docs.flutter.dev/release/breaking-changes/clip-behavior

 

다양한 Clip Widget의 기본 clipBehavior설정을 보려면 위의 reference를

 

※ reference : https://api.flutter.dev/flutter/dart-ui/Clip.html

 

Clip의 option이 하는 일을 보려면 위의 reference를 참고해주세요. 가장 빠르게 설정할 수 있는 Clip은 hardEdge입니다.

반응형
Comments