반응형

 

MySQL 사용시 테이블 대소문자 구분하지 않고 사용하기

 

Server version: 5.7.13

 

MySQL 설정 확인

mysql> show variables like 'lower%';
+------------------------+-------+
| Variable_name          | Value |
+------------------------+-------+
| lower_case_file_system | OFF   |
| lower_case_table_names | 0     |
+------------------------+-------+

 

lower_case_table_names

 - 1 : 대소문자 구분하지 않고 사용.

 - 0 : 대소문자 구분하여  사용.

 

 

MySQL 설정 변경

- read ony 값이라 set 명령어로 변경할 수 없음.

mysql> set lower_case_table_names=1;
ERROR 1238 (HY000): Variable 'lower_case_table_names' is a read only variable

 

- my.cnf 파일 수정 후 재구동

$ vi /etc/my.cnf

[mysqld]
...
lower_case_table_names = 1
...

 

반응형
반응형

java 소수점 처리

 - 데이터 타입이 float 이나 double 이어야 합니다.

 

ex) 나누기 연산을 해서 소수 둘째자리까지 출력하는 방법.

1. String.format

 - 반올림 된 값으로 결과값 출력.

 - 소수점 자릿수 지정(고정)

private static void div(int mb) {
        double gb = (double)mb / 1024;
        System.out.println("\n---------------------------------");
        System.out.println("" + mb + " / 1024 = " + gb);

        System.out.println("String.format = " + String.format("%.2f", gb));
}

div(1024);
div(1540);
div(1550);
div(1400);


결과 :
---------------------------------
1024 / 1024 = 1.0
String.format = 1.00

---------------------------------
1540 / 1024 = 1.50390625
String.format = 1.50

---------------------------------
1550 / 1024 = 1.513671875
String.format = 1.51

---------------------------------
1400 / 1024 = 1.3671875
String.format = 1.37

 

2. BigDecimal

 - 반올림/올림/내림 설정 가능.

 - 소수점 자릿수 지정(고정 / 마지막이 0 인 경우 버림 가능)

private static void div(int mb) {
        double gb = (double)mb / 1024;
        System.out.println("\n---------------------------------");
        System.out.println("" + mb + " / 1024 = " + gb);

        BigDecimal bdMb = new BigDecimal(mb);
        BigDecimal bdKb = new BigDecimal(1024);
        System.out.println("BigDecimal = " + bdMb.divide(bdKb, 2, RoundingMode.DOWN));
        System.out.println("BigDecimal(stripTrailingZeros) = " + bdMb.divide(bdKb, 2, RoundingMode.DOWN).stripTrailingZeros());
}
    
div(1024);
div(1540);
div(1550);
div(1400);


---------------------------------
1024 / 1024 = 1.0
BigDecimal = 1.00
BigDecimal(stripTrailingZeros) = 1

---------------------------------
1540 / 1024 = 1.50390625
BigDecimal = 1.50
BigDecimal(stripTrailingZeros) = 1.5

---------------------------------
1550 / 1024 = 1.513671875
BigDecimal = 1.51
BigDecimal(stripTrailingZeros) = 1.51

---------------------------------
1400 / 1024 = 1.3671875
BigDecimal = 1.36
BigDecimal(stripTrailingZeros) = 1.36

 

3. DecimalFormat

 - 반올림 된 값으로 결과값 출력.

 - 소수점 자릿수 지정(고정 / 마지막이 0 인 경우 버림 가능 / 0 버림 갯수 지정 가능)

private static void div(int mb) {
        double gb = (double)mb / 1024;
        System.out.println("\n---------------------------------");
        System.out.println("" + mb + " / 1024 = " + gb);

        DecimalFormat formatter = new DecimalFormat("0.##");
        System.out.println("DecimalFormat(0.##) = " + formatter.format(gb));

        DecimalFormat formatter2 = new DecimalFormat("0.0#");
        System.out.println("DecimalFormat(0.0#) = " + formatter2.format(gb));
}

div(1024);
div(1540);
div(1550);
div(1400);


결과 :
---------------------------------
1024 / 1024 = 1.0
DecimalFormat(0.##) = 1
DecimalFormat(0.0#) = 1.0

---------------------------------
1540 / 1024 = 1.50390625
DecimalFormat(0.##) = 1.5
DecimalFormat(0.0#) = 1.5

---------------------------------
1550 / 1024 = 1.513671875
DecimalFormat(0.##) = 1.51
DecimalFormat(0.0#) = 1.51

---------------------------------
1400 / 1024 = 1.3671875
DecimalFormat(0.##) = 1.37
DecimalFormat(0.0#) = 1.37

 

4. DecimalFormat + BigDecimal

 - 반올림 된 값으로 결과값 출력.

 - 소수점 자릿수 지정(고정 / 마지막이 0 인 경우 버림 가능 / 0 버림 갯수 지정 가능)

private static void div(int mb) {
        double gb = (double)mb / 1024;
        System.out.println("\n---------------------------------");
        System.out.println("" + mb + " / 1024 = " + gb);

        DecimalFormat formatter = new DecimalFormat("0.##");
        DecimalFormat formatter2 = new DecimalFormat("0.0#");
        System.out.println("DecimalFormat(BigDecimal, 0.##) = " + formatter.format(bdMb.divide(bdKb, 2, RoundingMode.DOWN)));
        System.out.println("DecimalFormat(BigDecimal, 0.0#) = " + formatter2.format(bdMb.divide(bdKb, 2, RoundingMode.DOWN)));
}
    
div(1024);
div(1540);
div(1550);
div(1400);
        

결과 :
---------------------------------
1024 / 1024 = 1.0
DecimalFormat(BigDecimal, 0.##) = 1
DecimalFormat(BigDecimal, 0.0#) = 1.0

---------------------------------
1540 / 1024 = 1.50390625
DecimalFormat(BigDecimal, 0.##) = 1.5
DecimalFormat(BigDecimal, 0.0#) = 1.5

---------------------------------
1550 / 1024 = 1.513671875
DecimalFormat(BigDecimal, 0.##) = 1.51
DecimalFormat(BigDecimal, 0.0#) = 1.51

---------------------------------
1400 / 1024 = 1.3671875
DecimalFormat(BigDecimal, 0.##) = 1.36
DecimalFormat(BigDecimal, 0.0#) = 1.36

 

반응형
반응형

 

MySQL 사용자 생성 시 에러가 발생했습니다.

mysql> create user 'user1'@'%' identified by 'passwd1';
ERROR 1819 (HY000): Your password does not satisfy the current policy requirements

 

패스워드가 정책 요구사항에 맞지 않다는 얘기입니다.

 

 

그럼 현재 정책이 어떤지 확인을 해보겠습니다.

mysql> show variables like 'validate_password%';
+--------------------------------------+--------+
| Variable_name                        | Value  |
+--------------------------------------+--------+
| validate_password_check_user_name    | OFF    |
| validate_password_dictionary_file    |        |
| validate_password_length             | 8      |
| validate_password_mixed_case_count   | 1      |
| validate_password_number_count       | 1      |
| validate_password_policy             | MEDIUM |
| validate_password_special_char_count | 1      |
+--------------------------------------+--------+
7 rows in set (0.01 sec)

 

패스워드 정책이 MEDIUM 으로 설정되어 있는 것을 확인할 수 있습니다.

| validate_password_policy             | MEDIUM |

 

 

※ 패스워드 정책은 MySQL 문서에 따르면 이렇게 정의되어 있네요.

 

 

해결책

1. 현재 설정되어 있는 정책에 맞게 패스워드를 만든다.

mysql> create user 'user1'@'%' identified by '유저1!Pw처럼';

2. 현재 설정되어 있는 정책을 바꾼다.

# MySQL 8
mysql> set global validate_password.policy=LOW;
Query OK, 0 rows affected (0.00 sec)

# MySQL 5
mysql> set global validate_password_policy=LOW;
Query OK, 0 rows affected (0.00 sec)

 

 

반응형

'Database > Mysql' 카테고리의 다른 글

MySQL 테이블 대소문자 구분  (0) 2024.03.08
index 생성 기준?  (0) 2021.09.24
[API] 앱 버전 체크 시 사용할 만한 쿼리  (1) 2017.10.11
mysql 에서 unix time 구하기  (0) 2015.09.23
mysql bin log 삭제.  (0) 2014.05.15
반응형

 

빗버킷에 저장소(repository) 생성 후 로컬 PC 에서 git 처음 사용시 에러 발생.

$ git push -u origin master
error: src refspec master does not match any
error: failed to push some refs to 'http://git.com/test.git'

 

아마도 remote 의 master 브랜치를 인식하지 못하는 것 같다.

$ git branch -m master

 

위 처럼 master 브랜치를 설정 한 후 push 하면 잘된다.

$ git push -u origin master
Enumerating objects: 3, done.
Counting objects: 100% (3/3), done.
Writing objects: 100% (3/3), 209 bytes | 209.00 KiB/s, done.
Total 3 (delta 0), reused 0 (delta 0), pack-reused 0
To http://git.com:7990/test.git
 * [new branch]      master -> master
branch 'master' set up to track 'origin/master'.

 

반응형

'프로그래밍' 카테고리의 다른 글

[이클립스] Plugin execution not covered by lifecycle configuration 오류  (0) 2021.03.11
[JSTL] 조건식 eq, ne, empty  (0) 2016.12.07
인코딩  (0) 2014.03.24
[svn] 사용자 추가  (2) 2011.02.15
반응형

엑셀 파일 읽기 시 아래 에러 발생시 해결 방법입니다.

 

라이브러리 추가

apache poi 라이브러리를 추가합니다.

		<dependency>
			<groupId>org.apache.poi</groupId>
			<artifactId>poi</artifactId>
			<version>5.2.3</version>
		</dependency>

		<dependency>
			<groupId>org.apache.poi</groupId>
			<artifactId>poi-ooxml</artifactId>
			<version>5.2.3</version>
		</dependency>

 

엑셀 파일 읽기 소스 코드

public static void readExcel() {
		try {
            FileInputStream file = new FileInputStream("file_path.xlsx");
            IOUtils.setByteArrayMaxOverride(Integer.MAX_VALUE);

            //Create Workbook instance holding reference to .xlsx file
            XSSFWorkbook workbook = new XSSFWorkbook(file);

            //Get first/desired sheet from the workbook
            XSSFSheet sheet = workbook.getSheetAt(0);

            //Iterate through each rows one by one
            Iterator<Row> rowIterator = sheet.iterator();
            while (rowIterator.hasNext()) {
                Row row = rowIterator.next();
                //For each row, iterate through all the columns
                Iterator<Cell> cellIterator = row.cellIterator();

                while (cellIterator.hasNext()) {
                    Cell cell = cellIterator.next();
                    //Check the cell type and format accordingly
                    switch (cell.getCellType()) {
                        case NUMERIC:
                            System.out.print(cell.getNumericCellValue() + "\t");
                            break;

                        case STRING:
                            System.out.print(cell.getStringCellValue() + "\t");
                            break;

                        case BLANK :
                        	System.out.print("\t");
                            break;

                        default:
                            throw new IllegalStateException("Unexpected value: " + cell.getCellType());
                    }
                }
                System.out.println("");
            }
            file.close();
        } catch (Exception e) {
            e.printStackTrace();
        }
	}

 

에러 발생

 

Exception in thread "main" java.lang.NoSuchMethodError: org.apache.logging.log4j.Logger.atDebug()Lorg/apache/logging/log4j/LogBuilder;

 

 

 

해결 방법

		<!-- https://mvnrepository.com/artifact/org.apache.logging.log4j/log4j-to-slf4j -->
		<dependency>
			<groupId>org.apache.logging.log4j</groupId>
			<artifactId>log4j-to-slf4j</artifactId>
			<version>2.17.2</version>
		</dependency>

		<!-- https://mvnrepository.com/artifact/org.apache.logging.log4j/log4j-api -->
		<dependency>
			<groupId>org.apache.logging.log4j</groupId>
			<artifactId>log4j-api</artifactId>
			<version>2.17.2</version>
		</dependency>

 

반응형

'프로그래밍 > Java' 카테고리의 다른 글

java 소수점 처리  (1) 2023.10.10
Java Stream 설명  (0) 2023.05.26
Stream reduce 간단 설명  (0) 2023.05.20
java file.encoding  (0) 2022.07.12
[JAVA] java.lang.UnsatisfiedLinkError: no net in java.library.path  (1) 2021.06.25
반응형

 

Spring Boot 사용시 logback 설정에 대한 우선 순위를 알아봅니다.

 

 

Spring Properties 파일에 정의

 - application.properties

logging.level.org.springframework.boot.autoconfigure=DEBUG

 

 

Jar 실행 시 환경 변수로 정의

 - logback.xml

<logger name="org.springframework.boot.autoconfigure"><level value="INFO" /></logger>

 

우선 순위

 - Spring Properties 에서 설정한 내용이 우선 순위가 높습니다.

 - 그러나 logback.xml 에서는 실시간 변경등 작업을 할 수 있습니다.

 

 

반응형

'프로그래밍 > Spring' 카테고리의 다른 글

Spring Properties 파일 로딩 우선 순위  (1) 2023.01.04
반응형

Java Stream

 

Java Stream 특징

- Java 8부터 도입된 기능으로, 컬렉션, 배열, 파일 등의 데이터 요소를 처리하는 연산을 지원하는 함수형 Stream 입니다.

- 데이터 소스로부터 연속된 데이터 흐름을 제공하여 데이터를 처리합니다.

- 데이터를 효율적으로 처리할 수 있으며, 병렬 처리를 포함한 다양한 연산을 적용할 수 있습니다. 

- 데이터를 필터링, 매핑, 정렬, 그룹핑 등 다양한 작업을 수행할 수 있으며, 함수형 프로그래밍 스타일을 지원하여 코드를 간결하고 가독성 있게 작성할 수 있습니다.

- 중간 연산과 최종 연산으로 구성된 파이프라인을 만들 수 있습니다. 중간 연산은 다른 Stream 을 반환하며, 최종 연산은 최종 결과를 반환합니다.

 

Java Stream 처리 단계

1. 데이터 소스 생성

 - 컬렉션, 배열, 파일 등의 데이터 소스로부터 Stream 을 생성합니다.

List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
Stream<Integer> stream = numbers.stream();

 

2. 중간 연산

 - 필터링, 매핑, 정렬 등의 작업을 수행할 수 있습니다.

stream = stream.filter(n -> n % 2 == 0) // 짝수만 필터링
               .map(n -> n * 2)         // 각 요소를 2배로 매핑
               .sorted();               // 정렬

 

3. 최종 연산

 - 리스트, 배열, 요소 개수 등 다양한 결과를 얻을 수 있습니다.

// 요소 개수 세기(count)
long count = numbers.stream()
                   .count();
System.out.println(count);  // 출력: 5

// 요소들의 합 구하기(sum)
int sum = numbers.stream()
                .mapToInt(Integer::intValue)
                .sum();
System.out.println(sum);  // 출력: 15


// 최대값 찾기(max)
Optional<Integer> max = numbers.stream()
                               .max(Comparator.naturalOrder());
System.out.println(max.orElse(0));  // 출력: 5


// 요소들의 평균 구하기(average)
OptionalDouble average = numbers.stream()
                               .mapToInt(Integer::intValue)
                               .average();
System.out.println(average.orElse(0));  // 출력: 3.0


// 요소들을 리스트로 변환(collect)
List<Integer> doubledNumbers = numbers.stream()
                                      .map(n -> n * 2)
                                      .collect(Collectors.toList());
System.out.println(doubledNumbers);  // 출력: [2, 4, 6, 8, 10]

※ 이외에도 최소값 찾기(min), 문자열로 연결하기(join), 그룹화하기(groupingBy) 등 다양한 최종 연산이 있습니다.

 

Java Stream VS Parallel Stream

구분 Stream Parallel Stream
데이터 처리 방식 순차적으로 연산을 수행 병렬로 연산을 수행
처리 속도 단일 CPU 코어에서 동작하며, 작업을 직렬로 처리.  멀티코어 시스템에서 데이터 처리 속도를 높일 수 있음.
작업을 분할하고 스레드 간의 동기화 오버헤드가 발생할 수 있으므로, 작업량이 많거나 데이터가 충분히 큰 경우에 이점.
스레드 안전성 - 멀티 스레드에서 작업을 수행하므로 스레드 안전성에 주의.
thread-safe 한 자료구조를 사용하거나 동기화 메커니즘을 적절하게 사용
순서 보장 연산 순서가 보장되며, 요소 순서 유지 병렬로 처리하기 때문에 요소의 순서가 보장되지 않을 수 있음. 
사용처 데이터의 순서가 중요하거나 상호작용이 필요한 경우 유용 멀티코어 CPU 시스템에서 병렬 처리에 특히 유용하며, 대량의 데이터를 동시에 처리해야 할 때 성능 향상을 제공

 

소스로 비교

public static void main(String[] args) {
    // 데이터 소스
    int[] numbers = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};

    // stream
    int sumSequential = Arrays.stream(numbers).sum();
    System.out.println("Sum (Sequential): " + sumSequential);

    // parallel stream
    int sumParallel = Arrays.stream(numbers)
        .parallel()
        .sum();
    System.out.println("Sum (Parallel): " + sumParallel);
}
    
// 결과 출력
Sum (Sequential): 55
Sum (Parallel): 55

stream : Arrays.stream(numbers)를 호출하여 스트림을 생성하고, sum() 메서드를 호출하여 모든 요소의 합을 계산

parallel stream : Arrays.stream(numbers)를 호출하여 스트림을 생성한 후 .parallel() 메서드를 호출하여 병렬 스트림으로 전환, 그리고 나서 sum() 메서드를 호출하여 병렬로 모든 요소의 합을 계산

※  합계를 구하는 간단한 작업. 작은 데이터셋이므로 parallel stream 을 사용하여도 순차 stream 과 동일한 결과.

 

parallel stream 잘못된 사용 예제

public static void main(String[] args) {
    // 데이터 소스
    int[] numbers = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};

    // 잘못된 사용 예제
    int sum = Arrays.stream(numbers)
        .parallel()
        .map(n -> {
            if (n == 5) {
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            return n;
        })
        .sum();
        
    System.out.println("Sum: " + sum);
}

// 결과 출력
Sum: 50

 - 배열 numbers를 데이터 소스로 사용하여 parallel stream을 이용하여 요소들을 처리하고 합계(sum)를 계산

- parallel stream 내부의 map 연산에서 특정 조건(n == 5)에 따라 5인 경우 1초의 지연을 발생

- parallel stream은 요소를 병렬로 처리하기 때문에 map 연산의 각 요소는 병렬로 실행

- 조건 n == 5를 만족하는 요소가 존재할 경우 1초의 지연이 발생하고, 이로 인해 전체 연산에 대한 지연이 발생

 

parallel stream 처리 순서 확인

-public static void main(String[] args) {
    // 데이터 소스
    int[] numbers = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};

    Arrays.stream(numbers)
        .parallel()
        .map(n -> {
            System.out.println("Map: " + n + " - " + Thread.currentThread().getName());
            return n;
        })
        .forEach(n -> System.out.println("ForEach: " + n + " - " + Thread.currentThread().getName()));
-}

// 결과 출력
Map: 1 - main
Map: 2 - ForkJoinPool.commonPool-worker-1
Map: 5 - ForkJoinPool.commonPool-worker-2
Map: 4 - ForkJoinPool.commonPool-worker-3
Map: 3 - ForkJoinPool.commonPool-worker-4
Map: 6 - main
ForEach: 6 - main
ForEach: 1 - main
ForEach: 2 - ForkJoinPool.commonPool-worker-1
ForEach: 3 - ForkJoinPool.commonPool-worker-4
ForEach: 4 - ForkJoinPool.commonPool-worker-3
ForEach: 5 - ForkJoinPool.commonPool-worker-2

- 배열 numbers를 데이터 소스로 사용하여 parallel stream을 생성하고, map 연산과 forEach 연산을 순차적으로 수행.

- map 연산은 각 요소를 변환하고, forEach 연산은 각 요소를 출력.

- Thread.currentThread().getName() : 현재 실행 중인 스레드의 이름 출력.

- map 연산은 여러 스레드에서 동시에 실행되므로 요소의 처리 순서가 보장되지 않음. 

- forEach 연산은 마지막에 순차적으로 수행되며, 최종 결과인 출력도 순차적으로 출력

※ 중간 연산과 최종 연산의 특성에 따라 다를 수 있으므로, 순서에 의존하는 작업을 수행하는 경우에는 주의가 필요

 

Java 8 에 추가된 기능인 Stream 에 대해서 간략하게 알아보았습니다. 더불어 parallel stream 과의 비교도 확인하였습니다.

반응형
반응형

이번 글에서는 주사위 앱 만들기에 대해서 알아봅니다.

 

주사위 앱 레이아웃

 - BottomNavigationBar 위젯을 이용해서 화면 전환을 한다.

 - RootScreen 위젯 : 상단과 하단으로 나누어져 있는 위젯입니다. 하단에는 화면 전환을 할 수 있는 BottomNavigationBar 위젯을 위치 시키고, 상단에는 TabBarView 를 통해 선택된 화면을 보여줍니다. 화면 전환은 BottomNavigationBar 의 탭을 클릭하거나 TabBarView 에서 좌우 스크롤을 통해 이루어집니다. TabBarView 에 나타내는 화면은 HomeScreen 위젯과 SettingsScreen 위젯 두 화면입니다.

 - HomeScreen 위젯 : 주사위 이미지를 위치시킬 Image 위젯, 글자를 작성할 Text 위젯, 주사위 숫자를 나타낼 Text 위젯으로 이루어져 있습니다.

 - SettingsScreen : 민감도를 정의하는 Slider 위젯을 위치시키고 이 Slider 를 좌우로 이동시켜 민감도를 정합니다. Text 위젯을 이용하여 레이블을 작성합니다.

 

주사위 앱 shake 플러그인 및 이미지 추가

 - pubspec.yaml

....

dependencies:
  flutter:
    sdk: flutter


  # The following adds the Cupertino Icons font to your application.
  # Use with the CupertinoIcons class for iOS style icons.
  cupertino_icons: ^1.0.2
  shake: 2.1.0

dev_dependencies:
  flutter_test:
    sdk: flutter

  # The "flutter_lints" package below contains a set of recommended lints to
  # encourage good coding practices. The lint set provided by the package is
  # activated in the `analysis_options.yaml` file located at the root of your
  # package. See that file for information about deactivating specific lint
  # rules and activating additional ones.
  flutter_lints: ^2.0.0

# For information on the generic Dart part of this file, see the
# following page: https://dart.dev/tools/pub/pubspec

# The following section is specific to Flutter packages.
flutter:

  # The following line ensures that the Material Icons font is
  # included with your application, so that you can use the icons in
  # the material Icons class.
  uses-material-design: true

  assets:
    - asset/img/
  
  ....

 

주사위 앱에서 사용할 상수 정의

 - 글자 크기, 자주 사용하는 색상등을 반복적으로 사용하는 값들을 미리 정의해놓고 사용하기 위해서 정의한다.

 - 일괄적으로 변경 시 유용하게 사용됩니다.

 - [프로젝트 디렉토리]/lib/const/colors.dart

import 'package:flutter/material.dart';

const backgroundColor = Color(0xFF0E0E0E);

const primaryColor = Colors.white;

final secondaryColor = Colors.grey[600];

※ secondaryColor : grey[600] 은 런타임시에 색상이 계산되기 때문에 const 사용이 불가하다. 그래서 final 로 정의합니다.

 

 

주사위 앱 위젯 구현

1. HomeScreen

 - [프로젝트 디렉토리]/lib/screen/home_screen.dart

import 'package:[프로젝트 명]/const/colors.dart';
import 'package:flutter/material.dart';

class HomeScreen extends StatelessWidget {
  final int number;

  const HomeScreen({
    required this.number,
    Key? key,
  }) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Column(
      mainAxisAlignment: MainAxisAlignment.center,
      children: [

        // ➊ 주사위 이미지
        Center(
          child: Image.asset('asset/img/$number.png'),
        ),
        SizedBox(height: 32.0),
        Text(
          '주사위 숫자',
          style: TextStyle(
            color: secondaryColor,
            fontSize: 20.0,
            fontWeight: FontWeight.w700,
          ),
        ),
        SizedBox(height: 12.0),
        Text(
          number.toString(),  // ➋ 주사위 값에 해당되는 숫자
          style: TextStyle(
            color: primaryColor,
            fontSize: 60.0,
            fontWeight: FontWeight.w200,
          ),
        ),
      ],
    );
  }
}

 - Column 위젯을 사용하여 Image 위젯과 Text 위젯을 배치합니다. 화면에 보여질 숫자를 RootScreen 위젯에서 정의하도록 생성자를 통해서 number 매개변수로 입력받는다.

 - 생성자로 입력받은 number 매개변수 값을 이용하여 Image 위젯에는 해당하는 숫자 이미지를, Text 위젯에는 숫자 값을 표시합니다.

 

2. SettingsScreen

 - [프로젝트 디렉토리]/lib/screen/settings_screen.dart

import 'package:[프로젝트 명]/const/colors.dart';
import 'package:flutter/material.dart';

class SettingsScreen extends StatelessWidget {
  final double threshold;  // Slider의 현잿값

  // Slider가 변경될 때마다 실행되는 함수
  final ValueChanged<double> onThresholdChange;

  const SettingsScreen({
    Key? key,

    // threshold와 onThresholdChange는 SettingsScreen에서 입력
    required this.threshold,
    required this.onThresholdChange,
  }) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Column(
      mainAxisAlignment: MainAxisAlignment.center,
      children: [
        Padding(
          padding: const EdgeInsets.only(left: 20.0),
          child: Row(
            children: [
              Text(
                '민감도',
                style: TextStyle(
                  color: secondaryColor,
                  fontSize: 20.0,
                  fontWeight: FontWeight.w700,
                ),
              ),
            ],
          ),
        ),
        Slider(
          min: 0.1,  // ➊ 최소값
          max: 10.0,  // ➋ 최대값
          divisions: 101,  // ➌ 최소값과 최대값 사이 구간 개수
          value: threshold,  // ➍ 슬라이더 선택값
          onChanged: onThresholdChange,  // ➎ 값 변경 시 실행되는 함수
          label: threshold.toStringAsFixed(1),  // ➏ 표시값
        ),
      ],
    );
  }
}

 - SettinsScreen 위젯은 Text 위젯과 Slider 위젯으로 이루어져 있습니다. Column 위젯을 이용하여 Text 위젯과 Slider 위젯을 세로로 배치합니다.

 - Slider 위젯 : 좌우로 움직일 때 onChanged 매개변수에 정의된 콜백함수가 호출됩니다. onChanged 매개변수로 입력받은 현재값을 State에 저장하고 다시 value 매개변수에 같은 값을 입력하게 됩니다.

 - Slider 위젯은 현재값과 onChanged 매개변수는 RootScreen 에서 입력받습니다.

 

3. RootScreen

 - [프로젝트 디렉토리]/lib/screen/root_screen.dart

import 'package:flutter/material.dart';
import 'package:[프로젝트 명]/screen/home_screen.dart';
import 'package:[프로젝트 명]/screen/settings_screen.dart';
import 'dart:math';
import 'package:shake/shake.dart';

class RootScreen extends StatefulWidget {
  const RootScreen({Key? key}) : super(key: key);

  @override
  State<RootScreen> createState() => _RootScreenState();
}

class _RootScreenState extends State<RootScreen> with TickerProviderStateMixin{    // ➊
  TabController? controller;  // 사용할 TabController 선언
  double threshold = 2.7;
  int number = 1;
  ShakeDetector? shakeDetector;

  @override
  void initState() {
    super.initState();

    controller = TabController(length: 2, vsync: this);  // ➋

    controller!.addListener(tabListener);
    shakeDetector = ShakeDetector.autoStart(   // ➊ 흔들기 감지 즉시 시작
      shakeSlopTimeMS: 100,  // ➋ 감지 주기
      shakeThresholdGravity: threshold,  // ➌ 감지 민감도
      onPhoneShake: onPhoneShake,  // ➍ 감지 후 실행할 함수
    );
  }

  void onPhoneShake() {  // ➎ 감지 후 실행할 함수
    final rand = new Random();

    setState(() {
      number = rand.nextInt(5) + 1;
    });
  }

  tabListener() {  // ➋ listener로 사용할 함수
    setState(() {});
  }

  @override
  dispose(){
    controller!.removeListener(tabListener); // ➌ listener에 등록한 함수 등록 취소
    shakeDetector!.stopListening();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: TabBarView(  // ➊ 탭 화면을 보여줄 위젯
        controller: controller,
        children: renderChildren(),
      ),

      // ➋ 아래 탭 네비게이션을 구현하는 매개변수
      bottomNavigationBar: renderBottomNavigation(),
    );
  }

  List<Widget> renderChildren(){
    return [
      HomeScreen(number: number),
      SettingsScreen(  // 기존에 있던 Container 코드를 통째로 교체
        threshold: threshold,
        onThresholdChange: onThresholdChange,
      ),
    ];
  }

  void onThresholdChange(double val){  // ➊ 슬라이더값 변경 시 실행 함수
    setState(() {
      threshold = val;
    });
  }

  BottomNavigationBar renderBottomNavigation() {
    return BottomNavigationBar(
      currentIndex: controller!.index,
      onTap: (int index) {  // ➎ 탭이 선택될 때마다 실행되는 함수
        setState(() {
          controller!.animateTo(index);
        });
      },
      items: [
        BottomNavigationBarItem(  // ➊ 하단 탭바의 각 버튼을 구현
          icon: Icon(
            Icons.edgesensor_high_outlined,
          ),
          label: '주사위',
        ),
        BottomNavigationBarItem(
          icon: Icon(
            Icons.settings,
          ),
          label: '설정',
        ),
      ],
    );
  }
}

 - TabBarView 는 TabController 가 필수입니다. TabController 는 위젯이 생성될 때 딱 한번만 초기화되어야 하니 initState() 에서 초기화 작업을 합니다. TabController vsync 기능을 사용하려면 필수로 TickerProviderStateMixin을 사용해야 합니다. TickerProviderStateMixin 은 애니메이션의 효율을 올려주는 역할을 합니다. TabController 의 length 매개변수에는 탭의 개수를 나타내고, TickerProviderStateMixin 를 사용하는 State 클래스를 this 로 넣어줍니다. 그러면 controller 를 이용해서 TabBarView 를 조작할 수 있습니다. controller 의 속성이 변할 때마다 실행시킬 콜백함수를 addListener() 함수를 통해 등록할 수 있습니다. setState() 를 실행하여 controller 의 속성이 변경될 때 build() 를 재실행하도록 합니다.

 - ShakeDetector : Shake 플러그인은 흔들기를 인지할 때마다 실행할 함수를 등록합니다. 얼마나 자주 흔들기 감지 주기, 감지 민감도, 감지 시 실행할 콜백함수 등을 매개변수로 전달합니다. 흔들 때마다 1 ~ 6 사이의 난수를 생성합니다.

 - renderChildren() : TabBarView 위젯을 반환하는 함수입니다. TabBarView 위젯을 이용하면 각종 Tab 위젯과 쉽게 연동 할 수 있는 UI 를 구현할 수 있습니다. 기본 애니메이션이 제공되며 children 매개변수에 각 탭의 화면에서 활용하고 싶은 위젯을 List 로 넣어줍니다. bottomNavigation 매개변수에 BottomNavigationBar 를 넣어주면 쉽게 Tab을 조정할 수 있는 UI 를 하단에 배치할 수 있습니다. 주사위 앱에서는 TabBarView 의 첫번째 화면에 HomeScreen, 두번째 화면에 SettinsScreen 을 표시합니다.

 - renderBottomNavigation() : BottomNavigationBar 위젯을 반환하는 함수입니다. BottomNavigationBar 에 제공될 각 탭은 BottomNavigationBar 위젯의 items 매개변수에 제공해주면 됩니다. BottomNavigationBarItem 의 icon 매개변수와 label 매개변수를 이용해서 구현합니다. 탭을 클릭했을 때 TabBarView 와 화면을 동기화하기 위해 animateTo() 를 실행하여 자연스러운 애니메이션으로 TabBarView 를 전환시킵니다.

 

4. 주사위 앱 실행 첫 화면 : main

 - [프로젝트 디렉토리]/lib/main.dart

import 'package:flutter/material.dart';
import 'package:[프로젝트 명]/screen/home_screen.dart';
import 'package:[프로젝트 명]/const/colors.dart';
import 'package:[프로젝트 명]/screen/root_screen.dart';

void main() {
  runApp(
    MaterialApp(
      debugShowCheckedModeBanner: false,
      theme: ThemeData(
        scaffoldBackgroundColor: backgroundColor,
        sliderTheme: SliderThemeData(  // Slider 위젯 관련
          thumbColor: primaryColor,    // 동그라미 색
          activeTrackColor: primaryColor,  // 이동한 트랙 색

          // 아직 이동하지 않은 트랙 색
          inactiveTrackColor: primaryColor.withOpacity(0.3),         ),
        // BottomNavigationBar 위젯 관련
        bottomNavigationBarTheme: BottomNavigationBarThemeData(
          selectedItemColor: primaryColor,     // 선택 상태 색
          unselectedItemColor: secondaryColor, // 비선택 상태 색
          backgroundColor: backgroundColor,    // 배경 색
        ),
      ),
      home: RootScreen(),
    ),
  );
}

 - Theme 설정 : 상수를 사용해서 테마를 적용합니다.

 - 홈 화면 : RootScreen 위젯으로 설정합니다.

 

 

주사위 앱을 실제 기기에서 실행해봅니다.

 - 주사위가 보이면 핸드폰을 흔들어서 난수가 생성되고 새로운 숫자와 주사위가 보이는 것을 확인합니다.

 - TabBarView 에서 좌우 스크롤하거나 BottomNavigationBar 를 클릭하여 화면 전환이 되는지 확인합니다.

 - [설정] 탭을 클릭하여 설정화면으로 이동한 뒤 Slider 위젯을 이용하여 민감도를 올립니다.

 - [주사위] 탭을 클릭하여 HomeScreen 화면으로 이동한 뒤 핸드폰을 더 강하게 흔들어야 난수가 발생하는지 확인합니다.

 

이상 플러터에서 주사위 앱을 만들고 실행까지 해보았습니다.

 

반응형
반응형

Java Stream reduce 함수에 대한 설명입니다.

 

바로 소스로 들어가보자!

import java.util.OptionalInt;
import java.util.stream.IntStream;

public class StreamReduce {

    public static void main(String[] args) {
        //Optional<T> reduce(BinaryOperator<T> accumulator);
        OptionalInt reduced1 = IntStream.range(1, 5) // [1, 2, 3, 4]
                        .reduce((a, b) -> {
                            System.out.println("a = " + a);
                            System.out.println("b = " + b);
                            return Integer.sum(a, b);
                        });

        System.out.println(reduced1.getAsInt());


        //T reduce(T identity, BinaryOperator<T> accumulator);
        int reduced2 = IntStream.range(1, 5) // [1, 2, 3, 4]
                        .reduce(10, (a, b) -> {
                            System.out.println("a = " + a);
                            System.out.println("b = " + b);
                            return Integer.sum(a, b);
                        });

        System.out.println(reduced2);
    }
}

 

긴말 필요 없이 결과도 바로 확인해보자!

a = 1
b = 2
a = 3
b = 3
a = 6
b = 4
10

a = 10
b = 1
a = 11
b = 2
a = 13
b = 3
a = 16
b = 4
20

 

Stream 생성 했던 IntStream 의 range 함수는 Javadoc 으로 확인해본다.

IntStream (Java Platform SE 8 ) (oracle.com)

 

IntStream (Java Platform SE 8 )

Returns an infinite sequential ordered IntStream produced by iterative application of a function f to an initial element seed, producing a Stream consisting of seed, f(seed), f(f(seed)), etc. The first element (position 0) in the IntStream will be the prov

docs.oracle.com

 

※ 첫번째 파라미터는 포함, 두번째 파라미터는 불포함

 - 항상 헷갈리는 부분이니까 꼼꼼히 읽어보자!!

static IntStream range(int startInclusive, int endExclusive)
Returns a sequential ordered IntStream from startInclusive (inclusive) to endExclusive (exclusive) by an incremental step of 1.
API Note:
An equivalent sequence of increasing values can be produced sequentially using a for loop as follows:

     for (int i = startInclusive; i < endExclusive ; i++) { ... }
 
Parameters:
startInclusive - the (inclusive) initial value
endExclusive - the exclusive upper bound
Returns:
a sequential IntStream for the range of int elements

 

 

자, 이제 본격적으로 Stream reduce 에 대한 설명 들어보고 가실께여~

1. 먼저 파라미터가 1개인 reduce 함수

        //Optional<T> reduce(BinaryOperator<T> accumulator);
        OptionalInt reduced1 = IntStream.range(1, 5) // [1, 2, 3, 4]
                        .reduce((a, b) -> {
                            System.out.println("a = " + a);
                            System.out.println("b = " + b);
                            return Integer.sum(a, b);
                        });

        System.out.println(reduced1.getAsInt());

첫번째 실행

 - a : Stream 의 첫번째 값 = 1

 - b : Stream 의 두번째 값 = 2

 - a + b = 3 을 반환(return) 한다.

두번째 실행

 - a : 첫번째 실행한 결과 값 = 3

 - b : Stream 의 다음(세번째) 값 = 3

 - a + b = 6 을 반환(return) 한다.

세번째 실행

 - a : 두번째 실행한 결과 값 = 6

 - b : Stream 의 다음(네번째) 값 = 4

 - a + b = 10 을 반환(return) 한다.

최종 실행 결과 값으로 10을 반환한다.

 

2. 먼저 파라미터가 2개인 reduce 함수

 - reduce 함수의 첫번째 파라미터는 초기 값을 의미한다.

        //T reduce(T identity, BinaryOperator<T> accumulator);
        int reduced2 = IntStream.range(1, 5) // [1, 2, 3, 4]
                        .reduce(10, (a, b) -> {
                            System.out.println("a = " + a);
                            System.out.println("b = " + b);
                            return Integer.sum(a, b);
                        });

        System.out.println(reduced2);

 

첫번째 실행

 - a : 초기 값 = 10

 - b : Stream 의 첫번째 값 = 1

 - a + b = 11 을 반환(return) 한다.

두번째 실행

 - a : 첫번째 실행한 결과 값 = 11

 - b : Stream 의 다음(두번째) 값 = 2

 - a + b = 13 을 반환(return) 한다.

세번째 실행

 - a : 두번째 실행한 결과 값 = 13

 - b : Stream 의 다음(세번째) 값 = 3

 - a + b = 16 을 반환(return) 한다.

네번째 실행

 - a : 세번째 실행한 결과 값 = 16

 - b : Stream 의 다음(네번째) 값 = 4

 - a + b = 20 을 반환(return) 한다.

최종 실행 결과 값으로 20을 반환한다.

 

 

위에서 살펴본 Stream reduce 두 함수의 차이는 초기 값이 있냐 없냐의 차이이다.

그리고 Stream reduce 함수에서 가장 중요하게 봐야할 부분은 accumulator 의 동작 방식이다.

첫번째, 두번째 인자에 값이 어떻게 전달되어 최종 결과값을 반환하는지에 대한 이해만 있다면 어렵지 않을 것이다.

 

Stream reduce 에 대한 Javadoc 페이지도 같이 확인하면 좋을 것 같아 링크를 건다.

Stream (Java Platform SE 8 ) (oracle.com)

 

Stream (Java Platform SE 8 )

A sequence of elements supporting sequential and parallel aggregate operations. The following example illustrates an aggregate operation using Stream and IntStream: int sum = widgets.stream() .filter(w -> w.getColor() == RED) .mapToInt(w -> w.getWeight())

docs.oracle.com

 

반응형
반응형

D-Day 앱 만들기

 

D-Day 앱 레이아웃

 - 화면의 상/하단으로 나눕니다.

 - 화면의 상단에는 D-Day 시작 날짜를 선택 할 수 있는 이미지 버튼과 D-Day 를 계산하여 표시합니다.

 - 화면의 하단에는 이미지를 넣어줍니다.

 

사전 준비

 - 배경으로 사용될 이미지를 준비합니다.

 - 폰트 파일을 준비합니다.

 - pubspec.yaml 에서 프로젝트에서 사용할 이미지와 폰트를 설정합니다.

....

flutter:

  # The following line ensures that the Material Icons font is
  # included with your application, so that you can use the icons in
  # the material Icons class.
  uses-material-design: true

  assets:
    - asset/img/

  fonts:
    - family: parisienne  # ➊ family 키에 폰트 이름을 지정할 수 있습니다.
      fonts:
        - asset: asset/font/Parisienne-Regular.ttf  # ➋ 등록할 폰트 파일의 위치

    - family: sunflower
      fonts:
        - asset: asset/font/Sunflower-Light.ttf
        - asset: asset/font/Sunflower-Medium.ttf
          weight: 500  # ➌ 폰트의 두께. FontWeight 클래스의 값과 같습니다.
        - asset: asset/font/Sunflower-Bold.ttf
          weight: 700

....

Weight 는 폰트의 두께를 의미합니다. 폰트의 두께별로 파일이 따로 존재하기 때문에 같은 폰트라도 다른 두께를 표현하는 파일은 Weight 값을 따로 표현해야 합니다.

두께 값은 100~900까지 100단위로 사용할 수 있으면 숫자가 높을수록 두껍다는 의미입니다. 코드에서 사용시에는 FontWeight.w500 처럼 사용하면 됩니다.

pubspec.yaml 파일을 수정했다면 [pub get] 을 실행하여 변경 사항을 반영해줍니다.

 

 

첫 화면

그럼 이제 본격적으로 D-Day 앱을 만들어 보겠습니다.

 - main.dart 파일에 위젯에서 사용할 텍스트와 IconButton 테마를 정의합니다.

import 'package:[프로젝트 명]/screen/home_screen.dart';
import 'package:flutter/material.dart';

void main() {
  runApp(
    MaterialApp(
      theme: ThemeData(  // ➊ 테마를 지정할 수 있는 클래스
          fontFamily: 'sunflower',  // 기본 글씨체
          textTheme: TextTheme(     // ➋ 글짜 테마를 적용할 수 있는 클래스
            headline1: TextStyle(   //  headline1 스타일 정의
              color: Colors.white,  //  글 색상
              fontSize: 80.0,       //  글 크기
              fontWeight: FontWeight.w700, //  글 두께
              fontFamily: 'parisienne',    //  글씨체
            ),
            headline2: TextStyle(
              color: Colors.white,
              fontSize: 50.0,
              fontWeight: FontWeight.w700,
            ),
            bodyText1: TextStyle(
              color: Colors.white,
              fontSize: 30.0,
            ),
            bodyText2: TextStyle(
              color: Colors.white,
              fontSize: 20.0,
            ),
          )
      ),
      home: HomeScreen(),
    ),
  );
}

MaterialApp 에는 theme 라는 매개변수가 있습니다. 이 매개변수에는 ThemeData 를 입력할 수 있습니다.

ThemeData 에서는 플러터가 기본으로 제공하는 대부분의 위젯의 기본 스타일을 지정할 수 있습니다.

위에서 사용한 textTheme 는 글자의 테마를 지정할 수 있는 매개변수입니다.

 

앱 화면(HomeScreen 위젯)

D-Day 앱 화면에 표시할 위젯을 작성합니다.

 - 화면을 _DDay 위젯과 _CoupleImage 위젯, 두 위젯을 위 아래로 배치하여 구현합니다.

 - lib/home_screen.dart

import 'package:flutter/material.dart';
import 'package:flutter/cupertino.dart';

class HomeScreen extends StatefulWidget {
  const HomeScreen({Key? key}) : super(key: key);

  @override
  State<HomeScreen> createState() => _HomeScreenState();
}

class _HomeScreenState extends State<HomeScreen> {
  DateTime firstDay = DateTime.now();

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      backgroundColor: Colors.pink[100],
      body: SafeArea(
        // ➊ 시스템 UI 피해서 UI 그리기
        top: true,
        bottom: false,
        child: Column(
          // ➋ 위, 아래 끝에 위젯 배치
          mainAxisAlignment: MainAxisAlignment.spaceBetween,

          // 반대 축 최대 크기로 늘리기
          crossAxisAlignment: CrossAxisAlignment.stretch,
          children: [
            _DDay(

              // ➎ 하트 눌렀을때 실행할 함수 전달하기
              onHeartPressed: onHeartPressed,
              firstDay: firstDay,
            ),
            _CoupleImage(),
          ],
        ),
      ),
    );
  }

  void onHeartPressed(){  // ➍ 하트 눌렀을때 실행할 함수
    showCupertinoDialog(  // ➋ 쿠퍼티노 다이얼로그 실행
      context: context,
      builder: (BuildContext context) {
        return Align(  // ➊ 정렬을 지정하는 위젯
          alignment: Alignment.bottomCenter,  // ➋ 아래 중간으로 정렬
          child: Container(
            color: Colors.white,  // 배경색 흰색 지정
            height: 300,  // 높이 300 지정
            child: CupertinoDatePicker(
              mode: CupertinoDatePickerMode.date,
              onDateTimeChanged: (DateTime date) {
                setState(() {
                  firstDay = date;
                });
              },
            ),
          ),
        );
      },
      barrierDismissible: true,
    );
  }
}

.....

SafeArea : 아이폰의 노치 디스플레이등에 대비에서 위쪽에 SafeArea 를 적용해준다. 하단에는 미적용한다.

mainAxisAlignment: MainAxisAlignment.spaceBetween

 - 위 아래 각각 끝에 _DDay, _CoupleImage 위젯을 위치시킵니다.

상태 관리

 - 처음 만난 날은 변경 할 수 있는 값입니다.

 - 이 날짜를 변수 값으로 저장하고 변경하면서 사용합니다.

 - 하트 아이콘(ImageButtion)를 누르면 날짜를 고를 수 있는 UI 가 나오며, 날짜를 선택하면 firstDay 변수를 변경시킵니다.

 - _DDay 위젯에 하트 아이콘을 눌렀을 때 실행되는 콜백함수를 매개변수로 전달해서 _HomeScreen 에서 상태를 관리합니다.

Align 위젯

 - 자식 위젯의 위치를 지정합니다.

 - bottomCenter 는 자식 위젯을 중간 아래에 배치합니다.

onDateTimeChanged 의 콜백 함수

 - CupertinoDatePicker 위젯에서 날짜가 변경될 때마다 실행합니다.

 - 콜백함수가 실행될 때마다 firstDay 변수에 선택된 date 값이 저장됩니다.

 

 

_DDay 위젯

 - 여러 Text 위젯과 하트 아이콘(ImageButton)으로 이루어져 있습니다.

 - 만나기 시작할 날짜와 며칠이 지났는지 표시하는 글자는 날짜를 변경할 때마다 자동으로 바뀌게 됩니다.

 - main.dart 에서 정의한 스타일을 Text 위젯의 style 에 적용합니다.

 - lib/home_screen.dart

class _HomeScreenState extends State<HomeScreen> {
....
}


class _DDay extends StatelessWidget {
  final GestureTapCallback onHeartPressed;
  final DateTime firstDay;

  _DDay({
    required this.onHeartPressed,  // ➋ 상위에서 함수 입력받기
    required this.firstDay,
  });

  @override
  Widget build(BuildContext context) {
    final textTheme = Theme.of(context).textTheme;
    final now = DateTime.now();

    return Column(
      children: [
        const SizedBox(height: 16.0),
        Text(
          // 최상단 U&I 글자
          'U&I',
          style: textTheme.headline1,
        ),
        const SizedBox(height: 16.0),
        Text(
          // 두번째 글자
          '우리 처음 만난 날',
          style: textTheme.bodyText1,
        ),
        Text(
          // 임시로 지정한 만난 날짜
          '${firstDay.year}.${firstDay.month}.${firstDay.day}',
          style: textTheme.bodyText2,
        ),
        const SizedBox(height: 16.0),
        IconButton(
          // 하트 아이콘 버튼
          iconSize: 60.0,
          onPressed: onHeartPressed,
          icon: Icon(
            Icons.favorite,
            color: Colors.red,
          ),
        ),
        const SizedBox(height: 16.0),
        Text(
          // 만난 후 DDay
          'D+${DateTime(now.year, now.month, now.day).difference(firstDay).inDays + 1}',
          style: textTheme.headline2,
        ),
      ],
    );
  }
}

Theme.of(context).textTheme

 - 위젯트리 위 가장 가까운 Theme 값을 가져옵니다.

 - Icon 의 경우 색상이 각각 다른 경우가 많기 때문에 theme 를 사용하지 않고 직접 지정합니다.

GestureTapCallback

 - Material 패키지에서 기본으로 제공하는 typedef 로, 버튼의 onPressed 및 onTap 콜백 함수들의 GestureTapCallback 타입으로 정의되어 있습니다.

 

 

_CoupleImage 위젯

 - lib/home_screen.dart

class _HomeScreenState extends State<HomeScreen> {
....
}

class _DDay extends StatelessWidget {
....
}

class _CoupleImage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Expanded(
      // Expanded 추가
      child: Center(
        // ➊ 이미지 중앙정렬
        child: Image.asset(
          'asset/img/middle_image.png',

          // ➋ 화면의 반만큼 높이 구현
          height: MediaQuery.of(context).size.height / 2,
        ),
      ),
    );
  }
}

Expanded

 - 핸드폰은 화면의 비율과 해상도가 모두 다릅니다. 따라서 하나의 화면을 기준으로 UI 작업을 하다보면 다른 크기의 핸드포에서는 같은 UI 배치가 나오지 않을 수 있습니다. 이미지를 화면의 반만큼 차지하도록 설정하였는데 만약 화면의 크기가 작아서 상단의 글자들이 화면의 반 이상을 차지하면 아래쪽 이미지는 남은 공간보다 더 많은 높이를 차지합니다. 그래서 일반적으로 이미지가 남는 공간만큼 차지하도록 Expanded 위젯을 사용합니다.

height: MediaQuery.of(context).size.height / 2

 - 이미지를 적용하고 높이를 화면 높이의 반으로 설정합니다.

 - MediaQuery 를 사용하면 화면의 크기와 관련된 각종 기능을 사용할 수 있습니다.

 - size getter 를 사용하여 전체의 너비와 높이를 사용하였습니다. 화면의 전체 높이 / 2 를 사용하여 이미지가 화면 높이 반만큼만 차지할 수 있도록 설정하였습니다.

Center 를 이용해서 이미지를 중앙에 배치시킵니다.

 

 

자!! 이제 코드 작성은 완성하였습니다.

앱을 실행해보자.

D-Day 앱. 짜잔~~~

 

반응형

+ Recent posts