이 글은 Today I Learn 시리즈의 58번째 기록입니다. (총 133개)

애니메이션

등장 애니메이션에서 “제일 클 때 안 보이는” 함정

발바닥 도장을 완료 시 200% → 100%로 줄이며 등장시키려 했는데, 정작 커지는 게 안 보였다. 원인은 scale과 opacity를 같은 진행값 하나로 묶은 것이다.

// ❌ 제일 클 때(scale 시작) opacity가 0 → 큰 상태가 안 보인다
final v = Curves.easeOut.transform(_c.value);
Opacity(
  opacity: v,                                   // 0 → 1
  child: Transform.scale(scale: 2.0 - 1.0 * v), // 200% → 100%
);

v = 0일 때 scale은 200%지만 opacity도 0이라 완전히 투명하다. 게다가 easeOut은 초반이 빨라서 큰 구간을 순식간에 지나간다. 결국 “큰 순간”이 보이지도 않고 찰나에 사라진다.

해결은 scale과 opacity의 타이밍을 분리하는 것. opacity를 앞 40% 안에 채워서, 커진 상태가 보이게 한다.

// ✅ raw t 기준으로 scale/opacity를 따로
final t = _c.value;
final scale = 2.0 - 1.0 * Curves.easeOut.transform(t); // 큰 → 작은
final opacity = (t * 2.5).clamp(0.0, 1.0);             // 앞 40%에 불투명
Opacity(opacity: opacity, child: Transform.scale(scale: scale));

교훈: 두 속성이 “같은 애니메이션”이어도 진행 곡선·속도는 따로 줘야 의도한 연출이 나온다.


AnimatedBuilder — raw value를 쓰고, 정적 child는 밖으로

AnimatedBuilderbuilder에서 _c.value(0~1 raw)를 직접 쓰면 Curve를 원하는 속성에만 부분 적용할 수 있다. 이 기능으로 opacity와 scale 타이밍이 분리된다. 그리고 매 프레임 바뀌지 않는 위젯은 child로 빼두면 builder가 다시 만들지 않아 성능에 좋다.

AnimatedBuilder(
  animation: _c,
  builder: (context, child) {
    final t = _c.value;
    return Opacity(opacity: (t * 2.5).clamp(0, 1),
      child: Transform.scale(scale: 2.0 - 1.0 * Curves.easeOut.transform(t),
        child: child)); // child 재사용
  },
  child: Image.asset('assets/paw.png', ...), // 매 프레임 재생성 X
);

위젯 재활용

내장 Material 아이콘을 “스티커”로 재활용 — 에셋 0, 라이선스 무료

스티커를 위해 PNG를 따로 만들거나 패키지를 붙일 필요가 없었다. Flutter에 기본 번들된 Icons(Material Icons, Apache 2.0)를 파스텔 색으로 tint하면 그대로 스티커가 된다. 의존성·에셋 추가 0.

// 스티커 = 아이콘 + 색
class StickerItem {
  final IconData icon;
  final Color color;
  const StickerItem(this.icon, this.color);
}

Icon(item.icon, color: item.color, size: 48); // 파스텔 tint

단, 내장 아이콘은 카테고리가 편중돼 있다. 특히 동물이 거의 없다 — 토끼(cruelty_free), 새(flutter_dash) 정도만 있고 정작 개·고양이가 없다. 더 다양한 세트가 필요하면 material_symbols_icons 같은 패키지 검토 또는 별도 제작한 PNG 사용이 필요하다.


상태 표시

3칸 인디케이터 — 값을 칸수·색으로 매핑

강아지 케어 상태(0 건강 ~ 3 아픔)를 알약형 3칸으로 표시했다. 남은 칸 = 3 - level로 뒤집고, 채워진 칸 수에 따라 색을 정한다.

final filled = (3 - level).clamp(0, 3);
final color = switch (filled) {
  3 => green,   // 건강
  2 => yellow,  // 주의
  1 => red,     // 위험
  _ => grey,    // 0칸 = 아픔
};

Row(children: List.generate(3, (i) => _cell(
  on: i < filled, color: color, // 왼쪽부터 채움
)));

레벨(내부 수치)과 표시(칸수·색)를 분리해두면, 나중에 단계 수나 색 규칙만 바꿔도 로직이 안 깨진다.


Series: Today I Learn

1 C++ 자료형(Data Type) 2 MD5 vs pHash 3 C++에서 함수의 선언과 정의 4 Tkinter padx, pady 5 메모리와 포인터 변수 6 Call by Value, Call by Reference, Call by Pointer 비교 7 const 8 Gemfile — Jekyll 프로젝트의 의존성 파일 9 kramdown-parser-gfm — Jekyll의 GFM 파서 10 파서(Parser) 11 AHU vs OHU 12 I might try it vs I'll try it 뉘앙스 차이 13 SESSION_EXPIRE_AT_BROWSER_CLOSE=True 14 configuration key 15 Git stash vs discard 16 subprocess.Popen으로 Windows 탐색기에 명령어를 전달 17 Post 잔디 분석하기 18 Google Sheets Sync 최적화 19 DSL (Domain Specific Language)과 GPL (General Purpose Language) 20 마크다운 표 그리는 방법 21 쿼리 파라미터(Query Parameter). 기존 QR코드 재활용 22 Django 보안 취약점 점검 및 수정 23 OOP Object-Oriented Programming 객체 지향 프로그래밍 24 Fernet 대칭 암호화 25 Jekyll 코드블록 안의 Liquid 태그 26 습관만들기 Pawbit TIL 1 27 insertOnConflictUpdate vs DoUpdate(target) 28 세션 필터 29 아코디언(Accordiaon) UI를 펼친상태로 만들기 30 input의 step 31 습관만들기 Pawbit TIL 2 32 습관만들기 Pawbit TIL 3 33 Word Cloud 34 Google Sheets를 데이터 버스로(with AppSheet) 35 Django 모델 텍스트 필드 자동 수집 패턴 36 localStorage로 섹션 토글 상태 유지 37 순차 ID 생성(`select_for_update()` + `max()` 조합) 38 역참조 검색과 distinct() 39 xlsx 다운로드와 로딩 오버레이 충돌 40 Android 파일 공유 MIME 타입 41 AssetManifest — Flutter 빌드 타임 asset 목록 런타임 조회 42 크레센도 Phaser + TypeScript TIL 1 43 크레센도 Phaser + TypeScript TIL 2 44 크레센도 Phaser + TypeScript TIL 3 45 크레센도 Phaser + TypeScript TIL 4 46 크레센도 Phaser + TypeScript TIL 5 47 여둘까 Office Layout TIL 1 48 습관만들기 Pawbit TIL 4 49 여둘까 Office Layout TIL 2 50 여둘까 Office Layout TIL 3 51 크레센도 Phaser + TypeScript TIL 6 52 UTF-8 BOM과 PowerShell 파일 쓰기 53 소리꽃 KeyBloom TIL 1 54 소리꽃 KeyBloom TIL 2 55 소리꽃 KeyBloom TIL 3 56 소리꽃 KeyBloom TIL 4 57 소리꽃 KeyBloom TIL 5 58 습관만들기 Pawbit TIL 5 읽는 중 59 여둘까 Office Layout TIL 4 60 소리꽃 KeyBloom TIL 6 61 소리꽃 KeyBloom TIL 7 62 소리꽃 KeyBloom TIL 8 63 메트로놈 Simple Metronome TIL 1 64 소리꽃 KeyBloom TIL 9 65 메트로놈 Simple Metronome TIL 2 66 소리꽃 KeyBloom TIL 10 67 습관만들기 Pawbit TIL 6 68 크레센도 Phaser + TypeScript TIL 7 69 메트로놈 Simple Metronome TIL 3 70 습관만들기 Pawbit TIL 7 71 정적 블로그 SEO 정비와 Pagefind 검색 도입 72 크레센도 Phaser + TypeScript TIL 8 73 메트로놈 Simple Metronome TIL 4 74 습관만들기 Pawbit TIL 8 75 안드로이드 AudioTrack 연속 재생, 실측 피커 정렬, 카메라 토치 플래시 76 메트로놈 Simple Metronome TIL 5 77 모래게임 Sandrop TIL 1 78 습관만들기 Pawbit TIL 9 79 메트로놈 Simple Metronome TIL 6 80 모래게임 Sandrop TIL 2 81 모래게임 Sandrop TIL 3 82 습관만들기 Pawbit TIL 10 83 습관만들기 Pawbit TIL 11 84 메트로놈 Simple Metronome TIL 7 85 모래게임 Sandrop TIL 4 86 모래게임 Sandrop TIL 5 87 습관만들기 Pawbit TIL 12 88 모래게임 Sandrop TIL 6 89 소리꽃 KeyBloom TIL 11 90 모래게임 Sandrop TIL 7 91 모래게임 Sandrop TIL 8 92 소리꽃 KeyBloom TIL 12 93 모래게임 Sandrop TIL 9 94 소리꽃 KeyBloom TIL 13 95 모래게임 Sandrop TIL 10 96 소리꽃 KeyBloom TIL 14 97 소리꽃 KeyBloom TIL 15 98 소리꽃 KeyBloom TIL 16 99 콘티온 Conti On TIL 1 100 소리꽃 KeyBloom TIL 17 101 콘티온 Conti On TIL 2 102 습관만들기 Pawbit TIL 13 103 콘티온 Conti On TIL 3 104 모래게임 Sandrop TIL 11 105 소리꽃 KeyBloom TIL 18 106 여둘까 Office Layout TIL 5 107 모래게임 Sandrop TIL 12 108 모래게임 Sandrop TIL 13 109 콘티온 Conti On TIL 4 110 모래게임 Sandrop TIL 14 111 소리꽃 KeyBloom TIL 19 112 소리꽃 KeyBloom TIL 20 113 소리꽃 KeyBloom TIL 21 114 소리꽃 KeyBloom TIL 22 115 습관만들기 Pawbit TIL 14 116 모래게임 Sandrop TIL 15 117 모래게임 Sandrop TIL 16 118 소리꽃 KeyBloom TIL 23 119 소리꽃 KeyBloom TIL 24 120 소리꽃 KeyBloom TIL 25 121 소리꽃 KeyBloom TIL 26 122 온실 GreenHouse TIL 1 123 콘티온 Conti On TIL 5 124 모래게임 Sandrop TIL 17 125 소리꽃 KeyBloom TIL 27 126 온실 GreenHouse TIL 2 127 콘티온 Conti On TIL 6 128 모래게임 Sandrop TIL 18 129 콘티온 Conti On TIL 7 130 모래게임 Sandrop TIL 19 131 소리꽃 KeyBloom TIL 28 132 모래게임 Sandrop TIL 20 133 모래게임 Sandrop TIL 21