Flutter에서 Variable Fonts써보기

블로그의 폰트를 정할 때 별생각 없이 Pretendard를 써야지 하고 김형진 님의 블로그에 접속했을 때 처음으로 보이는 애니메이션에 매료됐고 마침 Variable Fonts에 글을 쓰기로 했던 터라 이 둘을 엮어서 글을 써보게 됐습니다. Pretendard blog

물론 일을 하며, 개인 프로젝트를 하며 폰트를 쓸 일이 잦아 Variable Fonts의 존재는 알고 있었지만 TextStyle 클래스에 VariableFonts 속성이 없었던 것이 이유인지 아니면 디자인에 수동적인 자세를 취하던 게 이유인지 깊게 생각할 일이 없었던 거 같습니다.

Variable Fonts

여러 가지 글꼴 형태를 단일 파일로 통합하는 형태의 글꼴을 Variable Fonts 혹은 가변 글꼴이라고 합니다. 하나의 파일로 글자의 굵기, 기울임, 너비 등을 조절할 수 있어 선택지가 다양해지고 관리 측면에서 장점이 있습니다. Google Fonts의 Variable Fonts에 대한 자세한 설명

어떻게 사용할까?

그렇다면 Flutter에서는 어떻게 사용할까요?
정적 파일을 이용해 글꼴을 나타낸다면 우리는 보통 아래와 같은 방식으로 사용했을 것입니다.

flutter:

  uses-material-design: true
  fonts:
    - family: Pretendard
      fonts:
        - asset: assets/fonts/Pretendard-Thin.otf
          weight: 100
        - asset: assets/fonts/Pretendard-ExtraLight.otf
          weight: 200
        - asset: assets/fonts/Pretendard-Light.otf
          weight: 300
        ...

Pretendard는 굵기 관련 파일을 9개 지원하기 때문에 weight 별로 파일을 매칭 시켜주게 됩니다. 하지만 이런 식으로 애니메이션을 구현하게 된다면 아래와 같은 결과를 얻게 됩니다.

when I don't use FontVariation 원하는 애니메이션은 자연스럽게 굵기가 바뀌어야 하는데 기존의 방식대로 하게 된다면 각각의 굵기 사이 값을 표현할 방법이 없기 때문에 뚝뚝 끊기는 결과를 얻은 것입니다. 물론 100, 101, 102 ... 이런 식으로 모든 weight를 추가하면 나아지겠지만 우리가 원하는 방식은 아닙니다.

FontVariations

그렇기 때문에 우리는 Variable Fonts를 사용해야 합니다. Flutter에선 TextStyle의 FontVariations 속성을 통해 표현할 수 있습니다. 아래와 같이 VariableFonts를 지원하는 글꼴이라면 pubspec.yaml 추가해 주면 됩니다.

flutter:

  uses-material-design: true
  fonts:
    - family: Pretendard
      fonts:
        - asset: assets/fonts/PretendardVariable.ttf

그 후에 TextStyle의 fontVariations 속성에 FontVariation 객체를 만들어 전달함으로써 사용할 수 있습니다. 우리는 굵기 변화를 통해 애니메이션을 구현할 것이기 때문에 wght의 값을 지정해 줍니다.

Text(
  'Hello World',
  style: TextStyle(
    fontFamily: 'Pretendard',
    // 말 그대로 "다변"글꼴이기 때문에 글꼴이 지원하는 한 여러 옵션의 변화를 줄 수 있어 리스트입니다.
    fontVariations: [
      FontVariation('wght', 200)
    ]
  ),
);

구현

어떻게 사용하는지도 알았으니 본격적으로 구현을 해보겠습니다. 저는 AnimatedBuilder -> Text.rich 구조로 작성할 생각입니다.

initState

  @override
  void initState() {
    super.initState();
    _controller = AnimationController(vsync: this, duration: const Duration(seconds: 3), lowerBound: 0, upperBound: math.pi);
    _controller.repeat();
  }

AnimationController의 값을 0 - pi로 초기화 후 반복을 위해 repeat을 호출합니다.

build

  @override
  Widget build(BuildContext context) {
    return RepaintBoundary(
      child: AnimatedBuilder(
          animation: _controller,
          builder: (context, _) {

            return Text.rich(
                TextSpan(
                    children: List.generate(widget.text.length, (index) {

                      final variable = math.sin((_controller.value - index/widget.text.length) * 2) / 2 + 0.5;
                      final value = 100 + 900 * variable;

                      return TextSpan(
                        text: widget.text[index],
                        style: widget.style.copyWith(
                            fontVariations: [
                              FontVariation('wght', value),
                            ]
                        ),
                      );
                    })
                )
            );
          }
      ),
    );
  }
  1. 반복되는 화면 화면 변경의 범위를 제한하기 위해 RepaintBoundary로 최상위 위젯을 구성합니다.
  2. Text.rich의 자식이 될 각각의 글자의 wght값을 자연스럽게 반복하기 위해 삼각함수를 이용해 value 값을 세팅합니다

결과

Good result VariableFonts가 무엇인지 한눈에 설명 가능한 애니메이션을 구현했습니다 👏👏
추후 gif가 아닌 mp4로 재업로드 할 수 있도록 하겠습니다.

정리

  1. Variable Fonts가 만능은 아니지만 프로젝트에서 폰트 파일이 많아진다면 좋은 선택이 될 수 있다.
  2. 애니메이션과 함께 사용해 신선한 경험을 선사할 수 있다.

링크

코드

Pretendard 블로그