Info

개발, 기술, 그리고 일상에 대한 기록.

Archive

About

기록하고,
공유합니다.

개발, 기술, 그리고 일상에 대한 이야기를 담는 공간입니다.

dannyjoo

개발자로서,
기술과 일상의 접점을 기록합니다.

이 블로그는 개발하며 마주치는 문제들과 해결 과정, 그리고 일상의 소소한 이야기를 담는 공간입니다.

복잡한 것을 단순하게 정리하고, 나중의 나를 위해 기록합니다.

Danny Joo Developer
Interests
  • Android / Flutter
  • Backend
  • UI/UX
Links

글 보러 가기

Archive →
[리뷰] 로지텍 MX Master 4 사용 후기
리뷰

[리뷰] 로지텍 MX Master 4 사용 후기

목차1. 소개2. 장점3. 단점4. 총평 소개제품명: MX Master 4가격: 133,120원사용 기간: 약 10일 구매 이유최근 작업을 하면서 여유도(금적전 여유X) 생기고 그래서 마우스를 알아봤습니다. 처음에는 기존에 쿠팡에서 대충 주문해서 사용하던 버티컬 마우스 생각이 나서 버티컬 마우스 위주로 찾아봤습니다. 그 과정에서 유튜브 맞춤 동영상이 마우스 리뷰 동영상으로 도배가 되었습니다. 그리고 저에게 사무용 끝판왕 슈퍼 갓 엠페러 마우스는 MX Master다 라는 결론을 얻어버렸습니다.뭐 그렇게 기가맥히는 기능 + 가격을 제외하곤 안추천하는 사람들이 없을정도로 대단하신 마우스라 그러길래 쿠팡에 들낙하길 2일차... 직구로도 15만원이 넘던 마우스가 13만원에 구매할 수 있길래 충동적으로 구매를 하였..

리뷰

[리뷰] 로지텍 MX Master 4 사용 후기

목차

1. 소개

2. 장점

3. 단점

4. 총평

 

소개

  • 제품명: MX Master 4
  • 가격: 133,120원
  • 사용 기간: 약 10일

 

구매 이유

최근 작업을 하면서 여유도(금적전 여유X) 생기고 그래서 마우스를 알아봤습니다. 처음에는 기존에 쿠팡에서 대충 주문해서 사용하던 버티컬 마우스 생각이 나서 버티컬 마우스 위주로 찾아봤습니다.

 

그 과정에서 유튜브 맞춤 동영상이 마우스 리뷰 동영상으로 도배가 되었습니다. 그리고 저에게 사무용 끝판왕 슈퍼 갓 엠페러 마우스는 MX Master다 라는 결론을 얻어버렸습니다.

뭐 그렇게 기가맥히는 기능 + 가격을 제외하곤 안추천하는 사람들이 없을정도로 대단하신 마우스라 그러길래 쿠팡에 들낙하길 2일차... 직구로도 15만원이 넘던 마우스가 13만원에 구매할 수 있길래 충동적으로 구매를 하였고 전 다시 여유를 잃어버린 몸이 되었습니다.

장점

1. 액션링

액션링 설정

뭐 다들 입을 모아 추천하는 부분이죠. 

좋습니다. 좋아요.

이 버튼 자체도 햅틱 피드백. 아 요거 참 좋습니다. 느좋 그 자체.

 

2. 무한 스크롤

아 이것도 너무 편합니다.

이것도 편한 것도 편한 건데 느낌이 좋습니다.

회사에서 휠 굴리고 촵! 멈추고 다시 휠 굴리고 촵! 아 좋아요.

ADHD가 있는 저에겐 약 처방받았다라는 느낌입니다.

 

다만 이건 컨트롤을 자주 누르는 저에게 가끔 거북목 혹은 고도 근시 체험을 선사합니다.

(휠 돌아가고 있는 상태로 크롬 클릭하면 확대/축소가 어마무시하게 됨;;)

3. 외모

아 예뻐요.

보고 있으면 흐뭇합니다.

회사에 쓱 하고 냅두면 다들 쳐다볼 것 같은 그런 외모.

 


 

단점

1. 무게

무겁습니다.

생각보다 더 무겁습니다.

 

마우스를 드는 안좋은 습관이 있는데. 가끔 놀라요. 헬스장으로 출근했나 ㅋ

여튼 손목 건강을 위해서 샀는데 가끔 이게 맞나 싶네요.

2. 초기 설정

아 어렵습니다.

 

액션링을 통해 웹, 파일 탐색기, IDE 등 자주 사용하는 앱들을 자동으로 띄우고 싶어서 이것저것 알아보면서 설정했습니다. 체감상 2년 정도 지나고 완벽하게 세팅을 할 수 있었습니다.

 


 

총평

사무용 ADHD 치료제

음 전체적인 총평은 뭐 좋습니다.

사실 물건을 사고 후회를 잘 안하는 스타일이기도 해서 뭐...

 

ADHD 있으신 분들은 약 처방받는다고 생각하고 사셔도 무방합니다.

마우스에 버튼이 많아서 사무실에서 눈치 안보고 막 눌러볼 수 있습니다.

느낌도 좋아서 네... 추천해요...

 

쓰면서 역시 가격이 너무 사악하다는 생각이 다시 드네요...

 

키보드 후기로 돌아오겠습니다.

[Android] 앱 실행 시 바로 logcat 창 띄우기
Skill/Android

[Android] 앱 실행 시 바로 logcat 창 띄우기

목차1. 개요2. 설정 방법3. 결과개요안드로이드 스튜디오를 이용해 개발을 진행할 때 Log.d()를 이용해 값을 확인할 때 Logcat창을 확인하는 것이 일반적인데 run app을 하게 되면 Run창이 디폴트로 설정되어 있어 필요한 Logcat창이 아닌 Run창이 자동적으로 보여 불편함을 겪었다. 그래서 안드로이드 스튜디오 설정을 통해 이 디폴트 값을 Logcat창으로 바꾸는 과정을 알아보았다.설정 방법일단 공식 문서에도 설정 방법이 나와있고 방법은 굉장히 간단하다. 필자는 이 문서를 참고했다. 안드로이드 공식 문서 실행/디버그 구성 생성 및 수정 | Android 스튜디오 | Android Developers코드를 실행, 디버그, 테스트할 때 Android 스튜디오는 실행/디버그 구성을 사용하..

Skill/Android

[Android] 앱 실행 시 바로 logcat 창 띄우기

목차

1. 개요

2. 설정 방법

3. 결과

개요

안드로이드 스튜디오를 이용해 개발을 진행할 때 Log.d()를 이용해 값을 확인할 때 Logcat창을 확인하는 것이 일반적인데 run app을 하게 되면 Run창이 디폴트로 설정되어 있어 필요한 Logcat창이 아닌 Run창이 자동적으로 보여 불편함을 겪었다. 그래서 안드로이드 스튜디오 설정을 통해 이 디폴트 값을 Logcat창으로 바꾸는 과정을 알아보았다.


설정 방법

일단 공식 문서에도 설정 방법이 나와있고 방법은 굉장히 간단하다. 필자는 이 문서를 참고했다.

 

안드로이드 공식 문서

 

실행/디버그 구성 생성 및 수정  |  Android 스튜디오  |  Android Developers

코드를 실행, 디버그, 테스트할 때 Android 스튜디오는 실행/디버그 구성을 사용하여 작업을 수행할 방법을 결정합니다.

developer.android.com

 

1. Run 메뉴 들어가서 Edit Configurations 클릭

Run Setting

2. Miscellaneous 탭 누르기

Miscellaneous

3. Logcat 창에서 옵션 설정

Logcat 창에서 옵션 설정

Show logcat automatically를 체크하면 Run app 시 자동적으로 Logcat창이 보인다. 자신의 취향 혹은 필요성에 따라

Clear log before launch도 체크해 사용하면 좋을 것 같다.


결과

실행 시 바로 Logcat창이 보여 개발할 때의 귀찮음을 덜었다. 의외로 귀찮은 Logcat 창으로 옮기는 과정을 굉장히 간단한 설정을 통해 해결할 수 있었다. 아주 가성비가 좋은 설정이다.

 

 

[Android/kotlin] 뷰페이저2 가로로 스와이프 막기
Skill/Android

[Android/kotlin] 뷰페이저2 가로로 스와이프 막기

목차1. 개요2. 해결 방법3. 결과개요 프로젝트를 진행하던 도중 리사이클러뷰를 가로로 사용하는 디자인을 선택해 진행하는데 뷰페이저2의 가로로 스와이프 하는 것과 곂침이 발생하는 것을 겪었습니다. 이것을 해결하기 위한 방법을 찾아봤습니다.해결 방법 // viewPager에 설정 추가viewPager.run { isUserInputEnabled = false}해당 구문을 추가하면 뷰페이저는 사용자에 대한 입력을 무시하게 됩니다.따라서 뷰페이저의 스와이프 기능은 자연스럽게 작동하지 않게 됩니다.결과 작동이 잘 되는 모습을 볼 수 있습니다.프래그먼트의 이동은 하단의 탭으로 대체하였습니다.

Skill/Android

[Android/kotlin] 뷰페이저2 가로로 스와이프 막기

목차

1. 개요

2. 해결 방법

3. 결과

개요

 

스와이프 방향 곂침 문제

프로젝트를 진행하던 도중 리사이클러뷰를 가로로 사용하는 디자인을 선택해 진행하는데 뷰페이저2의 가로로 스와이프 하는 것과 곂침이 발생하는 것을 겪었습니다. 이것을 해결하기 위한 방법을 찾아봤습니다.


해결 방법

 

// viewPager에 설정 추가

viewPager.run {
    isUserInputEnabled = false
}

해당 구문을 추가하면 뷰페이저는 사용자에 대한 입력을 무시하게 됩니다.

따라서 뷰페이저의 스와이프 기능은 자연스럽게 작동하지 않게 됩니다.


결과

 

뷰페이저 스와이프 금지

작동이 잘 되는 모습을 볼 수 있습니다.

프래그먼트의 이동은 하단의 탭으로 대체하였습니다.

[GIT] .DS_Store파일 .gitignore로 무시하기
Skill/Git

[GIT] .DS_Store파일 .gitignore로 무시하기

목차1. 개요2. DS_Store란?3. gitignore로 추적 무시4. 결과개요 프로젝트를 진행 중 신나게 기능을 개발하고 난 뒤 android stutdio에서 commit을 한 뒤 터미널에서 습관성 git status가 발동해 무의식적으로 치게 되었습니다.그런데...음... 뭔가 잘못되었다. 난 저런 파일들을 수정한 적 없을뿐더러 만든 적조차 없었습니다. 구글링을 통해 왜 이런 현상이 발생했는지 알아보았습니다.DS_Store란? DS.Store 파일은 Desktop Services Store의 약자macOS 운영체제에서 Finder로 폴더를 볼 때마다 자동으로 생성된다고 합니다.해당 폴더에 대한 메타데이터를 저장하는 파일이고 이 메타데이터 안에는 "해당 디렉토리의 특성, 구조 등에 관한 내용"이 ..

Skill/Git

[GIT] .DS_Store파일 .gitignore로 무시하기

목차

1. 개요

2. DS_Store란?

3. gitignore로 추적 무시

4. 결과

개요

 

프로젝트를 진행 중 신나게 기능을 개발하고 난 뒤 android stutdio에서 commit을 한 뒤 터미널에서 습관성 git status가 발동해 무의식적으로 치게 되었습니다.

그런데...

git status

음... 뭔가 잘못되었다. 난 저런 파일들을 수정한 적 없을뿐더러 만든 적조차 없었습니다. 구글링을 통해 왜 이런 현상이 발생했는지 알아보았습니다.


DS_Store란?

 

DS.Store 파일Desktop Services Store의 약자

macOS 운영체제에서 Finder로 폴더를 볼 때마다 자동으로 생성된다고 합니다.

해당 폴더에 대한 메타데이터를 저장하는 파일이고 이 메타데이터 안에는 "해당 디렉토리의 특성, 구조 등에 관한 내용"이 포함된다고 합니다.

잠시 프로젝트 구조 좀 살펴보느라 Finder로 디렉토리에 접근하느라 저도 모르는 사이에 생성되었나 봅니다...

아마 저는 아직 터미널에 완벽하게 익숙하지 않기 때문에 앞으로도 이런 행위를 저지를 것 같아서 미리 gitignore로 무시하기로 했습니다~


gitignore로 추적 무시

 

먼저 전 기존에 .gitignore 파일이 존재했기 때문에 이를 기준으로 진행했습니다.

1. 기존에 Finder를 통해 생성됐던 DS_Store 파일들을 제거

이 파일들은 기본적으로 숨김처리가 되어있습니다. 그렇기 때문에 편하게 명령어를 통해 지워주도록 합시다.

(1) 먼저 작업중인 프로젝트의 루트 디렉토리로 이동합니다.

(2) 그리고 DS_Store 삭제 명령어를 입력

DS_Store 삭제 명령어

find . -name .DS_Store -print0 | xargs -0 git rm --ignore-unmatch -f

(3) git status를 입력해 상태 확인

명령어 실행 후

명령어 실행 후 .DS_Store 파일이 삭제된 것을 확인할 수 있습니다.

2. .gitignore 파일을 편집해 향후에 모든 .DS_Store 파일 무시

(1) 프로젝트 루트 디렉토리에 Mac 기준 cmd + shift + .키(숨김파일 보기)를 눌러 .gitignore 파일을 찾는다

(2) .gitignore 파일 수정

gitignore

#MAC
.DS_Store

이렇게 설정하면 .DS_Store 추적 금지 설정 완료가 됩니다.


결과

 

결과

해당 디렉토리에 .DS_Store가 있지만 git이 철저하게 무시하는 모습을 볼 수 있습니다.

[Android/Kotlin] 안드로이드 스튜디오 API 키 값 숨기는 방법
Skill/Android

[Android/Kotlin] 안드로이드 스튜디오 API 키 값 숨기는 방법

목차1. 개요2. API 키 값을 숨기는 이유3. 방법4. 결과5. 후기 개요 KAKAO API를 사용해 간단한 앱을 만들던 와중 Git에 올렸을 때의 API 키가 공개되는 것에 대한 문제를 직면하게 되었다.사실 로컬에서 테스트를 하고 개발을 할 때에는 큰 상관은 없지만 Git에 코드 업로드, 베포 시에는 문제가 되기 때문에 이 API 키를 숨기는 것에 대해 알아보았다.키를 숨기는 이유 키를 숨기는 이유는 여러가지 이유가 있다.1. 보안API 키를 외부에 노출시키면 누구나 해당 API를 사용할 수 있다.API 키가 외부에 노출되면 악의적인 공격이나 남용의 발생할 수 있다. 그렇기 때문에 API 키를 안전하게 보호하여 API를 사용하는 앱 또는 서비스의 보안을 유지해야 된다.2. 개인 정보 보호API 중..

Skill/Android

[Android/Kotlin] 안드로이드 스튜디오 API 키 값 숨기는 방법

목차

1. 개요

2. API 키 값을 숨기는 이유

3. 방법

4. 결과

5. 후기

 

개요

 

KAKAO API를 사용해 간단한 앱을 만들던 와중 Git에 올렸을 때의 API 키가 공개되는 것에 대한 문제를 직면하게 되었다.
사실 로컬에서 테스트를 하고 개발을 할 때에는 큰 상관은 없지만 Git에 코드 업로드, 베포 시에는 문제가 되기 때문에 이 API 키를 숨기는 것에 대해 알아보았다.


키를 숨기는 이유

 

키를 숨기는 이유는 여러가지 이유가 있다.

1. 보안

API 키를 외부에 노출시키면 누구나 해당 API를 사용할 수 있다.

API 키가 외부에 노출되면 악의적인 공격이나 남용의 발생할 수 있다. 그렇기 때문에  API 키를 안전하게 보호하여 API를 사용하는 앱 또는 서비스의 보안을 유지해야 된다.

2. 개인 정보 보호

API 중 사용자 데이터에 접근하는 권한이 있을 수 있다. 이러한 경우 키가 노출되면 사용자의 개인 정보가 유출될 수 있다.

이것은 개인 정보 보호 법규에 위배될 수 있다.

3. 과금 유발

API를 사용하는 것에 있어서 일정량 사용량 이상을 사용하게 되면 과금이 발생할 수 있는데 API 키가 공개되면 이 점을 이용해 무단으로 API 서비스를 이용하고 과금은 API 키를 공개한 사람이 지불하게 되는 불상사가 발생할 수 있다.

 

이러한 이유들로 인해 API 키는 외부에 공개되지 않는 것이 원칙이다.


방법

 

안드로이드 스튜디오에서 API 키를 숨기는 방법은 여러가지가 있는데 그 중 2가지 방법을 소개해보려고 한다.

 

1. gitignore를 이용한 방법

먼저 API 키를 저장할 클래스 생성

class Constants {

    companion object{
        const val BASE_URL = "https://dapi.kakao.com"

        // 개인 API 사용
        const val AUTH_HEADER = "KakaoAK $REST_API_KEY 입력" <- 에 개인이 부여받은 API 키입력
    }
}

 

 

.gitignore

프로젝트의 루트 디렉토리에서 맥 기준 cmd + shift + . 키를 입력해 숨김 파일을 확인한 후 

.gitignore 파일을 편집

.gitignore 파일

맨 밑줄에 해당 파일 추가 시 git에 push 시 해당 파일 추적을 멈춘다.

 

2. local.properties와 BuildConfig 사용

필자는 이 방법을 사용해 진행하였다.

먼저 앞서 올린 gitignore를 자세히 보면 local.properties라는 파일이 존재한다.

 

local.properties은 기본적으로 git이 추적하지 않기때문에 따로 gitignore 파일을 설정할 필요가 없다.

이 파일을 이용해 키 값을 숨길 수 있다.

 

먼저 안드로이드 스튜디오에서 local.properties 파일을 찾는다. 파일은 다음과 같은 경로에 위치한다.

local.properties 파일 위치

이 파일을 들어가서 API 키를 추가한다.

local.properties 파일

여기서 API 키를 설정한다.

그 후 build.gradle에 들어가 몇가지 수정을 해준다.

 

먼저 local.properties의 값을 불러오기 위한 설정을 추가해준다.

// local.properties의 값을 가져오기 위한 설정
Properties properties = new Properties()
properties.load(project.rootProject.file('local.properties').newDataInputStream())

그리고 buildConfig를 사용하기 위한 설정

// BuildConfig 사용을 위한 설정
defaultConfig {

        ...

    // BuildConfig에 값 추가
        buildConfigField "String", "KAKAO_REST_API_KEY", properties["api_key"]
    }
    
    buildFeatures {
    	// BuildConfig 사용 설정
        buildConfig = true
    }
    
    ...
    
}

적용시킨 모습은 다음과 같다.

build.gradle 수정 방법

그리고 사용할 RetrofitApi에 적용시킨다.

interface RetrofitApi {
    @GET("v2/search/image")
    suspend fun searchImage(
        @Header("Authorization") apiKey: String = BuildConfig.KAKAO_REST_API_KEY,
        @Query("query") query: String,
        @Query("sort") sort: String,
        @Query("page") page: Int,
    ): Response<ImageResponse>
}

이렇게 했으면 사용준비는 완료다.


결과

API가 잘 연결된 모습


후기

 

API를 숨기는 방법에는 다양한 방법이 있었고 두번째 방법은 구글링을 통해 꽤 많은 정보를 검색해야만 제대로 사용할 수 있었다.

또한 단순히 API를 연결하는 것에도 어려움을 많이 겪었다. 다음은 API 연결 방법에 대해 작성해봐야겠다.

[프로그래머스] 기사단원의 무기 풀이 - Kotlin
Skill/코딩테스트

[프로그래머스] 기사단원의 무기 풀이 - Kotlin

목차1. 문제 설명2. 풀이https://school.programmers.co.kr/learn/courses/30/lessons/136798?language=kotlin 프로그래머스코드 중심의 개발자 채용. 스택 기반의 포지션 매칭. 프로그래머스의 개발자 맞춤형 프로필을 등록하고, 나와 기술 궁합이 잘 맞는 기업들을 매칭 받으세요.programmers.co.kr문제 설명숫자나라 기사단의 각 기사에게는 1번부터 number까지 번호가 지정되어 있습니다. 기사들은 무기점에서 무기를 구매하려고 합니다. 각 기사는 자신의 기사 번호의 약수 개수에 해당하는 공격력을 가진 무기를 구매하려 합니다. 단, 이웃나라와의 협약에 의해 공격력의 제한수치를 정하고, 제한수치보다 큰 공격력을 가진 무기를 구매해야 하는 기사는..

Skill/코딩테스트

[프로그래머스] 기사단원의 무기 풀이 - Kotlin

목차

1. 문제 설명

2. 풀이


https://school.programmers.co.kr/learn/courses/30/lessons/136798?language=kotlin

 

프로그래머스

코드 중심의 개발자 채용. 스택 기반의 포지션 매칭. 프로그래머스의 개발자 맞춤형 프로필을 등록하고, 나와 기술 궁합이 잘 맞는 기업들을 매칭 받으세요.

programmers.co.kr


문제 설명

숫자나라 기사단의 각 기사에게는 1번부터 number까지 번호가 지정되어 있습니다. 기사들은 무기점에서 무기를 구매하려고 합니다.

각 기사는 자신의 기사 번호의 약수 개수에 해당하는 공격력을 가진 무기를 구매하려 합니다. 단, 이웃나라와의 협약에 의해 공격력의 제한수치를 정하고, 제한수치보다 큰 공격력을 가진 무기를 구매해야 하는 기사는 협약기관에서 정한 공격력을 가지는 무기를 구매해야 합니다.

예를 들어, 15번으로 지정된 기사단원은 15의 약수가 1, 3, 5, 15로 4개 이므로, 공격력이 4인 무기를 구매합니다. 만약, 이웃나라와의 협약으로 정해진 공격력의 제한수치가 3이고 제한수치를 초과한 기사가 사용할 무기의 공격력이 2라면, 15번으로 지정된 기사단원은 무기점에서 공격력이 2인 무기를 구매합니다. 무기를 만들 때, 무기의 공격력 1당 1kg의 철이 필요합니다. 그래서 무기점에서 무기를 모두 만들기 위해 필요한 철의 무게를 미리 계산하려 합니다.

기사단원의 수를 나타내는 정수 number와 이웃나라와 협약으로 정해진 공격력의 제한수치를 나타내는 정수 limit와 제한수치를 초과한 기사가 사용할 무기의 공격력을 나타내는 정수 power가 주어졌을 때, 무기점의 주인이 무기를 모두 만들기 위해 필요한 철의 무게를 return 하는 solution 함수를 완성하시오.

제한 사항
  • 1 ≤ number ≤ 100,000
  • 2 ≤ limit ≤ 100
  • 1 ≤ power ≤ limit

해답
class Solution_기사단원의_무기 {
    fun solution(number: Int, limit: Int, power: Int): Int {
        var answer = 0

        // 약수 개수를 담은 배열...
        val cnt = ArrayList<Int>()

        for (i in 1..number) {
            var divisorCount = 0
            var j = 1

            // 약수 개수 구하기...
            while (j * j <= i) {
                if (j * j == i) divisorCount++
                else if (i % j == 0) divisorCount += 2
                j++
            }
            cnt.add(divisorCount)
        }
        
        for (i in cnt.indices) {
            if (cnt[i] > limit) {
                cnt[i] = power
                answer += cnt[i]
            } else {
                answer += cnt[i]
            }
        }
        return answer
    }
}

처음에는 약수 개수를 구하는 방법을 단순히 i까지 반복하는 방식으로 구현했다. 하지만 이 방법을 사용했을 시 시간 초과가 발생했다.

// O(N) 시간 초과 코드... 
while (j <= i) {
    if (i % j == 0) divisorCount++
    j++
}

위 코드를 사용 시 해당 구문의 시간 복잡도는 O(N)이 나온다...
구글링을 통해 더 효율 좋은 코드를 검색해 본 결과 O(N^1/2)에 효율이 나오는 방법을 찾을 수 있었다.

// O(N^1/2)
while (j * j <= i) {
    if (j * j == i) divisorCount++
    else if (i % j == 0) divisorCount += 2
    j++
}

O(N^1/2)에 시간 복잡도를 가지는 위의 코드는 약수의 개수는 제곱근을 제외하곤 짝을 이룬다는 성질을 이용해 구현한 코드이다. 따라서 기존의 코드의 절반의 반복을 통해 같은 결괏값을 얻을 수 있었다.

 


총평

알고리즘을 구현하는 것에 대해 시간 복잡도의 개념을 알고 있었지만 시간 복잡도가 실제 코드의 성능과 직접적인 연관이 있다는 사실을 체감할 수 있는 문제였다. 다음 문제를 풀 때는 코드의 효율성에 대해 고려를 하면서 진행해야겠다.

[Flutter 오류] GridView 자식 위젯의 overflow 발생
Skill/Flutter

[Flutter 오류] GridView 자식 위젯의 overflow 발생

목차1. 문제 발생 2. 해결 방안 3. 해결 완료 1. 문제 발생Shazam의 기본 페이지를 따라 만드는 도중 GridView 자식 위젯의 bottom overflow 발생...자식 위젯인 Container에서 height 설정, GridView의 shrinkWrap 설정 등 최대한 아는 다양한 방법을 시도해봤지만 실패...2. 해결 방안Expanded( child: GridView.count( // crossAxisCount is the number of columns crossAxisCount: 2, // > childAspectRatio: 0.75, // 자식의 비율 설정... // This creates two columns ..

Skill/Flutter

[Flutter 오류] GridView 자식 위젯의 overflow 발생

 

목차

1.  문제 발생
2.  해결 방안
3.  해결 완료

 

1.  문제 발생

Shazam의 기본 페이지를 따라 만드는 도중 GridView 자식 위젯의 bottom overflow 발생...

overflow 발생

자식 위젯인 Container에서 height 설정, GridView의 shrinkWrap 설정 등 최대한 아는 다양한 방법을 시도해봤지만 실패...

2.  해결 방안

Expanded(
      child: GridView.count(
        // crossAxisCount is the number of columns
        crossAxisCount: 2,

		// <<해결 방안>>
        childAspectRatio: 0.75, // 자식의 비율 설정...

        // This creates two columns with two items in each column
        children: List.generate(songs.length, (index) {

그러다 찾은 해결 방안은 GridView.count의 설정인 childAspectRatio의 값을 설정해주는 것

 

childAspectRatio 옵션은 말 그대로 GridView의 자식 위젯의 비율을 정하는 옵션입니다. 

저의 경우 자식 위젯이 bottom overflow가 발생했음으로 세로방향의 길이를 늘려야했습니다. 따라서 해당 옵션의 값을 1보다 작게 줘 길이를 세로로 늘렸습니다.

 

정확히는 childAspectRatio : 가로 / 세로, 의 형태를 갖춥니다.

 

보다 자세한 설명은 아래의 블로그를 참고하면 좋습니다.

 

Flutter 플러터에서 GridView 위젯의 사용자 정의 높이를 설정하는 방법은 무엇인가요?, How to set Custom

질문 Container GridView의 높이를 지정한 후에도 내 코드는 정사각형 위젯을 생성합니다. class MyHomePage extends StatefulWidget { MyHomePage({Key key, this.title}) : super(key: key); final String title; @override _MyHomePageState c

stcodelab.com

 

3. 해결 완료

오류 해결

overflow 오류가 해결된 모습입니다.

[JAVA] JAVA swing을 이용한 간단한 이미지 변경 프로그램 만들기
Skill/JAVA

[JAVA] JAVA swing을 이용한 간단한 이미지 변경 프로그램 만들기

안녕하세요.JAVA swing을 이용해 간단한 이미지 변경 프로그램을 만들어보았습니다.목차1. 프레임 생성 및 설정 2. 패널 생성 및 설정 3. 버튼, 라벨, 이미지 라벨 생성 및 설정 4. 패널에 컴포넌트 추가 및 프레임에 추가 5. 버튼 클릭 시 동작 추가 6. 완성 1. 프레임 생성 및 설정// 프레임 생성JFrame frame = new JFrame("Pokemon");frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);// 프레임 크기 설정frame.setSize(400, 400);frame.setLocationRelativeTo(null); // 프레임을 화면 가운데에 배치frame.setResizable(false); // 프레임 ..

Skill/JAVA

[JAVA] JAVA swing을 이용한 간단한 이미지 변경 프로그램 만들기

안녕하세요.

JAVA swing을 이용해 간단한 이미지 변경 프로그램을 만들어보았습니다.

목차

1.  프레임 생성 및 설정
2.  패널 생성 및 설정
3.  버튼, 라벨, 이미지 라벨 생성 및 설정
4.  패널에 컴포넌트 추가 및 프레임에 추가
5.  버튼 클릭 시 동작 추가
6.  완성

 

1.  프레임 생성 및 설정

// 프레임 생성
JFrame frame = new JFrame("Pokemon");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

// 프레임 크기 설정
frame.setSize(400, 400);
frame.setLocationRelativeTo(null); // 프레임을 화면 가운데에 배치
frame.setResizable(false); // 프레임 창 크기 고정(false)

// 프레임 표시
frame.setVisible(true);

프로그램의 기본적인 프레임을 생성해 줍니다. 추후에 이 프레임 안에 버튼, 메시지 창 등을 추가하게 됩니다.

프로그램 실행 시 화면 가운데 위치하는 것을 원했기 때문에 setLocationRelativeTo() 함수를 이용해 실행 시 바로 화면 중앙에서 실행되게 설정했습니다.

또한 프로그램의 창크기를 고정하기 위해 setResizable() 함수를 이용해 창크기의 변경을 막아놨습니다.

 

2.  패널 생성 및 설정

// 이미지 라벨 패널 생성
JPanel imgLabelPanel = new JPanel();
imgLabelPanel.setLayout(new FlowLayout());
imgLabelPanel.setBorder(BorderFactory.createEmptyBorder(0, 0, 0, 0));

// 버튼 패널 생성
JPanel buttonPanel = new JPanel();
buttonPanel.setLayout(new FlowLayout());

// 대화창 패널 생성
JPanel labelPanel = new JPanel();
labelPanel.setLayout(new BorderLayout());
labelPanel.setBackground(Color.WHITE);
labelPanel.setBorder(BorderFactory.createLineBorder(Color.GRAY));
labelPanel.setBorder(BorderFactory.createStrokeBorder(new BasicStroke(2.0f)));

프레임 안에서 각각의 종류가 다른 컴포넌트를 넣기 위한 패널을 생성합니다. 각각의 패널에 대한 정보도 설정해 줍니다.

대화창의 패널은 레이아웃을 BorderLayout으로 설정했습니다. 이는 대화창에서의 메시지를 가운데의 띄우기 위한 선행작업입니다.

 

3. 버튼, 라벨, 이미지 라벨 생성 및 설정

먼저 사용할 이미지를 찾아 해당 프로젝트 내에 이미지 파일을 넣습니다. 저는 따로 images라는 폴더를 만들어 이미지를 추가해 줬습니다.

images 폴더 내 이미지 추가

그러고 나서 이미지를 불러오는 코드를 작성해 줍니다.

// 이미지 로드
ImageIcon image = null;
try {
    image = new ImageIcon(ImageIO.read(main.class.getResource("images/pichu.png")));
} catch (IOException e) {
    e.printStackTrace();
}

해당 코드로 이미지를 불러오게 됩니다.

 

이후 이미지가 들어갈 라벨, 대화창에 쓰일 라벨 그리고 상호작용 가능한 버튼들을 생성합니다.

// 이미지를 표시할 라벨 생성
JLabel imageLabel = new JLabel(image);
imageLabel.setPreferredSize(new Dimension(200, 200));

// 흰색 배경의 라벨 생성
JLabel backgroundLabel = new JLabel();
backgroundLabel.setOpaque(true);
backgroundLabel.setBackground(Color.WHITE);

// 라벨 생성
JLabel label = new JLabel("<html><body style='text-align:center;'>오잉??<br> 피츄의 상태가...?</html>");
label.setHorizontalAlignment(JLabel.CENTER);

// 버튼 생성
JButton OKBtn = new JButton("진화");
Dimension OKBtn_buttonSize = new Dimension(50, 30);
OKBtn.setPreferredSize(OKBtn_buttonSize);

JButton NoBtn = new JButton("취소");
Dimension NoBtn_buttonSize = new Dimension(50, 30);
NoBtn.setPreferredSize(NoBtn_buttonSize);

JButton resetBtn = new JButton("초기화");
Dimension resetBtn_buttonSize = new Dimension(70, 30);
resetBtn.setPreferredSize(resetBtn_buttonSize);
resetBtn.setEnabled(false);

버튼과 라벨을 만들고 버튼의 텍스트, 크기 배경 색깔 등의 세부 설정을 해줍니다. 중간에 대화창에 쓰일 lable은 텍스트의 중앙 정렬, 줄 넘김 등을 사용하기 위해 html 문법을 사용했습니다.

 

4.  패널에 컴포넌트 추가

// 버튼 패널에 버튼 추가
buttonPanel.add(OKBtn);
buttonPanel.add(NoBtn);
buttonPanel.add(resetBtn);

// 이미지 패널에 이미지 추가
imgLabelPanel.add(imageLabel);

// 대화 패널에 라벨 추가
labelPanel.add(label, BorderLayout.CENTER);

// 프레임에 추가
frame.setLayout(new BorderLayout());
frame.add(imgLabelPanel, BorderLayout.NORTH); // 이미지 패널 추가
frame.add(labelPanel, BorderLayout.CENTER); // 대화 패널 추가
frame.add(buttonPanel, BorderLayout.SOUTH); // 버튼 패널 추가

앞서 만든 패널에 해당하는 컴포넌트들을 추가해 줍니다.

BorderLayout의 특징을 이용해 각각의 패널이 프레임 어느 곳에 위치할지 특정해 줍니다.

 

5.  버튼 클릭 시 동작 추가

 

// 버튼에 액션 리스너 추가
OKBtn.addActionListener(new ActionListener() {
    @Override
    public void actionPerformed(ActionEvent e) {
        try {
            ImageIcon afterimage = null;
            afterimage = new ImageIcon(ImageIO.read(Pokemon.class.getResource("images/pikachu.png")));
            imageLabel.setIcon(afterimage);

        } catch (IOException ex) {
            ex.printStackTrace();
        }
        label.setText("<html><body style='text-align:center;'>축하합니다!<br> 피츄는 피카츄로 진화했습니다!</html>");
        NoBtn.setEnabled(false);
        OKBtn.setEnabled(false);
        resetBtn.setEnabled(true);
    }
});

NoBtn.addActionListener(new ActionListener() {
    @Override
    public void actionPerformed(ActionEvent e) {
        try {
            ImageIcon afterimage = null;
            afterimage = new ImageIcon(ImageIO.read(Pokemon.class.getResource("images/pichu2.png")));
            imageLabel.setIcon(afterimage);

        } catch (IOException ex) {
            ex.printStackTrace();
        }
        label.setText("<html><body style='text-align:center;'>피츄는 진화하지 않기로 했다...</html>");
        NoBtn.setEnabled(false);
        OKBtn.setEnabled(false);
        resetBtn.setEnabled(true);
    }
});

resetBtn.addActionListener(new ActionListener() {
    @Override
    public void actionPerformed(ActionEvent e) {
        try {
            ImageIcon afterimage = null;
            afterimage = new ImageIcon(ImageIO.read(Pokemon.class.getResource("images/pichu.png")));
            imageLabel.setIcon(afterimage);
        } catch (IOException ex) {
            ex.printStackTrace();
        }
        label.setText("<html><body style='text-align:center;'>오잉??<br> 피츄의 상태가...?</html>");
        NoBtn.setEnabled(true);
        OKBtn.setEnabled(true);
        resetBtn.setEnabled(false);
    }
});

각 버튼에 클릭 시 이벤트를 설정해 줍니다. 앞서 만들어줬던 버튼들에 해당하는 기능이 작동할 수 있도록 적절한 코드를 넣어줍니다.

이 프로그램은 버튼 클릭 시 이미지가 변환하는 프로그램이기 때문에 버튼이 클릭되면 해당하는 알맞은 이미지가 나올 수 있도록 설정해 줬습니다. 또한 초기화 버튼을 누르면 초기 상태로 돌아갈 수 있습니다.

이미지뿐만 아니라 해당하는 대화창의 내용도 바뀌어야 하기 때문에 label의 텍스트 또한 변경해 주었습니다.

그 외에도 진화/취소 버튼은 동시에 작동할 수 없기 때문에 setEnabled를 사용해 한 개의 버튼이 클릭되고 나면 나머지 한 개의 버튼을 비활성화되게 처리했습니다. 반대로 진화/취소 버튼이 활성화가 되어있을 때 초기화 버튼을 비활성화 처리해 주었습니다.

 

6.  완성

 

완성된 프로그램

프로그램의 완성된 모습은 다음과 같습니다.