오늘도 어김없이 열심히 코딩했다. 점점 코딩속도도 빨라지는거 같았다. 코드가 별다른게 없어서 그런거 일지도 모른다. 이번에는 처음 다뤄보는 개념이 등장했는데, 본문에서 정리하도록 하겠다. 아마 이것도 간단하게만 정리해놓고 따로 포스팅으로 다뤄야 할거 같다.
# 결과물 미리보기
금고를 생각해보면 된다. 3개의 다이얼( 0 ~ 9 ) 까지 돌려서 버튼을 누르면 비밀번호를 비교한다. 검정버튼을 누르면 비밀번호를 바꾸는 모드이다. 그럴경우 저 버튼 색이 노란색으로 바뀌게 되고 다시 버튼을 누르면 다이얼의 번호로 번호가 다시 바뀐다. 그리고 들어가지는 메모장에서는 자유롭게 메모를 작성하고 창을 나갔다가 다시 들어와도 메모가 저장된다. 3번째 GIF는 EditText에 쓰인 글이 바로바로 저장이 되게 한 것이 아님을 로그와 함께 보여주기 위해 첨부했다. 바뀔때마다 putExtra를 하게되면 입력이 새로운 입력이 0.5초간 없으면 그때마다 저장하게 됨을 알 수 있다.
# 알게 된 것
- View를 lazy하게 초기화 또 다른 이유 ( 저번 포스팅과 다름 ) 🔥
- 내가 다운 받은 Font를 사용하는 방법 🔥
- 상단 ActionBar를 없애는 방법 🔥
- AlertDialog 사용방법 🔥
- SharedPreferences 사용 방법, commit() , apply() 차이 🔥
- AppCompatButton을 사용하는 이유 🔥
- handler()
- Runnable { ... }
## View를 lazy하게 초기화 하는 또 다른 이유 🔥
private val numberPickerFirst : NumberPicker by lazy {
findViewById<NumberPicker?>(R.id.numberPicker_first).apply {
minValue = 0
maxValue = 9
}
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
numberPickerFirst
}
View들을 위와 같이 class의 전역변수로 선언했는데, 이렇게하는 또다른 이유가 있었다. 바로 class가 생성되는 시점과 oncCreate()가 호출되는 시점이 다르기 때문이라고 한다. 즉 onCreate가 되기전에 먼저 호출되면 원하는 View와 연결되지 않을 수 있기 때문에 미리 null로 만들어 놓고 쓰임이 있을때 부착시키는 것이다.
## Font 적용 방법 🔥
- 원하는 폰트 다운
- res -> font(폴더생성) -> 복사&붙여넣기 ( 파일 이름은 소문자로 )
- fontFamily 속성에 추가
## 기본 ActionBar 없애기 🔥
앱을 만들면 정말 별로인 상단바가 생긴다. 이걸 없애줘보자.
- res -> values -> themes -> themes.xml
<resources xmlns:tools="http://schemas.android.com/tools">
<!-- Base application theme. -->
<style name="Theme.SecretDiary" parent="Theme.MaterialComponents.DayNight.DarkActionBar">
<!-- Primary brand color. -->
<item name="colorPrimary">@color/purple_500</item>
<item name="colorPrimaryVariant">@color/purple_700</item>
<item name="colorOnPrimary">@color/white</item>
<!-- Secondary brand color. -->
<item name="colorSecondary">@color/teal_200</item>
<item name="colorSecondaryVariant">@color/teal_700</item>
<item name="colorOnSecondary">@color/black</item>
<!-- Status bar color. -->
<item name="android:statusBarColor" tools:targetApi="l">?attr/colorPrimaryVariant</item>
<!-- Customize your theme here. -->
</style>
<style name="Theme.SecretDiary.NoActionBar" parent="Theme.MaterialComponents.DayNight.NoActionBar">
</style>
</resources>
기존 name에 " . " 을이용해 패키지를 하나더 만들고 , NoActionBar를 선택 한 후 , manifests
<activity android:name=".DiaryActivity"
android:theme="@style/Theme.SecretDiary.NoActionBar"/>
적용할 액티비티에 새로만든 theme 적용
## AlertDialog 만들기 🔥
안드로이드는 몇가지의 정형화 된 Dialog를 지원한다. 그래서 보다 쉽게 만들 수 있다.
AlertDialog.Builder(this) // this == context
.setTitle("다이얼로그 제목")
.setMessage("다이얼로그 본문")
.setPositiveButton("확인") { _, _ -> } // 언더바는 사용하지 않는다는 의미
.create()
.show()
이렇게 만들어 사용하면 된다. 근데 나는 이 다이얼로그의 내용만다르게 몇번 사용해서 아래와같이 함수를 만들어 사용했다. 모름지기 반복되는 코드는 최대한 제거하라는 말을 잘 지킨거 같아서 뿌듯했다.
private fun failDialog(title: String, message: String) {
AlertDialog.Builder(this)
.setTitle(title)
.setMessage(message)
.setPositiveButton("확인") { _, _ -> }
.create()
.show()
}
failDialog("비밀번호","비밀번호를 확인해주세요")
failDialog("비밀번호","비밀번호 재설정 중")
## SharedPreferences 사용 방법, commit() , apply() 차이 🔥
SharedPreferences를 사용하면 로컬에서 해당 값을 계속 가지고 있을 수 있다. 저번 프로젝트를 하면서 꽤 많이 썼는데, 예를들면 한번 로그인한 사용자인지 판단하고, 다음부터는 로그인을 하지 않아도 되겠끔와 같은 처리를 해줄 수도 있다. 작고 간단한DB라고 생각하면 좋다.
// getSharedPreferences("프레퍼런스 이름", "모드")
val preferences = getSharedPreferences("password", Context.MODE_PRIVATE)
// preferences에 접근하여 "password"키를 찾고 없다면 "000"을 기본값으로 설정
val curPassword = preferences.getString("password","000")
MODE에 따라 다른 앱에서 접근을 허용할 것 인지 아닌지를 정할 수 있다. 위와같이 프레퍼런스를 만들고 가져와서 사용할 수 있다.
// 프레퍼런스에 넣어줄 때
val preferences = getSharedPreferences("password", Context.MODE_PRIVATE)
preference.edit {
putString("password",userPassword)
commit()
}
edit을 사용해 안에다가 뭘 넣어줄지 정의한다. commit()과 apply() 두가지로 저장을 결정해줄수 있는데 commit()은 동기로 처리되고 apply()는 비동기로 처리된다. 너무 오래걸리는 작업을 commit()으로 처리하면, 처리되는동안 다른 작업을 할 수 없다.
## AppCompatButton을 사용하는 이유 🔥
위와 같이 그냥 Button을 사용할 경우, theme에 적용된 기본 버튼이 사용되어 background 변경이 어렵고(가능은 함) 또 기본으로 지정된 속성들이 적용되기 때문에 해당 AppCompatButton으로 버튼을 만들면 최소 기능만 적용된 버튼을 구현하여 입맛에 맞게 바꿔 줄 수 있다.
## 메모장에 정해진 시간만큼 입력이 없을경우 저장시키기
-> 이건 쓰레드를 다루는 개념이 처음이라 확실히 더 공부해야겠다. 이번 앱을 만들면서 필요했던 흐름과 개념에대해서만 간단히 적도록 하겠다.
private val handler = Handler(Looper.getMainLooper())
val runnable = Runnable {
getSharedPreferences("diary",Context.MODE_PRIVATE).edit {
putString("detail",diaryEditText.text.toString())
}
// 입력 창 변화 감지
diaryEditText.addTextChangedListener {
detailPreferences.edit {
Log.d(tag,"Change!!")
putString("detail",diaryEditText.text.toString())
handler.removeCallbacks(runnable)
handler.postDelayed(runnable,500L)
}
}
Handler에 Looper.getMainLooper()를 적용하여 만든 handler는 자동으로 메인쓰레드와 연결이 된다. 이제 글자를 입력하면 addTextChangedListener이 실행되고 그안에가 돌게 된다. 그러나 Runnalbe이 이전것이 이미 존재한다면 지워버린다. 만약 0.5초(지정한시간)이 지나면 이걸 runnalbe를 다시 보내서 메인쓰레드가 처리하도록 한다.
메인 쓰레드가 아닌 곳에서는 메인 UI를 변경하는 작업을 할 수 없다고 했다. 일단 나중에 이 부분은 수정하도록 하고 글 맨 하단에 관련된 레퍼런스를 달아 놓도록 하겠다.
# 앱을 만들며 느낀 것
코틀린에 다양한 기능이 있다는 것을 알게 되면서 언어 자체에도 매력을 느끼고 있다. 또한 람다함수를 저번에 확실히(?) 이해해서 그런가 안드로이드에서 제공하는 함수들을 사용할때 뭔가 의도에 맞게 한번에 사용할 수 있는거 같다. 근데 난 이 앱은 어떻게 디자인을 하나씩 넣는지 디자인을 어떻게 해야하는지 도무지 감이 안온다..
# 궁금한 점
- 쓰레드 개념과 핸들러, 메시지에 대해서 공부가 필요
- 다이어리 글을 수정하는 작업을 할때, 왜 commit()또는 apply()를 사용하지 않았고, 사용하지 않아도 작동하는 이유는 무엇일까?
- 전역에 글씨 폰트를 한번에 변경할 수 있는 방법은 없을까? ( 물론 있겠지.. ㅋㅋ )
# 1
#2