> Frontend/Flutter

Flutter_14. Theme in Widget & Dark Mode & Named Constructor & For in

Janku 2023. 5. 12. 20:25

 

 

 

0) 학습 내용

  1.  main.dart에서 작성된 Theme 을 widget에서 사용
  2.  Dark Mode
  3.  Constructor ( named Constructor)
  4.  For in

 

1) main.dart에서 작성된 Theme 을 widget에서 사용

 

... 생략
theme: ThemeData().copyWith(

... 생략

 

  • main.dart에서 만들어진 theme 을 widget에서 사용해보기 (expenses_list.dart에서)

 

return Dismissible(
  key: ValueKey(expenses[index]), //create key obj
  background: Container(color: Theme.of(context).colorScheme.error.withOpacity(0.3), // setting up background color
  margin: EdgeInsets.symmetric(horizontal: Theme.of(context).cardTheme.margin!.horizontal),
  ),
  child: ExpenseItem(expense: expenses[index]),
  onDismissed: (direction) {

 

  • main.dart에서 사용된 Theme of(context로 받아와), 해당 theme에 설정된 colorScheme 내에 error 색상으로 설정
  • 동일하게,  설정된 cardTheme에서 설정된 margin값 가져와서 사용 
cardTheme: const CardTheme().copyWith(
    color: kColorScheme.secondaryContainer,
    margin: const EdgeInsets.symmetric(horizontal: 16, vertical: 8)),

 

  • cardTheeme 내 margin의 horizontal은 16으로 설정되어 있음. 

 

2)  Dark Mode

 

import 'package:flutter/material.dart';
import 'package:flutter06_expense/widgets/expenses.dart';

// generates a color scheme based on a seed color
var kColorScheme = ColorScheme.fromSeed(
  seedColor: const Color.fromARGB(255, 96, 59, 181),
);

var kDarkColorScheme = ColorScheme.fromSeed(
  brightness: Brightness.dark,
  // need to set Brightness => otherwise, bright will be white
  seedColor: const Color.fromARGB(255, 5, 99, 125),
);

void main() {
  runApp(
    MaterialApp(
      // copyWith: uses default useMaterial3 setting, no need to start from scratch
      //   the theme of the MaterialApp by copying the default theme using ThemeData().copyWith()
      darkTheme: ThemeData.dark().copyWith(
        useMaterial3: true,
        colorScheme: kDarkColorScheme,
        cardTheme: const CardTheme().copyWith(
            color: kDarkColorScheme.secondaryContainer,
            margin: const EdgeInsets.symmetric(horizontal: 16, vertical: 8)),
        elevatedButtonTheme: ElevatedButtonThemeData(
          style: ElevatedButton.styleFrom(
              backgroundColor: kDarkColorScheme.primaryContainer,
              // primary container is the background color
              foregroundColor: kDarkColorScheme
                  .onPrimaryContainer), //colors the content inside of that button: onPrimaryContainer
        ),
      ),
      theme: ThemeData().copyWith(
          useMaterial3: true,
          // add more settings below
          // 1. colorScheme
          colorScheme: kColorScheme,
          // 2. app-bar theme
          appBarTheme: const AppBarTheme().copyWith(
              backgroundColor: kColorScheme.onPrimaryContainer,
              // this foregroundColor will overwrite other foreground theme
              foregroundColor: kColorScheme.primaryContainer),
          cardTheme: const CardTheme().copyWith(
              color: kColorScheme.secondaryContainer,
              margin: const EdgeInsets.symmetric(horizontal: 16, vertical: 8)),
          elevatedButtonTheme: ElevatedButtonThemeData(
            style: ElevatedButton.styleFrom(
                backgroundColor: kColorScheme.primaryContainer),
          ),
          textTheme: ThemeData().textTheme.copyWith(
                titleLarge: TextStyle(
                  fontWeight: FontWeight.w900,
                  color: kColorScheme.onSecondaryContainer,
                  fontSize: 20,
                ),
              )),
      themeMode: ThemeMode.light,
      // change material 2 to material3
      home: const Expenses(),
    ),
  );
}

 

  • main.dart.에 kDarkColorScheme을 설정해준다.
  • 기존에 작성된 theme 위에 darkTheme 을 설정하고, 해당 theme에서 사용할 Theme을 설정한다. 
  • 이때, colorScheme: kDarkColorScheme으로 설정하여,  fromSeed 를 통해 설정한 기본 color set으로 설정
themeMode: ThemeMode.light,

 

  • 이후,  아래 쪽에 ThemeMode.system으로 설정하여, 기기의 설정에 따라 다르게 색상이 보여지도록 설정 

DarkMode 적용 시,

  •  위의 사진과 같이 dark mode  가 적용된다. 

 

 

3) Constructor ( named Constructor)

 

  • 인스턴스가 생성될 때, 호출되는 초기화 메소드
  • 기본 생성자는  클래스의 이름과 같으며, 리턴값이 없고, 자동 생성된다.
  • params를 전달해줄때는 따로 생성자를 생겅해야한다.
  • Named Constructors를 사용하면, 하나의 클래스에 다양한 종류의 생성자를 생성할 수 있다. 
  • 클래스의 생성자 실행 순서로 초기화 목록 -> 상위 클래스의 기본 생성자  -> 하위 클래스의 기본 생성자.
  • 초기화 목록(Initializer List)를 사용하면, 생성자 실행전에 변수 초기화 가능하고, 생성자 옆에 : 으로 시작하면 , 으로 구분

 

class ExpenseBucket {
  const ExpenseBucket({required this.category, required this.expenses});

  // adding my own alternative names constructor
  // in order to filter out the expenses that belong to specific category
  // need initializer
  ExpenseBucket.forCategory(List<Expense> allExpenses, this.category)
      : expenses = allExpenses.where((expense) => expense.category == category).toList();

  final Category category;
  final List<Expense> expenses;

  // adding getter
  double get totalExpenses {
    double sum = 0;

    for (final expense in expenses) {
      sum += expense.amount;
    }

    return sum;
  }
}

 

  • expense.dart에서 ExpenseBucket을 생성
  • 해당 class 에서, forCategory라는 named constructor를 사용했는데,  초기화 목록을 사용하여, 우선순위 최상위에 넣어준다. 
  • 이 constructor는 받은 allExpenses 라는 값을 iterate하면서, 받은 category가 해당 파일에 생성된 expense.category 와 동일한지 확인하고, 동일한 경우, 아래 생성된 expenses에 넣어준다.

 

 

4)  Code 추가 및 for in 

import 'package:flutter/material.dart';

import 'package:flutter06_expense/widgets/chart/chart_bar.dart';
import 'package:flutter06_expense/models/expense.dart';

class Chart extends StatelessWidget {
  const Chart({super.key, required this.expenses});

  final List<Expense> expenses;

  List<ExpenseBucket> get buckets { // helper getters
    return [
      ExpenseBucket.forCategory(expenses, Category.food),
      ExpenseBucket.forCategory(expenses, Category.leisure),
      ExpenseBucket.forCategory(expenses, Category.travel),
      ExpenseBucket.forCategory(expenses, Category.work),
    ];
  }

  double get maxTotalExpense {
    double maxTotalExpense = 0;

    for (final bucket in buckets) {
      if (bucket.totalExpenses > maxTotalExpense) {
        maxTotalExpense = bucket.totalExpenses;
      }
    }

    return maxTotalExpense;
  }

  @override
  Widget build(BuildContext context) {
    final isDarkMode =
        MediaQuery.of(context).platformBrightness == Brightness.dark;
    return Container(
      margin: const EdgeInsets.all(16),
      padding: const EdgeInsets.symmetric(
        vertical: 16,
        horizontal: 8,
      ),
      width: double.infinity,
      height: 180,
      decoration: BoxDecoration(
        borderRadius: BorderRadius.circular(8),
        gradient: LinearGradient(
          colors: [
            Theme.of(context).colorScheme.primary.withOpacity(0.3),
            Theme.of(context).colorScheme.primary.withOpacity(0.0)
          ],
          begin: Alignment.bottomCenter,
          end: Alignment.topCenter,
        ),
      ),
      child: Column(
        children: [
          Expanded(
            child: Row(
              crossAxisAlignment: CrossAxisAlignment.end,
              children: [
                for (final bucket in buckets) // alternative to map()
                  ChartBar(
                    fill: bucket.totalExpenses == 0
                        ? 0
                        : bucket.totalExpenses / maxTotalExpense,
                  )
              ],
            ),
          ),
          const SizedBox(height: 12),
          Row(
            children: buckets
                .map(
                  (bucket) => Expanded(
                child: Padding(
                  padding: const EdgeInsets.symmetric(horizontal: 4),
                  child: Icon(
                    categoryIcons[bucket.category],
                    color: isDarkMode
                        ? Theme.of(context).colorScheme.secondary
                        : Theme.of(context)
                        .colorScheme
                        .primary
                        .withOpacity(0.7),
                  ),
                ),
              ),
            )
                .toList(),
          )
        ],
      ),
    );
  }
}

 

  • chart.dart

 

import 'package:flutter/material.dart';

class ChartBar extends StatelessWidget {
  const ChartBar({
    super.key,
    required this.fill,
  });

  final double fill;

  @override
  Widget build(BuildContext context) {
    final isDarkMode =
        // media query:: get environment information of which your app is running
        MediaQuery.of(context).platformBrightness == Brightness.dark;
    return Expanded(
      child: Padding(
        padding: const EdgeInsets.symmetric(horizontal: 4),
        child: FractionallySizedBox(
          heightFactor: fill,
          child: DecoratedBox(
            decoration: BoxDecoration(
              shape: BoxShape.rectangle,
              borderRadius:
                  const BorderRadius.vertical(top: Radius.circular(8)),
              color: isDarkMode
                  ? Theme.of(context).colorScheme.secondary
                  : Theme.of(context).colorScheme.primary.withOpacity(0.65),
            ),
          ),
        ),
      ),
    );
  }
}
  • chart_bar.dart
children: [
  for (final bucket in buckets) // alternative to map()
    ChartBar(
      fill: bucket.totalExpenses == 0
          ? 0
          : bucket.totalExpenses / maxTotalExpense,
    )
],

 

  • for final expense in expenses, : 받아온 expenses에서 하나씩 iterate해서 나온 값을  기준으로 ChartBar에 보내준다. 

 

List<ExpenseBucket> get buckets { // helper getters
  return [
    ExpenseBucket.forCategory(expenses, Category.food),
    ExpenseBucket.forCategory(expenses, Category.leisure),
    ExpenseBucket.forCategory(expenses, Category.travel),
    ExpenseBucket.forCategory(expenses, Category.work),
  ];
}

 

 

  • ExpenseBucket 에서 만들어진 named constructor 인 forCategory에 expenses 와 Category. * 을 전달