[TIL] Day 17 InheritedWidget / InheritedModel & ThemeData & Slider & RichText & TextSpan

현서·2025년 12월 16일

[TIL] Flutter 9기

목록 보기
29/65
post-thumbnail

튜터님과 Widget 공부

InheritedWidget / InheritedModel

개념 정리

문제 상황
Flutter는 위젯 트리 구조
부모 → 자식 데이터 전달은 쉬움
조부모 → 깊은 자식 데이터 전달은 불편

A
 └ B
   └ C
     └ D  ← A의 데이터를 쓰고 싶다

생성자에 계속 넘기면 불편

➡️ 이걸 해결하기 위해 나온 게 InheritedWidget

InheritedWidget

개념

위젯 트리를 따라 데이터를 내려보내는 위젯
하위 위젯이 BuildContext로 접근 가능
값이 바뀌면 의존 중인 위젯 rebuild

class MyInheritedWidget extends InheritedWidget {
  final int count;

  const MyInheritedWidget({
    super.key,
    required this.count,
    required Widget child,
  }) : super(child: child);

of 메서드

static MyInheritedWidget of(BuildContext context) {
  return context.dependOnInheritedWidgetOfExactType<MyInheritedWidget>()!;
}

이걸 호출한 위젯은 의존성 등록
값 변경 시 rebuild 대상

updateShouldNotify


bool updateShouldNotify(MyInheritedWidget oldWidget) {
  return count != oldWidget.count;
}

true → 의존 중인 위젯 전부 rebuild
false → 아무도 rebuild 안 됨

사용 예

class CounterText extends StatelessWidget {
  
  Widget build(BuildContext context) {
    final count = MyInheritedWidget.of(context).count;
    return Text('$count');
  }
}

문제점

값이 여러 개일 때
하나만 바뀌어도 전부 rebuild

InheritedModel

개념

InheritedWidget의 확장
여러 의존성(aspect)을 분리
필요한 부분만 rebuild

class MyModel extends InheritedModel<MyAspect> { ... }

Aspect란?

rebuild 단위
보통 enum 사용

enum MyAspect { color, size }

기본 구조

class MyInheritedModel extends InheritedModel<MyAspect> {
  final Color color;
  final double size;

  const MyInheritedModel({
    super.key,
    required this.color,
    required this.size,
    required Widget child,
  }) : super(child: child);

updateShouldNotify

전체 변경 여부


bool updateShouldNotify(MyInheritedModel oldWidget) {
  return color != oldWidget.color || size != oldWidget.size;
}

updateShouldNotifyDependent


bool updateShouldNotifyDependent(
  MyInheritedModel oldWidget,
  Set<MyAspect> dependencies,
) {
  if (dependencies.contains(MyAspect.color) &&
      color != oldWidget.color) {
    return true;
  }

  if (dependencies.contains(MyAspect.size) &&
      size != oldWidget.size) {
    return true;
  }

  return false;
}

aspect별로 rebuild 판단

구독 방법

InheritedModel.inheritFrom<MyInheritedModel>(
  context,
  aspect: MyAspect.color,
);

InheritedWidget vs InheritedModel

항목InheritedWidgetInheritedModel
의존성 단위하나여러 개
rebuild전체부분

linter-rules
팀원들끼리 코드 규칙 정할 때


📝 Flutter ThemeData

ThemeData는 앱 전체의 색상·글자·버튼 스타일을 한 번에 관리하기 위한 설정 객체
각 위젯에 일일이 스타일을 주지 않기 위해 사용

✏️ ThemeData를 쓰는 이유

버튼마다 color, style를 직접 주면 코드가 길어짐
페이지가 많아질수록 디자인 수정이 어려워짐
그래서 MaterialApp 단계에서 ThemeData를 한 번만 설정

👉 “앱 전체의 기본값을 정해두고, 위젯들은 그 기본값을 따라가게 만든다”는 흐름

✏️ ThemeData 위치

main.dart → MaterialApp → theme 구조로 사용

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  
  Widget build(BuildContext context) {
    return MaterialApp(
      theme: ThemeData(
        primaryColor: Colors.blue,
      ),
      home: const HomePage(),
    );
  }
}

ThemeData는 항상 MaterialApp 안에 들어감

✏️ 자주 쓰는 ThemeData 속성

1️⃣ primaryColor

theme: ThemeData(
  primaryColor: Colors.orange,
),

앱의 대표 색상
AppBar, 버튼 등에 기본으로 적용됨

2️⃣ scaffoldBackgroundColor

theme: ThemeData(
  scaffoldBackgroundColor: Colors.grey[100],
),

모든 Scaffold의 배경색
페이지마다 배경색 안 써도 됨

3️⃣ textTheme

theme: ThemeData(
  textTheme: const TextTheme(
    bodyMedium: TextStyle(fontSize: 16),
  ),
),

Text 위젯의 기본 글자 스타일
TextStyle을 매번 쓰지 않아도 됨

4️⃣ dividerColor

ThemeData(
  dividerColor: Colors.grey, // 앱 전체 Divider 색 지정
)

Divider(
  color: Theme.of(context).dividerColor, // theme에서 가져와서 적용 가능
  thickness: 2,
)

Flutter에서 구분선(Divider) 색상을 지정하는 속성
Divider() 위젯이나 리스트 항목 사이의 선, ListTile 구분선 같은 곳에서 사용
기본적으로 ThemeData 안에서 정의할 수 있어서 앱 전체 Divider 색을 통일할 수 있음

📝 Slider

✏️ Slider란?

사용자가 값을 드래그해서 선택할 수 있는 위젯
주로 숫자 값 범위를 선택할 때 사용
방향은 기본적으로 수평
최소값(min)과 최대값(max) 사이의 값을 선택

✏️ 기본 속성

속성타입설명
valuedouble현재 슬라이더 값
onChanged ValueChanged<double>값이 바뀔 때 호출되는 콜백
mindouble최소값 (기본 0.0)
maxdouble최대값 (기본 1.0)
divisionsint?슬라이더를 나눌 구간 수, null이면 연속값
labelString?드래그할 때 보여줄 값 표시
activeColorColor?선택된 영역 색
inactiveColorColor?선택되지 않은 영역 색
thumbColorColor?슬라이더 원(핸들) 색
onChangeStartValueChanged<double>?드래그 시작 시 호출
onChangeEndValueChanged<double>?드래그 끝났을 때 호출

✏️ 사용 예시

double _sliderValue = 50;

Slider(
  value: _sliderValue,
  min: 0,
  max: 100,
  divisions: 10, // 10단계로 나누기
  label: _sliderValue.round().toString(),
  onChanged: (double value) {
    setState(() {
      _sliderValue = value;
    });
  },
)

_sliderValue를 상태로 관리해야 실시간 값 갱신 가능
divisions를 사용하면 슬라이더가 계단식(step)으로 움직임
label을 설정하면 슬라이더 위에 값 표시

✏️ SliderTheme 사용

슬라이더를 더 세부적으로 꾸미고 싶으면 SliderTheme 사용 가능

SliderTheme(
  data: SliderTheme.of(context).copyWith(
    activeTrackColor: Colors.orange,
    inactiveTrackColor: Colors.grey,
    thumbColor: Colors.red,
    overlayColor: Colors.red.withAlpha(32),
    valueIndicatorColor: Colors.blue,
    trackHeight: 6,
  ),
  child: Slider(
    value: _sliderValue,
    min: 0,
    max: 100,
    divisions: 10,
    label: _sliderValue.round().toString(),
    onChanged: (value) {
      setState(() {
        _sliderValue = value;
      });
    },
  ),
)

trackHeight → 슬라이더 선 두께
overlayColor → 드래그 시 원 주변 색
valueIndicatorColor → 값 표시 배경 색

✏️ 주의사항

  • value는 항상 min과 max 사이여야 함
  • onChanged가 null이면 슬라이더 비활성화(disabled)
  • divisions가 null이면 연속 슬라이더, 숫자를 주면 단계별 슬라이더

📝 RichText & TextSpan

✏️ RichText란?

여러 스타일을 섞어 한 줄에 표현할 때 사용
Row + Text 3개로 나눠서 구현할 수도 있지만, 글자별 스타일이 다를 경우 RichText가 더 편리

  • 예시: Row 사용 시
Row(
  children: [
    Text('Result ', style: TextStyle(fontSize: 18)),
    Text('Normal', style: TextStyle(fontSize: 18, color: Colors.pink)),
    Text('(BMI 10-30)', style: TextStyle(fontSize: 18)),
  ],
);

예시: RichText 사용 시

RichText(
  text: TextSpan(
    style: TextStyle(fontSize: 18),
    children: [
      TextSpan(text: 'Result: '),
      TextSpan(
        text: 'Normal',
        style: TextStyle(
          fontWeight: FontWeight.bold,
          color: Theme.of(context).highlightColor,
        ),
      ),
      TextSpan(text: ' (BMI 10-30)'),
    ],
  ),
)

✏️ TextSpan란?

TextSpan 클래스는 스타일이 적용된 텍스트 단위
위젯은 아니고, RichText 안에서만 사용
TextStyle 적용 가능
자식 TextSpan도 가질 수 있음 (중첩 가능)

Text.rich 단축 표현

RichText 대신 Text.rich를 사용하면 더 간단하게 작성 가능

Text.rich(
  TextSpan(
    style: TextStyle(fontSize: 18),
    children: [
      TextSpan(text: 'Result: '),
      TextSpan(
        text: 'Normal',
        style: TextStyle(
          fontWeight: FontWeight.bold,
          color: Theme.of(context).highlightColor,
        ),
      ),
      TextSpan(text: ' (BMI 10-30)'),
    ],
  ),
)

✏️ 정리

RichText → 글자별 스타일이 다를 때 사용
TextSpan → 스타일 적용 단위, 위젯 아님
Text.rich → RichText 단축 표현
스타일을 자식에게 상속 가능, 중첩도 가능
Row + Text보다 코드가 간결하고 유지보수가 쉬움


BMI 계산기까지 만들어봤다룡

공부 소감

오늘 목표한 강의 완강까지 완료하고 강의 실습까지 했다룡. 이제 내일부터 과제 시작하고 기본 기능까지 이번주에 완료하는 게 목표다. 빨리 과제 끝내구 위젯 공부 더 해야지~

0개의 댓글