[Flutter] 토스, 마스터카드 로고 만들기

CHOI·2022년 1월 22일
7

[Flutter] 따라하기

목록 보기
2/3
post-thumbnail

토스뱅크카드

이전 게시글에서 CustomPaint를 이용해서 토스뱅크카드의 형태를 잡았습니다.

이번에는 카드 정보와 함께 토스 로고마스터카드 로고를 추가하겠습니다.


카드 정보

카드 정보는 상단에는 회색 텍스트의 타이틀이 있고,
하단에는 흰색 텍스트의 카드 정보가 있습니다.

이 두 가지를 여러 곳에서 사용할 수 있도록 위젯으로 만들었습니다.

// 카드 정보.
Widget _cardInformation({required String title, required String information}) {
  return Column(
    crossAxisAlignment: CrossAxisAlignment.start,
    children: [
      // 카드 정보 타이틀.
      Text(
        title,
        style: const TextStyle(
          color: Colors.grey,
          fontWeight: FontWeight.w500,
        ),
      ),

      // padding.
      const SizedBox(height: 5),

      // 카드 정보.
      Text(
        information,
        style: const TextStyle(
          color: Colors.white,
          fontSize: 20,
          fontWeight: FontWeight.w700,
        ),
      ),
    ],
  );
}

카드 정보 위젯을 만들었으면 카드 정보를 만들어 놓았던 토스뱅크카드 위에 Positioned을 잡고 위치를 배치했습니다.
위젯들을 이용해서 카드번호와 유효기간, cvv 번호를 입력해 주세요.

// 카드 정보.
Positioned(
  left: 30,
  bottom: _height / 3.5,
  child: Column(
    crossAxisAlignment: CrossAxisAlignment.start,
    children: [
      // 카드 번호.
      _cardInformation(title: '카드번호', information: '0000 0000 0000 0000'),

      // 유효기간 + CVV.
      Padding(
        padding: const EdgeInsets.only(top: 18),
        child: Row(
          children: [
            // 유효기간.
            _cardInformation(title: '유효기간', information: '00 / 00'),

            // padding.
            const SizedBox(width: 32),

            // cvv.
            _cardInformation(title: 'CVV', information: '000'),
          ],
        ),
      ),
    ],
  ),
),


토스 로고

토스 로고를 자세히 보시면 그라데이션 효과가 있습니다.
그라데이션 효과를 구현하는 방법은 여러가지 있지만 일반 텍스트를 ShaderMask 위젯으로 감싸주었습니다.
그리고 ShaderMask 안에 Gradient 클래서를 넣었습니다.

ShaderMask

ShaderMask class
셰이더에 의해 생성된 마스크를 자식에 적용하는 위젯입니다.
예를 들어 ShaderMask는 새로운 ui.Gradient.linear 마스크를 사용하여 자식의 가장자리를 점차적으로 페이드 아웃하는 데 사용할 수 있습니다.

shaderCallback
shaderCallback은 자식의 현재 크기와 함께 호출되므로 셰이더를 자식의 크기와 위치에 맞게 사용자 지정할 수 있습니다. 즉 자식의 크기에 맞게 그라데이션 효과를 지정 할 수 있습니다.

ShaderMask(
  blendMode: BlendMode.srcIn,
  shaderCallback: (bounds) => ...
  child: const Text(
    'toss bank',
    style: TextStyle(
    fontSize: 30,
    fontWeight: FontWeight.w700,
    ),
  ),
);

Gradient

이제 ShaderMask 안에 그라데이션 효과를 넣을 수 있는 Gradient 위젯을 추가해 주면 됩니다.
Gradient를 상속받고 있는 클랙스는 LinearGradient, RadialGradient, SweepGradient가 있습니다.
클래스 별로 그라데이션 효과를 다르게 부여할 수 있습니다.

LinearGradientRadialGradientSweepGradient
ShaderMask(
  blendMode: BlendMode.srcIn,
  shaderCallback: (bounds) => const LinearGradient(
    begin: Alignment.topCenter,
    end: Alignment.bottomCenter,
    colors: [
      Colors.white,
      Colors.black12,
    ],
  ),
  child: ...
);

위에 코드처럼 작성하면 오류가 발생합니다.

Error: The return type 'LinearGradient' isn't a 'Shader', as required by the closure's context.

위에 오류를 보면 LinearGradient는 Shader가 아니기 때문에 ShaderMask 안에 위치할 수 없다는 의미입니다.
그렇다면 LinearGradient를 Shader 속성으로 변경해 주는 코드가 필요로 합니다.
바로 createShader() 메서드를 이용하면 됩니다.

LinearGradient().createShader()

createShader()

createShader()
주어진 사각형을 채우기 위해 이 그라디언트에 대한 셰이더를 만듭니다.
그래디언트의 구성이 텍스트 방향 종속인 경우(예: Alignment 개체 대신 AlignmentDirectional 개체를 사용하는 경우) textDirection 인수는 null이 아니어야 합니다.
셰이더의 변환은 이 그라디언트의 변환에서 해결됩니다.

createShader()의 인자로는 Rect가 들어가야 합니다.
Rect의 값으로 그라데이션 효과의 위치와 형태를 지정할 수 있습니다.
저는 중앙값을 지정해 주었습니다.

.createShader(
  Rect.fromCenter(
    center: Offset(bounds.width / 2, bounds.height * 0.7),
    width: bounds.width,
    height: bounds.height,
  ),
);


mastercard 로고

mastercard 로고는 저번 시간에 사용했던 CustomPaint와 토스로고에서 이용했던 Gradient를 모두 이용해서 만들 수 있습니다.

토스뱅크카드의 마스터카드 로고를 보시면 테두리에 다양한 색상으로 그라데이션 효과가 있습니다.
그리고 원은 회색으로 채워져있습니다.
그래서 저는 Stack을 이용해서 하단에는 테두리의 그라이데이션 효과를 만들고, 그 위에 회색 원을 놓는 방식으로 만들었습니다.

Gradient

우선 마스터카드 로고의 테두리와 마스터카드 텍스트를 Gradient를 이용해서 구현했습니다.
테두리는 SweepGradient를 이용하고 텍스트는 LinearGradient를 이용했습니다.

Stack(
  alignment: AlignmentDirectional.center,
  children: [
    // 그라데이션 배경.
    Positioned(
      top: 0,
      left: 0,
      child: _gradientCircle(),
    ),
    Positioned(
      top: 0,
      right: 0,
      child: _gradientCircle(),
    ),
    
    ...
  ],
),

// 테두리 그라데이션.
ClipRRect _gradientCircle() {
  return ClipRRect(
    borderRadius: BorderRadius.circular(40),
    child: Container(
      width: 80,
      height: 80,
      decoration: const BoxDecoration(
        gradient: SweepGradient(
          colors: Colors.accents,
        ),
      ),
    ),
  );
}
// 마스터카드 텍스트.
ShaderMask(
  blendMode: BlendMode.srcIn,
  shaderCallback: (bounds) => LinearGradient(
    begin: Alignment.centerLeft,
    end: Alignment.centerRight,
    colors: [
      Colors.grey.shade600,
      Colors.grey,
    ],
  ).createShader(
    Rect.fromCenter(
      width: bounds.width,
      height: bounds.height,
      center: Offset(bounds.width / 2, bounds.height / 2),
    ),
  ),
  child: const Text(
    'mastercard',
    style: TextStyle(
      fontSize: 20,
      fontWeight: FontWeight.w800,
    ),
  ),
),

CustomPaint

CustomPaint는 왼쪽, 중앙, 오른쪽 분리해서 따로 구현을 했습니다.
Canvas에 도형을 그리는 데는 arcTo() 메서드를 이용했습니다.
arcTo 메서드는 두 직선 사이의 호를 그릴 때 사용하기 좋은 메서드입니다.

arcTo()
forceMoveTo 인수가 false이면 직선 세그먼트와 호 세그먼트를 추가합니다.
forceMoveTo 인수가 true이면 호 세그먼트로 구성된 새 하위 경로를 시작합니다.

두 경우 모두 호 세그먼트는 타원 주위의 startAngle 라디안에서 타원 주위의 startAngle + sweepAngle 라디안까지 주어진 직사각형으로 둘러싸인 타원의 가장자리를 따르는 호로 구성되며 0 라디안은 오른쪽의 점이 됩니다. 직사각형의 중심을 가로지르는 수평선을 가로지르는 타원의 측면과 타원을 중심으로 시계 방향으로 가는 양의 각도.

forceMoveTo가 false인 경우 추가된 선분은 현재 지점에서 시작하여 호의 시작 부분에서 끝납니다.

왼쪽

// 지름.
const double _diameter = 38 * 2;

// 왼쪽.
final _leftPath = Path()
  ..arcTo(
    Rect.fromCenter(
      center: const Offset(40, 40),
      width: _diameter,
      height: _diameter,
    ),
    math.pi / 2.9,
    math.pi * 1.28,
    false,
  )
  ..arcTo(
    Rect.fromCenter(
      center: const Offset(80, 40),
      width: _diameter + 4,
      height: _diameter + 4,
    ),
    -math.pi / 1.45,
    -math.pi * 0.63,
    false,
  );
  
canvas.drawPath(_leftPath, _paint);

오른쪽

// 오른쪽.
final _rightPath = Path()
  ..arcTo(
    Rect.fromCenter(
      center: const Offset(80, 40),
      width: _diameter,
      height: _diameter,
    ),
    -math.pi / 1.52,
    math.pi * 1.28,
    false,
  )
  ..arcTo(
    Rect.fromCenter(
      center: const Offset(40, 40),
      width: _diameter + 4,
      height: _diameter + 4,
    ),
    math.pi / 3.2,
    -math.pi * 0.63,
    false,
  );

canvas.drawPath(_rightPath, _paint);

중앙

// 중앙.
final _centerPath = Path()
  ..arcTo(
    Rect.fromCenter(
      center: const Offset(40, 40),
      width: _diameter,
      height: _diameter,
    ),
    -math.pi / 3.1,
    math.pi * 0.63,
    false,
  )
  ..arcTo(
    Rect.fromCenter(center: const Offset(80, 40), width: _diameter, height: _diameter),
    math.pi / 1.48,
    math.pi * 0.65,
    false,
  );

canvas.drawPath(_centerPath, _paint);

Paint()

3가지로 분리한 값들을 하나의 CustomPainter에 넣고, Paint의 속성을 채우는 속성으로 변경을 하면 마스터카드 로고는 완성입니다.

final _paint = Paint()..color = Colors.grey.shade400;


완성

토스뱅크카드의 형태, 카드 정보, 토스 로고, 마스터카드 로고 구현을 통해서 토스뱅크의 카드 페이지를 만들었습니다.

아마도 토스에서는 카드 형태와 마스터카드 로고 정도는 이미지로 구현을 했을 것으로 예상을 하고 있습니다.
그래도 코드로 구현을 하면서 GradientCustomPaint를 더 공부할 수 있게 되었습니다.

📌 제가 구현 방식 보다 더 좋은 구현 방식이 있으면 댓글로 공유 부탁드려요~


Github
https://github.com/cyb9701/toss-card

profile
Mobile App Developer

1개의 댓글

comment-user-thumbnail
2022년 2월 22일

👍

답글 달기