1์ฃผ์ฐจ
bandicam.2021-10-10.11-58-38-060.mp4
binding.loginButton.setOnClickListener {
if (binding.inEditId.text.toString() != "" && binding.inEditPw.text.toString() != "") {
val intent = Intent(this,HomeActivity::class.java)
startActivity(intent)
Toast.makeText(this, "์ด์ฐฝํ๋ ํ์ํฉ๋๋ค", Toast.LENGTH_SHORT).show()
} else {
binding.inEditId.text.clear()
binding.inEditPw.text.clear()
Toast.makeText(this, "๋ก๊ทธ์ธ์คํจ", Toast.LENGTH_SHORT).show()
}
}
edit text์ ๋ด์ฉ์ด ์๋ ํ์ธํ ์๋ค๋ฉด ํ ์คํธ๋ฉ์ธ์ง๋ฅผ ๋์ฐ๋ฉฐ ์ธํ ํธ ํ๋๋ถ๋ถ
getResult = registerForActivityResult(
ActivityResultContracts.StartActivityForResult()){
if(it.resultCode == RESULT_OK) {
binding.inEditId.text.clear()
binding.inEditId.text.append(it.data?.getStringExtra("Id"))
binding.inEditPw.text.clear()
binding.inEditPw.text.append(it.data?.getStringExtra("Pw"))
}
}
signup์ผ๋ก ๋์ด๊ฐ๋ค ๋์์์๋ ๋ฐ์ดํฐ ๋ฐ์์์ ์ฒ๋ฆฌํ๋ ํํธ
binding.signUpDone.setOnClickListener{
if(binding.upEditId.text.toString() != "" && binding.upEditPw.text.toString() != "" && binding.upEditName.text.toString() != ""){
val intent = Intent(this,SignInActivity::class.java).apply {
this.putExtra("Id",binding.upEditId.text.toString())
this.putExtra("Pw",binding.upEditPw.text.toString())
}
setResult(RESULT_OK,intent)
finish()
}else{
Toast.makeText(this,"์
๋ ฅ๋์ง์์ ์ ๋ณด๊ฐ ์์ต๋๋ค",Toast.LENGTH_SHORT).show()
}
}
finish๋ก ๋์๊ฐ๋ฉด์ ๊ฐ์ ธ๊ฐ์ผํ๋ ๋ฐ์ดํฐ๋ค putExtra๋ก ๊ฐ์ ธ๊ฐ๋๋ถ๋ถ
binding.homeToGit.setOnClickListener {
val intent = Intent(Intent.ACTION_VIEW, Uri.parse("https://github.com/2chang5"))
startActivity(intent)
}
์์์ ์ธํ ํธ๋ก ์น์ผ๋ก ๋์ด๊ฐ๋๋ถ๋ถ
edittext ๋ฅผ ์ฝ๋๋จ์์ ํ ์คํธ๋ฅผ ๋ฃ์ด์ฃผ๊ธฐ์ํด
์ด๋ฐ์์ผ๋ก ํ ์คํธ์ ์ง์ ๋ฌธ์์ด์ ๋ฃ์ด์คฌ๋๋ฐ ์๋ฃํ์ด ์๋ง์์ ์ ์ฉํ ์๊ฐ ์์๋ค. edittext์ ํ ์คํธ๋ Editable TYPE์ด์๋๋ฐ Editable๋ผ๋ ์ธํฐํ์ด์ค๋ฅผ ๊ตฌํํ ๊ฐ์ฒด์ด๋ฏ๋ก Editable ์์ ์ ์๋ clear() append()๊ฐ์ ๋ฉ์๋๋ฅผ ์ฌ์ฉํด์ ์กฐ์ํ ์์์๋ค.
๊ธฐ์กด์ ๋งจ๋ ํ์ด์ง๋ฅผ ์ด๋ํ ๋ startActivity๋ฅผ ํตํด์๋ง ์์ง์๊ณ ๋ญ๊ฐ ์ด์ํ๋ค. ์คํ์ ์์ฌ์๋ ๊ฑฐ์ณ์๋ ํ๋ฉด์ผ๋ก ๋์๊ฐ๋๋ back ๋ฒํผ์ ๋ฌผ๋ฆฌ์ ์ผ๋ก ๋๋ฌ์ ๋์๊ฐ๋๋ฐ ์ด๋ ๊ฒ ๊ธฐ์กด ์คํ์ ์ต์๋จ์์๋ ํ๋ฉด์์ ์คํ์ ์์ฌ์๋ ํ๋ฉด์ผ๋ก ๋์๊ฐ๊ธฐ ์ํด ๋ฐฑ๋ฒํผ๊ณผ ๊ฐ์ ํจ๊ณผ๋ฅผ ๊ฐ์ง finish() ๋ฅผ ํตํด์ ํ๋ฉด์ ์ข ๋ฃํ๊ณ ๊ธฐ์กด์ ํ๋ฉด์ผ๋ก ๋์๊ฐ์์๋ค.
์ฌ์ฉ์์
image view๋ฅผ ์ฌ์ฉํ ๋ ๊ธฐ์กด์๋ srcCompat ์์ฑ์ ์์ค๋ฅผ ์ค์ ํ๋๋ฐ ์ด๋ฒ์ ์ด์ํ๊ฒ ์ด๋ฏธ์ง๊ฐ ๋ก๋๋์ง ์์๋ค ๊ตฌ๊ธ๋งํด๋ณธ๊ฒฐ๊ณผ
srcCompat์ Android Support Library์ ํฌํจ๋ ๋ฐฉ์(method of work)์ด๋ค. (AppCompat์ ์์) ์๋๋ก์ด๋ ์ํฌํธ ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ฅผ ๊ฐ๋จํ๊ฒ ์๋ฏธํ๋ฉด '์ด๋ ๋ฒ์ ์์๋ ๋๊ฐ์ด ๊ตฌํํ ์ ์๋'์ ํํ๋ ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ผ๊ณ ์ ์ํ ์ ์๋ค. srcCompat์ vector Drawables(์ฆ, ๊ทธ๋ฆผ)๋ฅผ ๋ชจ๋ ์๋๋ก์ด๋์์ ํํํ๊ฒ ํด์ฃผ๋ ์๋ ์ํฌํธ ๋ผ์ด๋ธ๋ฌ๋ฆฌ ์์ ๊ตฌํ๋ ๊ธฐ๋ฅ์ด๋ค. ๊ทธ๋ฌ๋ฏ๋ก ๋ด๊ฐ ํด๋น ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ฅผ ์ฐ๊ณ ์๋ ๊ฒ์ด ์๋๋ผ๋ฉด ๋น์ฐํ srcCompat์ผ๋ก ๋ฆฌ์์ค๋ฅผ ์ง์ ํด๋ดค์ ์๋ฎฌ๋ ์ดํฐ๊ฐ ์ ๋๋ก ๊ทธ๋ ค์ค ๋ฆฌ ์๋ค. ๊ทธ๋ฌ๋ฏ๋ก srcCompat์ ์ฌ์ฉํ์ฌ ์ด๋ฏธ์ง๋ฅผ ๊ทธ๋ฆฌ๊ณ ์ถ๋ค๋ฉด ImageViewํ๊ทธ๊ฐ ์๋ android.support.v7.widget.AppCompatImageView๋ฅผ ์ฌ์ฉํด์ผ ํ๋ค. ๋ํ ๋ค์์คํ์ด์ค๋ ์ง์ ํด์ค์ผ ํ๋ค (xmlns:app="http://schemas.android.com/apk/res-auto")
minSDKversion์ด ๋กค๋ฆฌํ(5.0, level 21)์ด์์ด๋ผ๋ฉด src๋ฐฉ์์ ์ฌ์ฉํ ์ ์๋๋ฐ ์ด๋๋ถํฐ ์๋๋ก์ด๋์์ ๋จธํ ๋ฆฌ์ผ ๋์์ธ์ด ์๊ฒผ๊ธฐ ๋๋ฌธ์ด๋ค. src์ ๊ฒฝ์ฐ xml์์ ImageViewํ๊ทธ์ผ ๊ฒฝ์ฐ์ ์ฌ์ฉํ ์ ์์ผ๋ฉฐ ์ด๋๋ถํฐ src ์ค์ ์ ์ฌ์ฉํ์ฌ ์ด๋ฏธ์ง๋ฅผ ๋ฃ์ ์ ์๋ค. srcCompat๊ณผ ๋ฌ๋ฆฌ ๋กค๋ฆฌํ ์ด์๋ถํฐ ๊ธฐ๋ณธ์ผ๋ก ์ง์ํ๋ฉฐ ๋กค๋ฆฌํ ์ด์ ๋ฒ์ ์ ์ง์ํด์ผ ํ ๊ฒฝ์ฐ์๋ ๋ฐ๋์ srcCompat์ ์ฌ์ฉํด์ผ ํ๋ค.
๊ณผ๊ฑฐ์๋ ๋ชจ๋ ๋ฒ์ ์ ์ปค๋ฒํ ์ ์๋ค๋ ์ ๋๋ฌธ์ srcCompat์ด ๋๋ฆ ๊ฐ์น๊ฐ ์์์ผ๋ ํ์ฌ๋ ๋๋ถ๋ถ์ ์ฌ๋๋ค์ด ์ต์ ๋กค๋ฆฌํ ์ด์์ ๋ฒ์ ์ ์ฐ๊ธฐ ๋๋ฌธ์ ์ฅ์ ์ด ์๋นํ ํฌ์๋์๋ค.
์ถ์ฒ: https://ammff.tistory.com/100 [์๋ฉ๋ฆฌ์นด๋ ธ๊ฐ ๊ทธ๋ ๊ฒ ๋ง์๋ต๋๋ค ์ฌ๋ฌ๋ถ]
์ด๋ ๋ค๊ณ ํ๋ค.
๊ทธ๋์ src ์์ฑ์ผ๋ก ๋ฐ๊พธ๋ ํด๊ฒฐ๋์๋ค.
(์ถํ์ ํ๋ฒ ์ดํด๋ณผ๊ฒ: parcelable๋ก ๊ฐ์ฒด ์ ๋ฌํด๋ณด์)
์๋๋ ๊ธฐ์กด์ startActivityForResult() ์ onActivityResult()์ ์ฌ์ฉํ์๋๋ฐ deprecated ๋๊ณ ๊ทธ ๋์ฉ์ผ๋ก registerForActivityResult๊ฐ ๋ค์ด์๋ค.
์ฉ๋๋ฅผ ์ดํด๋ณด์๋ฉด
startActivity : ์ ์กํฐ๋นํฐ๋ฅผ ์ด์ด์ค (๋จ๋ฐฉํฅ)
registerForActivityResult : ์ ์กํฐ๋นํฐ๋ฅผ ์ด์ด์ค + ๊ฒฐ๊ณผ๊ฐ ์ ๋ฌ (์๋ฐฉํฅ)
๊ฐ๋จํ ๋งํด์ ์กํฐ๋นํฐ๋ฅผ ์ด๋ ๊ฐ๋๋ ํ๋๋๋ก putExtra() ๋ฅผ ์ด์ฉํด์ ๋ฐ์ดํฐ ์ ๋ฌํ๊ณ ์ด๋ฆฐ ์กํฐ๋นํฐ๊ฐ finish๋ก ์ข ๋ฃ๋์์๋ ํ์ํ ์ ๋ณด๋ฅผ ๊ฐ์ง๊ณ ๊ธฐ์กด ์กํฐ๋นํฐ๋ก ๋์์ฌ์์๊ฒ ํ๋๊ฒ์ด๋ค. ๊ทธ๋ฆฌ๊ณ ์ข ๋ฃ์์ ์ ์ฝ๋๋ฅผ ์คํํด์ฃผ์ด ๊ฐ์ง๊ณ ์จ ๋ฐ์ดํฐ๋ฅผ ์ฒ๋ฆฌํ ์์๋ค.
๊ธฐ์กด startActivityForResult() ์ ๋น๊ตํ์ฌ ์ฅ์ ์ ์ด๋ฌํ๋ค.
-๋์ปคํ๋ง ๋ฐ ๊ด์ฌ์ฌ ๋ถ๋ฆฌ : ๊ธฐ์กด ์กํฐ๋นํฐ ๋๋ ํ๋ ๊ทธ๋จผํธ์ onActivityResult์์ if์ else if๋ก ๋๋ฐฐ๋๋ ๋น์ฆ๋์ค ๋ก์ง๋ค์ด ์ฝ๋ฐฑ๋ฉ์๋ ๋๋ ๋ถ๋ฆฌ๋ ํด๋์ค ๋จ์๋ก ์ชผ๊ฐ์ด์ ธ์ ๊ด๋ฆฌ๋ ์ ์๋ค. ์ด๋ ์ฝ๋์ ๊ฐ๋ ์ฑ์ ๋์ด๊ณ , ์ ๋ํ ์คํธ๋ฅผ ์์ํ๊ฒ ํ๋ฉฐ, ์ ์ง๋ณด์์ธก๋ฉด์์๋ ๋ง์ ๋์์ด ๋๋ค.
-Type-Safety : ActivityResultContract๋ ์ ๋ ฅ ๋ฐ์ดํฐ์ ์ถ๋ ฅ ๋ฐ์ดํฐ์ ํ์ ์ ๊ฐ์ ํ๊ธฐ ๋๋ฌธ์ ์๋ชป๋ ํ์ ์ผ๋ก ์บ์คํ ํ๋ ์ฌ์ํ ์ค์๋ฅผ ๋ฏธ์ฐ์ ๋ฐฉ์ง์์ผ์ค๋ค.
-NPE ๋ฐฉ์ง : Intent๋ก ๋ถํฐ ๋ฐ์ดํฐ๋ฅผ ์ป์ผ๋ ค๊ณ ํ ๋ NullPointerException์ด ๋ฐ์ํ๋ ๊ฒฝํ์ ๋๊ตฌ๋ ํ๋ฒ์ฏค์ ํด๋ณด์์ ๊ฒ์ด๋ค. ์๋ก์ด API๋ NPE๊ฐ ๋ฐ์ํ ํ๋ฅ ์ ์ค์ฌ์ค ๊ฒ์ด๋ค
์์ ๋งํฌ์ ๋ด๋ถ์ ์ผ๋ก ์ด๋ป๊ฒ ๋์๊ฐ๋์ง์๋ํด ์์ธํ ๋์์๋ค. contract๋ฅผ ์ง์ ์ ์ํ๊ณ ๋ฑ๋กํ์ฌ ์ฌ์ฉํ ์๋์์ง๋ง ์ด๋ฏธ ์ ์๋ contract๋ฅผ ์ฌ์ฉํ ์๋์๋ค.
์ด๋ ๊ฒ ์ ์๋ contract๋ฅผ ์ ์ ํ ์๊ธฐ์ ์ฌ์ฉํ๋ฉด๋๋ค.
์๋ฅผ๋ค์ด ์ด๋ฏธ์ง ๋ฑ์ ์ปจํ ์ธ ์ uri๋ฅผ ๋ฐ์์ค๊ณ ์ถ๋ค๋ฉด getContent๋ฅผ ์ฌ์ฉํ๊ฒ๋๋์์ด๋ค.
์ด์ ๊ฐ๋จํ๊ฒ ์ฌ์ฉ๋ฒ์ ๋ณด์๋ฉด
a์ํฐ๋นํฐ์์ b ์ํฐ๋นํฐ๋ก ๋์ด๊ฐ๋ค๊ฐ ์ ๋ณด๋ฅผ ๋ค๊ณ a๋ก ๋ค์ ๋์ด์ค๋ ์ํฉ์ด๋ค ์ฐ์ a์กํฐ๋นํฐ์์๋ registerForActivityResultํจ์๋ฅผ ์ด์ฉํด์ callback์ ๋ฑ๋กํด์ค๋ค. result๋ฅผ ๋ฐ๊ธฐ์ํด StartActivityForResult๋ฅผ ์ด์ฉํ๋ค.
๋๋ค์ ์์์ ๊ทธ๋์ result๋ก ๋์ด์ค๋ ๊ฒฐ๊ณผ๋ฅผ resultCode๋ฅผ ํ์ธํ๊ณ data๋ฅผ ๋ฐ์์ ๋ฐ์ดํฐ ์ฒ๋ฆฌ๋ฅผ ํด์ฃผ๋ฉด๋๋ค. data์๋ intent์ putExtra๋ก ๋ฃ์ด๋์๊ฒ๋ค์ ๋ฝ์์์ธ์์๋ค.
๊ทธ๋ฆฌ๊ณ ์ด์ lacuch๋ฅผ ์์ผ์ค์ผํ๋๋ฐ intent์ ๋์ด๊ฐ๋ ค๋๊ฒ๋ค ์์ ๋ฃ์ด์ฃผ๊ณ ์ธ์์ Intent๋ฅผ ๋๊ฒจ์ฃผ๋ฉฐ launch๋ฅผ ์คํ์ํจ๋ค.
๋ค์ ์ํฐ๋นํฐb์์ ํด์ผํ ์ผ์๋ณด์
์ํฐ๋นํฐ b์์๋ intent์ ์ํ๋ ์ ๋ณด putExtra๋ก ๋ฃ๊ณ setResultํจ์์ ์ธ์๋ก resultcode์ intent๋ฅผ ๋ฃ์ด์ฃผ๊ณ finish()๋ก a๋ก ๋์ด๊ฐ๋ฉด๋๋ค.
์ธํ ํธ๋ ํ๋ง๋๋ก ํ๋ฉด์ด ์ฎ๊ฒจ์ง๊ฑฐ๋ ์ ํ๋ฅผ ๊ฑธ๊ฑฐ๋ ์นํ์ด์ง๋ค์ ์ด๊ฑฐ๋ ํ ๋ ์ ๋ณด๋ฅผ 4๋ ์ปดํฌ๋ํธ๋ผ๋ฆฌ ์ ๊ธฐ์ ์ผ๋ก ์ ๋ณด์ ๋ฌ์ํ๋ฉฐ ์๋ํ ์์๊ฒ ํด์ฃผ๋ ์์์ด๋ค.
๋ํ ์ธํ ํธ๋ ์์ ์ด ๋ง๋ ์ฑ์์์ ํ๋ํ๋ ๊ฒ ๋ฟ๋ง ์๋๋ผ ๋ด๊ฐ ๋ง๋ค์ง ์์ ํ ์ ํ๋ฆฌ์ผ์ด์ ์ ๊ธฐ๋ฅ์ ์ํํ ์ ์๋ค. ์ฆ ์๋๋ก์ด๋ ์์คํ ์ ๋ด๊ฐ ๋ง๋ ์ธํ ํธ์ ์ ๋ณด๋ฅผ ์ฒ๋ฆฌํ๋ฉด์ ๋ด๊ฐ ๋ง๋ ์กํฐ๋นํค๋ ์ ํ๋ฆฌ์ผ์ด์ ์ ๊ตฌ์ฑ์์๊ฐ ํด์ผํ ์ผ์ ์ง์ ํ๋ ๊ฒ ์ด์ธ์๋ ํ ์ ํ๋ฆฌ์ผ์ด์ ์ ๊ธฐ๋ฅ์ ์ํํ๋ ๋ฑ ํจ์ฌ ์ ์ฐํ ๊ธฐ๋ฅ์ ์ ํ๋ฆฌ์ผ์ด์ ์ ๋ง๋ค ์ ์๊ฒ ํ๋ค.
์ด์ ๋ช ์์ ์ธํ ํธ์ ์์์ ์ธํ ํธ์ ์ฐจ์ด์ ์ ๋ด๋ณด์
๋ช ์์ ์ด๋ฒคํธ: ์ธํ ํธ์ ํด๋์ค ๊ฐ์ฒด๋ ์ปดํฌ๋ํธ ์ด๋ฆ์ ์ง์ ํ์ฌ ํธ์ถํ ๋์์ ํ์คํ ์์์๋๊ฒฝ์ฐ์ ์ฌ์ฉ,
์ฃผ๋ก ์ดํ๋ฆฌ์ผ์ด์ ๋ด๋ถ์์ ์ฌ์ฉํ๋ค.
-> ํน์ ์ปดํฌ๋ํธ๋ ์กํฐ๋นํฐ๊ฐ ๋ช ํํ๊ฒ ์คํ๋์ด์ผํ ๊ฒฝ์ฐ ์ฌ์ฉํ๋ค. ์ฆ ํ๋ฉด์ด๋ ๋ฑ ์ฑ๋ด์์ ์ฃผ๋ก ์ฌ์ฉ๋๋ค.
์์์ ์ธํ ํธ: ์ธํ ํธ์ ์ก์ ๊ณผ ๋ฐ์ดํฐ๊ฐ ์ ํด์ก์ง๋ง ํธ์ถํ ๋์์ด ๋ฌ๋ผ์ง์์๋๊ฒฝ์ฐ ์์์ ์ธํ ํธ๋ฅผ ์ฌ์ฉ ์๋ฅผ๋ค์ด ์น์ ์ฌ๋๊ฒฝ์ฐ์ ์ง์ ๊ตฌํํ์ง ์๊ณ ์๋๋ก์ด๋ ์์คํ ๋ด์ ์๋ ์น๋ธ๋ผ์ฐ์ ธ๋ฅผ ๋์ด๋ค ์ฐ๋๋ฐ ๊ทธ๋ ๋ธ๋ผ์ฐ์ ธ๋ ์ฌ๋ฌ๊ฐ๊ฐ ๊น๋ ค์์์ ์๊ธฐ์ ์ ํํ ์ด๋๊ฑธ ํธ์ถํ ๊ฒ์ธ์ง ๋ชจ๋ฅด๋ ์ํฉ์ด๋ค. ์ด๋ฐ๊ฒฝ์ฐ ์์์ ์ธํ ํธ๋ฅผ ํตํด ์ ๋ณด์ฒ๋ฆฌ๋ฅผ ํ ์์๋ ์ ์ ํ ์ปดํฌ๋ํธ๋ฅผ ์ฐพ์์ ์ฌ์ฉ์์๊ฒ ๊ณ ๋ฅด๊ฒํ๊ณ ๊ทธ์์ํด ์ฒ๋ฆฌํ ๊ฒฐ๊ณผ๋ฅผ ๋ณด์ฌ์ฃผ๋๊ฒ์ด๋ค.
ํ๋ง๋๋ก ์ ๋ฆฌํด์ ์ผ๊ณผ ๋ฐ์ดํฐ๋ ์ ํด์ก๋๋ฐ ์ธ๋ถ์์ ์ฒ๋ฆฌํ๋ คํ ๋ ๊ทธ ์ผ์ ๋๊ฐํ ์ง๋ ์ ํด์ ธ์์ง์์ ๊ทธ๋ฅ ๋ ๋๊ฒจ ๋ฒ๋ฆฌ๊ณ ๊ทธ์ผ์ ํ ํ๋ก๊ทธ๋จ์ ์๋๋ก์ด๋ ๋ด๋ถ์ ์ผ๋ก ์ ํด์ง๊ฑฐ๋ ์ฌ๋ฌ๊ฐ์ผ๊ฒฝ์ฐ ์ฌ์ฉ์๊ฐ ์ ํ๋๋ก ํ๋๊ฒ์ด๋ค.
๋น์จ์ ์ ํด์ ์ฌ์ฉํ๊ณ ์ถ์๋ weidth๋ height ๋์คํ๋๋ง ์ ํ๊ณ ํ๋๋ 0dp๋ก ์ค์ ํด์ฃผ๊ณ ๊ฐ๋ก ์ธ๋ก์ ๋น์จ์ ConstraintDimensionRatio ์์ฑ์ ํตํด์ ์ ํด์ค๋ค
๋น์จ ํํ๋ฐฉ๋ฒ์ ์ด๋ฌํ๋ค
-
app:layout_constraintDimensionRatio="1:1" (width:height๋ก ํํํ๋ ๋ฐฉ๋ฒ)
-
app:layout_constraintDimensionRatio="1.0" (width์ height์ ๋น์จ์ float๊ฐ์ผ๋ก ํํํ๋ ๋ฐฉ๋ฒ)
3-2
์ฝํ๋ฆฐ ํน์ฑ์ ๋ง์ง๋ง ์ธ์๊ฐ ๋๋ค์์ด๋ผ๋ฉด ๊ดํธ ๋ฐ์ผ๋ก ๋นผ์ ์์ฑํ ์์๊ธฐ ๋๋ฌธ์ด๋ค.
SAM ๋ณํ
์ฝํ๋ฆฐ์์๋ ์ถ์ ๋ฉ์๋ ํ๋๋ฅผ ์ธ์๋ก ์ฌ์ฉํ ๋๋ ํจ์๋ฅผ ์ธ์๋ก ์ ๋ฌํ๋ฉด ํธํฉ๋๋ค.
์๋ฐ๋ก ์์ฑ๋ ๋ฉ์๋๊ฐ ํ๋์ธ ์ธํฐํ์ด์ค๋ฅผ ๊ตฌํํ ๋๋ ๋์ ํจ์๋ฅผ ์์ฑํ ์ ์์ต๋๋ค.
์ด๋ฅผ SAM(Single Abstract Method) ๋ณํ ์ด๋ผ ํฉ๋๋ค.
๊ทธ๋ฆฌ๊ณ ๋๋ค๊ฐ ์ด๋ค ๋ฉ์๋์ ์ ์ผํ ์ธ์์ธ ๊ฒฝ์ฐ์๋ ๋ฉ์๋์ ๊ดํธ๋ฅผ ์๋ตํ ์ ์์ต๋๋ค
2์ฃผ์ฐจ
bandicam.2021-10-22.17-29-56-316.mp4
์ผ๋จ follower๋ ๋ฆฌ์คํธํํ repository๋ ๊ทธ๋ฆฌ๋ ํํ๋กํด์ level2,3 ์ ์ฉ์ ๋ค follower์๋ค๊ฐ๋ง ํ๋ค.
level1์ ๋น์ฐํ ์ํํ๊ณ
level 2-1์
๋ค์ด๊ฐ๋ ์์ธํ๋ฉด์ ๊ตฌ์ฑํ๋๊ฒ์ DetailFragment๋ฅผ ๋ง๋ค์ด ๊ฑฐ๊ธฐ์ ๋ฐ์ดํฐ๋ฅผ arguments๋ก ์ ๋ฌํด์ ๊ตฌ์ฑํ๊ณ
๋ฐ์ ์ค๋ช ์ ์ด๋ฆ์ด ๋ญ๊ฐ์ ๋ฐ๋ผ์ ๊ทธ๋ฅ databinding์ผ๋ก ์ง๊ฐ์์์ ๋ฐ๋๊ฒ ๋ง๋ค์๋ค.
๋ณด๊ธฐ์ข์ผ๋ผ๊ณ ํ์ํ๋ถ๋ถ๋ง ์ฝ๋๋ฅผ ์๋ผ์ ๋ฃ์ด๋จ๋๋ฐ ์ํ๊ฑด์ง๋ ๋ชจ๋ฅด๊ฒ ๋ค ํผ๋๋ฐฑ ๋ถํ๋๋ฆฝ๋๋ค.
2-1
DetailActivity.kt
class DetailActivity : AppCompatActivity() {
private lateinit var binding: ActivityDetailBinding
private lateinit var name: String
private var src by Delegates.notNull<Int>()
lateinit var detailIntroduce : MutableLiveData<String>
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = DataBindingUtil.setContentView(this, R.layout.activity_detail)
binding.detail = this
binding.lifecycleOwner = this
name = intent.getStringExtra("name")!!
src = intent.getIntExtra("src",R.drawable.pig)
siteFragment()
decideDetailIntroduction()
}
fun siteFragment(){
val detailFragment = DetailFragment()
var bundle = Bundle()
bundle.putString("name",name)
bundle.putInt("src",src)
detailFragment.arguments = bundle
supportFragmentManager.beginTransaction().add(R.id.detailFragmentFrame,detailFragment).commit()
}
fun decideDetailIntroduction(){
if (name == "๋ฌธ๋ค๋น"){
detailIntroduce=MutableLiveData<String>().apply { value = "๊ณ ํฅ์ ๊ฒฝ์๋จ๋ ํฉ์ฒ์ด๊ณ ,ํ์ฌ 24์ด์ด๋ฉฐ ์๋๋ก์ด๋ ํํธ์ฅ์ ๋งก๊ณ ์์.. ์๋ ์ข์. ์๋ ์ข์. ์๋ ์ข์.์๋ ์ข์. ์๋ ์ข์. ์๋ ์ข์. ์๋ ์ข... "}
}else if (name == "์ฅํ๋ น"){
detailIntroduce=MutableLiveData<String>().apply { value = "๋๊ตฐ์ง ๋ชฐ๋ผ์ ์ต์กํฉ๋๋ค"}
}
// ์ค๋ต
}
}
DetailFragment.kt
package changhwan.experiment.sopthomework
import android.os.Bundle
import androidx.fragment.app.Fragment
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import changhwan.experiment.sopthomework.databinding.FragmentDetailBinding
import kotlin.properties.Delegates
class DetailFragment : Fragment() {
private var _binding: FragmentDetailBinding? = null
private val binding get() = _binding!!
private lateinit var name : String
private var src by Delegates.notNull<Int>()
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
_binding = FragmentDetailBinding.inflate(layoutInflater, container, false)
return binding.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
name = arguments?.getString("name")!!
src = arguments?.getInt("src",R.drawable.pig)!!
binding.detailImage.setImageResource(src!!)
binding.detailName.text = name
}
override fun onDestroyView() {
super.onDestroyView()
_binding = null
}
}
2-2
๊ณผ์ ์ค ์๊ฒ๋๊ฒ์ ์์ธํ ์ค๋ช ํด๋จ๋ค
CustomDividerDecoration.kt
package changhwan.experiment.sopthomework
import android.graphics.Canvas
import android.graphics.Paint
import androidx.annotation.ColorInt
import androidx.recyclerview.widget.RecyclerView
class CustomDividerDecoration(private val height: Float,private val padding: Float, @ColorInt private val color: Int,private val margin : Int):RecyclerView.ItemDecoration() {
private val paint = Paint()
init{
paint.color = color
}
override fun onDrawOver(c: Canvas, parent: RecyclerView, state: RecyclerView.State) {
val left = parent.paddingStart + padding
val right = parent.width - parent.paddingEnd - padding
for (i in 0 until parent.childCount) {
val child = parent.getChildAt(i)
val params = child.layoutParams as RecyclerView.LayoutParams
val top = ( child.bottom.toFloat() + margin)
val bottom = child.bottom.toFloat() + height + margin
c.drawRect(left, top, right, bottom, paint)
}
}
}
CustomMarginDecoration.kt
package changhwan.experiment.sopthomework
import android.graphics.Rect
import android.view.View
import androidx.recyclerview.widget.RecyclerView
class CustomMarginDecoration(private val padding: Int) : RecyclerView.ItemDecoration() {
override fun getItemOffsets(
outRect: Rect,
view: View,
parent: RecyclerView,
state: RecyclerView.State
) {
super.getItemOffsets(outRect, view, parent, state)
outRect.top = padding
outRect.bottom = padding
outRect.left = padding
outRect.right = padding
}
}
FollowerFragment.kt
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
siteFollowerRecycler()
binding.followerRecycle.addItemDecoration(CustomMarginDecoration(50))
binding.followerRecycle.addItemDecoration(CustomDividerDecoration(10f,10f, resources.getColor(R.color.main),40))
2-3
์ด๊ฒ๋ ๊ณผ์ ์ค ์๊ฒ๋๊ฒ์ ์์ธํ ์ค๋ช ํด๋จ๋ค
itemActionListener.kt
package changhwan.experiment.sopthomework
interface ItemActionListener {
fun onItemMoved(from: Int, to: Int)
fun onItemSwiped(position: Int)
}
ItemDragListener.kt
package changhwan.experiment.sopthomework
interface ItemActionListener {
fun onItemMoved(from: Int, to: Int)
fun onItemSwiped(position: Int)
}
FollowerAdapter.kt
class FollowerAdapter(private val listener: ItemDragListener) :
RecyclerView.Adapter<FollowerAdapter.FollowerViewHolder>(), ItemActionListener {
//...
override fun onItemMoved(from: Int, to: Int) {
if (from == to) {
return
}
val fromItem = followerData.removeAt(from)
followerData.add(to, fromItem)
notifyItemMoved(from, to)
}
override fun onItemSwiped(position: Int) {
followerData.removeAt(position)
notifyItemRemoved(position)
}
inner class FollowerViewHolder(
private val binding: FollowerItemBinding,
listener: ItemDragListener
) : RecyclerView.ViewHolder(binding.root) {
init {
binding.root.setOnTouchListener { v, event ->
if (event.action == MotionEvent.ACTION_DOWN) {
listener.onStartDrag(this)
}
false
}
}
}
}
ItemTouchHelperCallBack.kt
package changhwan.experiment.sopthomework
import androidx.recyclerview.widget.ItemTouchHelper
import androidx.recyclerview.widget.RecyclerView
class ItemTouchHelperCallback(val listener: ItemActionListener) : ItemTouchHelper.Callback() {
override fun getMovementFlags(
recyclerView: RecyclerView,
viewHolder: RecyclerView.ViewHolder
): Int {
val dragFlags = ItemTouchHelper.DOWN or ItemTouchHelper.UP
val swipeFlags = ItemTouchHelper.START or ItemTouchHelper.END
return makeMovementFlags(dragFlags,swipeFlags)
}
override fun onMove(
recyclerView: RecyclerView,
viewHolder: RecyclerView.ViewHolder,
target: RecyclerView.ViewHolder
): Boolean {
listener.onItemMoved(viewHolder!!.adapterPosition, target!!.adapterPosition)
return true
}
override fun onSwiped(viewHolder: RecyclerView.ViewHolder, direction: Int) {
listener.onItemSwiped(viewHolder!!.adapterPosition)
}
override fun isLongPressDragEnabled(): Boolean = true
}
FollowerFragment.kt
class FollowerFragment : Fragment(), ItemDragListener {
private lateinit var itemTouchHelper : ItemTouchHelper
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
itemTouchHelper = ItemTouchHelper(ItemTouchHelperCallback(followerAdapter))
itemTouchHelper.attachToRecyclerView(binding.followerRecycle)
}
override fun onStartDrag(viewHolder: RecyclerView.ViewHolder) {
//์ด๋ถ๋ถ ์ฐธ๊ณ ํ ๋ธ๋ก๊ทธ์ ๋ค๋ฅด๊ฒ ์๋ฌด๊ฒ๋ ์์ด์ผ์ง๋ง ๋์๊ฐ๋ค ์ดํด์๋จ ์ด๋ถ๋ถ์ ์ถ๊ฐ์ ์ธ ๊ณต๋ถํด์ผ๊ฒ ๋ค
}
}
3-1
์ ์ ๋ณด๋ง ๊ณผ์ ์ค ์๊ฒ๋๊ฒ์ ์์ฑํ๊ณ ์ ์ฉํ์ง๋ ์์๋ค.
3-2
DiffUtil์ oldList์ newList๋ฅผ ๋น๊ตํ์ฌ ์ฐจ์ด๋ฅผ ๊ณ์ฐํ๊ณ , newList๋ก ๊ฐฑ์ ํด์ฃผ๋ ์ ํธ๋ฆฌํฐ ํด๋์ค์ด๋ค.
์ฆ, ์ด ํด๋์ค๋ฅผ ์ฌ์ฉํ๋ฉด ์์ดํ ๋ณ๊ฒฝ์ ๊ตฌ์ฒด์ ์ธ ์ํฉ์ ๋ฐ๋ผ Adapter์ ์ ์ ํ ๋ฉ์๋๋ฅผ ํธ์ถํ์ง ์์๋ ๋๋ค.
ContactDiffUtill.kt
package changhwan.experiment.sopthomework
import androidx.recyclerview.widget.DiffUtil
class ContactDiffUtil(private val oldList: List<FollowerData>, private val currentList: List<FollowerData>):
DiffUtil.Callback(){
override fun getOldListSize(): Int =oldList.size
override fun getNewListSize(): Int =currentList.size
override fun areItemsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean {
return oldList[oldItemPosition].followerName==currentList[newItemPosition].followerName
}
override fun areContentsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean {
return oldList[oldItemPosition]==currentList[newItemPosition]
}
}
์ด 4๊ฐ ๋ฉ์๋๋ฅผ ์ค๋ฒ๋ผ์ด๋ ํด์ค์ผ ํ๋ค. ๋ฉ์๋๋ ์ด๋ฆ๋ช ๊ณผ ๋ฆฌํด๊ฐ์ ๋ณด๋ฉด ์ด๋ค ์ญํ ์ ํ๋์ง ์ฝ๊ฒ ์์ธกํ ์ ์๋ค.
FollowerAdapter.kt
//diffUtill ๋ถ๋ถ ์ด์ํ๋ฉด ๋์ค์ ๋ฐ๊ฟ์ผํจ
fun setContact(contacts: List<FollowerData>){
val diffResult= DiffUtil.calculateDiff(ContactDiffUtil(this.followerData, followerData), false)
diffResult.dispatchUpdatesTo(this)
this.followerData=followerData
}
//์ฌ๊ธฐ๊น์ง diffUtill
์ฝ๋์ ๋ป์,
- calculateDiff()๋ก oldList์ newList์ ์ฐจ์ด๋ฅผ ๊ณ์ฐํ๋ค.
- ์ฐจ์ด ๊ฐ์ ์ ๋ฐ์ดํธํ๊ณ , (notify~ ๊ธฐ๋ฅ์ ๊ฐ๋ค๊ณ ๋ณด๋ฉด ๋๋ค).
- list๊ฐ ๊ฐฑ์ ๋์์ผ๋ฏ๋ก ๊ธฐ์กด this.contacts๋ฅผ newList์ธ contacts๋ก ์ ๋ฐ์ดํธํ๋ค.
FollowerFragment.kt
//diffUtill๋ถ๋ถ ์๋๋ followerAdapter.notifyDataSetChanged()์์
followerAdapter.setContact(followerAdapter.followerData)
//์ฌ๊ธฐ๊น์ง
๊ฐ ํญ๋ชฉ๋ง๋ค ํด๋ฆญ ์ด๋ฒคํธ ๋ฆฌ์ค๋ ๋ฌ์์ ์ด๋ฒคํธ๋ฅผ ์ฒ๋ฆฌํด๋ณด์
ViewHolder ํน์ onBindViewHolder() ํจ์ ๋๊ณณ์์ ์ด๋ฒคํธ ์ฒ๋ฆฌ ํ๋ ๋ฐฉ๋ฒ์ด ์๋๋ฐ
์ฐ์ viewholder์์ ์ฒ๋ฆฌํด์ฃผ๋๊ฒ๋ถํฐ ๋ถํฐ ๋ด๋ณผ๊ฒ์ด๋ค.
1-1ViewHolder์์ ์ฒ๋ฆฌ
์ฐ๋ฆฌ๊ฐ viewbinding ์ ์ด์ฉํด์ viewHolder๋ฅผ ๋ง๋ค์๊ธฐ์ ViewHolder์ ์์ฑ์๋ก binding๊ฐ์ฒด๋ฅผ ๊ฝ์์คฌ๋ค.
๊ทธ๋์ ์ด binding๊ฐ์ฒด์ root๊ฐ ๋ฆฌ์ฌ์ดํด๋ฌ๋ทฐ๊ฐ ํํํ๋ ํญ๋ชฉํ๋ ์ฆ item์ ๋ ์ด์์์ ์ ๊ทผํ ์์๋ค
๊ทธ๋์ initํจ์๋ฅผ ๋ง๋ค์ด root์ onClicklistener๋ฅผ ์ถ๊ฐํ๋ค.
1-2 onBindViewHolder() ์์ ์ฒ๋ฆฌ
์ด ํจ์ ์์์๋ item์ ๋ํ ํด๋ฆญ ๋ฆฌ์ค๋๋ฅผ ์ ์ํ ์์๋ค. ํ์ง๋ง ๋ ์์ ์ ๋ฐฉ๋ฒ์ ์ฌ์ฉํ๋ค
๊ฒฐ๊ณผ์ ์ผ๋ก Holder ํด๋์ค ๋ด๋ถ์์ ์ฌ์ฉํ๋๊ฒ๊ณผ ๊ฐ๋ค.
๋ฆฌ์ฌ์ดํด๋ฌ ๋ทฐ์์ ์์ดํ ์ ํด๋ฆญํด์ ์ด๋ฒคํธ๋ฅผ ๋ฐ์๊น์ง๋ ์์ผฐ๋๋ฐ ์ํ๋ ์ด๋ฒคํธ๊ฐ ์๋ก์ด ์กํฐ๋นํฐ ์คํ์ผ ๊ฒฝ์ฐ
์ด๋ํฐ์์ ์ด๋ฒคํธ๋ฅผ ์คํ์์ผ์ฃผ๊ณ ์๊ธฐ์ ๊ธฐ์กด์ ์กํฐ๋นํฐ์์ Intent์ ๋ฃ์ด์คฌ๋ ์ธ์๋ค์ ๊ทธ๋๋ก ๋ฃ์ด์ฃผ๋ฉด ์๋๋ค.
์ผ๋จ clickListener์์ intent์ถ๊ฐํ๊ณ ์ฒซ๋ฒ์งธ ์ธ์๋ก binding.root์ context๊ฐ ์๊ธฐ์ ๊ทธ๊ฑธ ์ฌ์ฉํ๋ค.
๊ทธ๋์ ์ปจํ ์คํธ๋ฅผ ๋ง์ถฐ์ ๋ฃ์ด์ฃผ๊ณ ๋๋ฒ์งธ ์ธ์๋ก ์ด๋ํ๋ ค๋ ์กํฐ๋นํฐ ๋ฃ๊ณ
startActivityํจ์์ ์ฒซ๋ฒ์งธ ์ธ์๋ก binding.root์ context, ๋๋ฒ์งธ ์ธ์๋ก intent, ์ธ๋ฒ์งธ๋ก ๋ณ๋ค๋ฅธ ์ต์ ์ด์๋ค๋ฉด null์ ์ ๋ ฅํ๋ฉด๋๋ค.
๋ฐ์ดํฐ ๋ณด๋ด๊ธฐ
์ฒซ๋ฒ์งธ ๋ฐฉ๋ฒ
// ์ ์ผ ๋จ์ํ๊ณ ์ฌ์ด ๋ฐฉ๋ฒ
val intent = Intent(this,์ฎ๊ฒจ๊ฐ ์กํฐ๋นํฐ::class.java)
intent.putExtra("num1",1) //๋ฐ์ดํฐ ๋ฃ๊ธฐ
intent.putExtra("num2",2) //๋ฐ์ดํฐ ๋ฃ๊ธฐ
startActivity(intent)
๋๋ฒ์งธ ๋ฐฉ๋ฒ
val intent = Intent(this@Intent1,Intent2::class.java).apply {
this.putExtra("num1",1) // ๋ฐ์ดํฐ ๋ฃ๊ธฐ
this.putExtra("num2",2) // ๋ฐ์ดํฐ ๋ฃ๊ธฐ
}
startActivity(intent)
//์ฝํ๋ฆฐ์ ์ ์ฉํ ๊ธฐ๋ฅ!๐คฉ apply
//ํ๋์ ๋ชจ์์ ๋ณผ ์ ์์ด์ ์ ์ฉํ ๋ฏ
๋ฐ์ดํฐ ๋ฐ๊ธฐ
val number1 = intent.getIntExtra("num1", 0)
val number2 = intent.getIntExtra("num2", 0)
๋ฐ์ดํฐ ๋ณด๋ด๊ธฐ
var fragment2 = Fragment2()
var bundle = Bundle()
bundle.putInt("num1",1)
bundle.putInt("num2",2)
fragment2.arguments = bundle //fragment์ arguments์ ๋ฐ์ดํฐ๋ฅผ ๋ด์ bundle์ ๋๊ฒจ์ค
activity?.supportFragmentManager!!.beginTransaction()
.replace(R.id.view_main, fragment2)
.commit()
๋ฐ์ดํฐ ๋ฐ๊ธฐ
val num1 = arguments?.getInt("num1")
val num2 = arguments?.getInt("num2")
itemDecoration ํ์ฉํด์ ๊ตฌ๋ถ์ ๊ณผ ๊ฐ๊ฒฉ์ฃผ๊ธฐ
xmlํ์ผ์์ margin์ด๋ ๊ตฌ๋ถ์ ์ ์ด๋์ ๋ ๋ง๋ค์์์ง๋ง ์ด๊ฑฐ๋ ์ ํํ๋งํ์๋ฉด ์ํ๋จ ๋์ชฝ์ margin์ ํ๋ฒ๋ง ๋ค์ด๊ฐ๊ณ ๋๋จธ์ง ์ค๊ฐ๋ถ๋ถ์ ๋๋ฒ์ฉ ๋ค์ด๊ฐ๋๋ฌธ์
๊ตฌ๋ถ์ ์ xml๋ด์์ view๋ฅผ ์ถ๊ฐํด์ ๋ฃ์ผ๋ฉด ๋ ์ด์์์ ๋ถํ์ํ ๋ทฐ๋ฅผ ์ถ๊ฐํจ์ผ๋ก์จ ๋ ์ด์์ ๊ณ์ธต์ด ์ฆ๊ฐํ๊ณ ๊ทธ์๋ฐ๋ผ ์ฑ๋ฅ์ ์์ข์ ์ํฅ์ ๋ฏธ์น๋ฉฐ
์ข์ฐ๋ก ์ค์์ดํ ํ๋ ์ ๋๋ฉ์ด์ ์ด์๋ค๋ฉด ๊ตฌ๋ถ์ ์ด ๊ฐ์ด ์์ง์ธ๋ค.
๊ทธ๋์ itemDecoration์ ์ฌ์ฉํ๋๋ฐ itemDecoration ํด๋์ค๋ Recyclerview ๋ด๋ถ์ ์๋ ์ถ์ ํด๋์ค์ด๋ค.
์ด๋ฆ์ฒ๋ผ RectclerView์ ์์ดํ ๋ค์ ๊พธ๋ฏธ๋ ์ญํ ์ ํ๋ค.
์ฌ์ค ์ปค์คํ ํ๋๋๋ก ๋ง์ ๊ธฐ๋ฅ๋ค์ ๊ตฌํํ ์ ์์ผ๋ฏ๋ก ํ๊ณ ์ถ์๊ฒ ์์ผ๋ฉด ๊ตฌ๊ธ๋งํด์ ์ฌ์ฉํด์ผ๊ฒ ๋ค ๊ทผ๋ฐ ์ ์ฃ๋ค ์์ ์ฝ๋๊ฐ ์๋ฐ๋๊ณ !!!!!!!!!!!!!
๋ํ์ ์ผ๋ก ๊ตฌ๋ถ์ ์ด๋ ์ฌ๋ฐฑ์ ๋ฃ๋๋ฐ ๋ง์ด๋ค ์ฌ์ฉํ๋ค.
๋ด๋ถ ํจ์๊ฐ 3๊ฐ์ง๊ฐ ์๋๋ฐ
1.onDraw
์์ดํ ์ด ๊ทธ๋ ค์ง๊ธฐ ์ ์ ํธ์ถ๋จ์ผ๋ก ์์ดํ (viewholder)๋ณด๋ค ์๋์ ์์นํ๊ฒ๋๋ค ์์ดํ ๊ณผ onDraw๊ฐ ๊ทธ๋ฆฌ๋ ๊ฒ์ด ๊ฒน์น๋ค๋ฉด ์์ดํ ์ด ๋ฎ์ด์์์ onDraw๊ฐ ๊ทธ๋ฆฌ๋ ๊ฒ์ด ์๋ณด์ธ๋ค.
2.onDrawOver
์์ดํ ์ด ๊ทธ๋ ค์ง๊ณ ๋๋ค์์ ํธ์ถ๋๋ ํจ์๋ก ์ด๊ฑฐ๋ ๊ฒน์น๋ค๋ฉด ์์ดํ ์ ๊ฐ๋ฆด์์๋ค.
3.getItemOffsets
๊ฐ ํญ๋ชฉ์ ๋ฐฐ์นํ ๋ ํธ์ถํ๋ค -> margin์ ์ค๋ ์ฌ์ฉ
outRect์ ์ํ๋ ํฌ๊ธฐ์ ๊ฐ๊ฒฉ์ (์ผ์ชฝ, ์์ชฝ, ์ค๋ฅธ์ชฝ, ์๋์ชฝ) ์ 4๊ฐ ํ๋์ ์ค์ ํด์ค๋ค.
๊ทธ๋์ ์ผ๋จ ๋ง๋ค์ด๋ณด์.
๊ตฌ๊ธ๋งํ๋ฉด ์์ ๊ฒ๋๊ฒ ๋ง๋ค ๋ณต์กํ๊ฑฐ๋ ๋๋์ฑ๋ง๋ค. ๊ทธ๋ฆฌ๊ณ ๋ญ์๋ฆฌ์ธ์ง ์ดํดํ๊ธฐ ์ข ๋ํดํ๋ค
๊ทธ๋ฅ ์ฌ์ด ๊ฒ๋ค๋ก ์ ์ฉํด๋ณด๋ฉฐ ํ์ํ ๋ ๋ง๋ค ๋ง๋ค์ด์ ์ฌ์ฉํด ๋๊ฐ๊ณ ๋์ด๋๋ฅผ ์กฐ๊ธ์ฉ ๋์ฌ์ผ๊ฒ ๋ค.
๊ฐ์ฅ์ฌ์ด margin๋ง๋ค๊ธฐ
package changhwan.experiment.sopthomework
import android.graphics.Rect
import android.view.View
import androidx.recyclerview.widget.RecyclerView
class CustomMarginDecoration(private val padding: Int) : RecyclerView.ItemDecoration() {
override fun getItemOffsets(
outRect: Rect,
view: View,
parent: RecyclerView,
state: RecyclerView.State
) {
super.getItemOffsets(outRect, view, parent, state)
outRect.top = padding
outRect.bottom = padding
outRect.left = padding
outRect.right = padding
}
}
๋ฐ๋ก classํ์ผ์ ํ๋ํ์ itemDecoration์ ์์๋ฐ์ํ
์์ฑ์๋ก ๋์ธ ๊ฐ์ ๋ฐ์์
getItemOffsets๋ฅผ ์ค๋ฒ๋ผ์ด๋ฉ ํ ๋ ์ฌ๋ฐฉ top,bottom,left,right์ ์์ฑ์๋ก ๋ฐ์ ๊ฐ์ ๋ฃ์ด์ margin์ ํ๋ณดํ๋ค.
์ ์ฉ์ ์ ์ฉ์ํฌ ๋ฆฌ์ฌ์ดํด๋ฌ๋ทฐ์๋ค
binding.followerRecycle.addItemDecoration(CustomMarginDecoration(50))
์ด๋ฐ์์ผ๋ก addItemDecoration์ผ๋ก ๋ฃ์ด์ฃผ๋ฉด๋๋ค.(์๋ช ์ฃผ๊ธฐ์ ํ๋ฉด์ด ๊ทธ๋ ค์ง๊ณ ๋ํ์ ์คํ์์ผ์ผํ๋๊ฒ ๊ฐ๋ค)
๊ฐ์ฅ์ฌ์ด ๊ตฌ๋ถ์ ๋ง๋ค๊ธฐ
package changhwan.experiment.sopthomework
import android.graphics.Canvas
import android.graphics.Paint
import androidx.annotation.ColorInt
import androidx.recyclerview.widget.RecyclerView
class CustomDividerDecoration(private val height: Float,private val padding: Float, @ColorInt private val color: Int,private val margin : Int):RecyclerView.ItemDecoration() {
private val paint = Paint()
init{
paint.color = color
}
override fun onDrawOver(c: Canvas, parent: RecyclerView, state: RecyclerView.State) {
val left = parent.paddingStart + padding
val right = parent.width - parent.paddingEnd - padding
for (i in 0 until parent.childCount) {
val child = parent.getChildAt(i)
val params = child.layoutParams as RecyclerView.LayoutParams
val top = ( child.bottom.toFloat() + margin)
val bottom = child.bottom.toFloat() + height + margin
c.drawRect(left, top, right, bottom, paint)
}
}
}
๊ตฌ๋ถ์ ์ด ์ข๋นก์ธ๋ค
for๋ฌธ์ ๋ค์ด๊ฐ๋๊ฒ๋ค์ด ๋ญ์๋ฆฌ์ธ๊ฐ ์ถ์๋ฐ ์ข ๊ณตํต์ ์ผ๋ก ์์ ๋ง๋ค ๊ฑฐ์ ๊ฒน์น๋ค.
๋ช๊ฐ์ง ์์ ๋ค์ ๋ถ๋ถ๋ถ๋ถ์ ๋ฐ์ ์์ด์ ์ฌ์ฉํ๋ค
์์ฑ์์ ๋ค์ด๊ฐ๋ height์ ๋ฐ๋ผ ๊ตฌ๋ถ์ ์ ๊ตต๊ธฐ๊ฐ ๋ฌ๋ผ์ง๊ณ
padding์ ๋ฐ๋ผ ์ข์ฐ์ ์ฌ๋ฐฑ์ด์๊ธฐ๋ฉฐ
color์ ์๊น ์ค์ ์ด๊ณ
margin์ ์ผ๋ง๋ ๋์ธ์ง ๊ฑฐ๋ฆฌ์ค์ ์ด๋ค.
๊ทผ๋ฐ ์ด๋ฐ๊ฒ๋ ๋ด๊ฐ ๋ญ๋ฃ์์ง ์์์ ๊ฒฐ์ ์ด๋ค ์ด์งํผ.
recyclerview์ drag&drop swipe to Dismiss ๊ตฌํ
ItemTouchHelper๋ RecyclerView.ItemDecoration์ ์๋ธ ํด๋์ค์ด๋ค. RecyclerView ๋ฐ Callback ํด๋์ค์ ํจ๊ป ์๋ํ๋ฉฐ, ์ฌ์ฉ์๊ฐ ์ด๋ฌํ ์ก์ ์ ์ํํ ๋ ์ด๋ฒคํธ๋ฅผ ์์ ํ๋ค. ์ฐ๋ฆฌ๋ ์ง์ํ๋ ๊ธฐ๋ฅ์ ๋ฐ๋ผ ๋ฉ์๋๋ฅผ ์ฌ์ ์ํด์ ์ฌ์ฉํ๋ฉด ๋๋ค.
ItemTouchHelper.Callback์ ์ถ์ ํด๋์ค๋ก ์ถ์ ๋ฉ์๋์ธ getMovementFlags(), onMove(), onSwiped()๋ฅผ ํ์๋ก ์ฌ์ ์ํด์ผ ํ๋ค. ์๋๋ฉด Wrapper ํด๋์ค์ธ ItemTouchHelper.SimpleCallback์ ์ด์ฉํด๋ ๋๋ค.
์ด์ ์์๋๋ก ๊ตฌํํ๋๊ฒ์ ์ซ์๊ฐ๋ณด์
1.ItemDragListener.kt ๋ง๋ค๊ธฐ
interface ItemDragListener {
fun onStartDrag(viewHolder :RecyclerView.ViewHolder)
}
์ฌ์ฉ์๊ฐ Drag ์ก์ ์ ์์ํ ๋ itemTouchHelper์ ์ด๋ฒคํธ๋ฅผ ์ ๋ฌํ๋ค.
2.ItemActionListener.kt ๋ง๋ค๊ธฐ
interface ItemActionListener {
fun onItemMoved(from: Int, to: Int)
fun onItemSwiped(position: Int)
}
์์ดํ ์ด Drag & Drop ๋๊ฑฐ๋ Swiped ๋์ ๋ ์ด๋ํฐ์ ์ด๋ฒคํธ๋ฅผ ์ ๋ฌํ๋ค.
3.adapter์์ ItemActionListener ์ธํฐํ์ด์ค๋ฅผ ๊ตฌํ
class FollowerAdapter(private val listener: ItemDragListener) :
RecyclerView.Adapter<FollowerAdapter.FollowerViewHolder>(), ItemActionListener {
//...
override fun onItemMoved(from: Int, to: Int) {
if (from == to) {
return
}
val fromItem = followerData.removeAt(from)
followerData.add(to, fromItem)
notifyItemMoved(from, to)
}
override fun onItemSwiped(position: Int) {
followerData.removeAt(position)
notifyItemRemoved(position)
}
}
์ด๋ํฐ์์๋ ItemActionListener ์ธํฐํ์ด์ค๋ฅผ ๊ตฌํํ๋ค. onItemMoved(), onItemSwiped()์ ์ฌ์ ์ํ์ฌ ์์ดํ ์ด๋๊ณผ ์ ๊ฑฐ ์ฝ๋๋ฅผ ์์ฑํ๋ค. ์ด๋ ์ด๋ํฐ๊ฐ ์์ดํ ๋ณ๊ฒฝ ์ฌํญ์ ์ธ์ํ ์ ์๋๋ก notifyItemMoved(), notifyItemRemoved()๋ฅผ ํธ์ถํด์ผ ํ๋ค.
4.viewAdapter์์ OnTouchListener๋ฌ์์ฃผ๊ธฐ
inner class FollowerViewHolder(
private val binding: FollowerItemBinding,
listener: ItemDragListener
) : RecyclerView.ViewHolder(binding.root) {
init {
binding.root.setOnTouchListener { v, event ->
if (event.action == MotionEvent.ACTION_DOWN) {
listener.onStartDrag(this)
}
false
}
}
}
์ด๋ํฐ ์์ฑ์์ ํ๋ผ๋ฏธํฐ๋ก ๋ฐ์ ItemDragListener๋ ๋ทฐํ๋์์ ์ฌ์ฉ๋๋ค. ์ฌ๊ธฐ์๋ ๋๋๊ทธ ํธ๋ค์ ํตํ ์์ดํ ์ด๋์ ๊ตฌํํ๊ณ ์ ํ๊ธฐ ๋๋ฌธ์, ๋๋๊ทธ ํธ๋ค ๋ทฐ์ ํฐ์น ๋ฆฌ์ค๋๋ฅผ ๋ฌ์์ค๋ค. ๊ทธ๋ฆฌ๊ณ ์ฌ์ฉ์๊ฐ ACTION_DOWN ์ก์ ์ ์ทจํ์ ๋ listener.onStartDrag()๋ฅผ ํธ์ถํ๋ค.
5.ItemTouchHelperCallback.kt ์์ฑ
package changhwan.experiment.sopthomework
import androidx.recyclerview.widget.ItemTouchHelper
import androidx.recyclerview.widget.RecyclerView
class ItemTouchHelperCallback(val listener: ItemActionListener) : ItemTouchHelper.Callback() {
override fun getMovementFlags(
recyclerView: RecyclerView,
viewHolder: RecyclerView.ViewHolder
): Int {
val dragFlags = ItemTouchHelper.DOWN or ItemTouchHelper.UP
val swipeFlags = ItemTouchHelper.START or ItemTouchHelper.END
return makeMovementFlags(dragFlags,swipeFlags)
}
override fun onMove(
recyclerView: RecyclerView,
viewHolder: RecyclerView.ViewHolder,
target: RecyclerView.ViewHolder
): Boolean {
listener.onItemMoved(viewHolder!!.adapterPosition, target!!.adapterPosition)
return true
}
override fun onSwiped(viewHolder: RecyclerView.ViewHolder, direction: Int) {
listener.onItemSwiped(viewHolder!!.adapterPosition)
}
override fun isLongPressDragEnabled(): Boolean = true
}
ItemTouchHelper.Callback์ ์์๋ฐ๋ ItemTouchHelperCallback ํด๋์ค๋ฅผ ๊ตฌํํ๋ค. ์์ฑ์์ ํ๋ผ๋ฏธํฐ๋ก ItemActionListener๋ฅผ ๋ฐ๋๋ค.
5-1 ์ฐ์ getMovementFlags()๋ฅผ ์ฌ์ ์ํด Drag ๋ฐ Swipe ์ด๋ฒคํธ์ ๋ฐฉํฅ์ ์ง์ ํ๋ค.
5-2 ์์ดํ ์ด Drag ๋๋ฉด ItemTouchHelper๋ onMove()๋ฅผ ํธ์ถํ๋ค. ์ด๋ ItemActionListener๋ก ์ด๋ํฐ์
fromPosition๊ณผ toPosition์ ํ๋ผ๋ฏธํฐ์ ํจ๊ป ์ฝ๋ฐฑ์ ์ ๋ฌํ๋ค.
5-3 ์์ดํ ์ด Swipe ๋๋ฉด ItemTouchHelper๋ ๋ฒ์๋ฅผ ๋ฒ์ด๋ ๋๊น์ง ์ ๋๋ฉ์ด์ ์ ์ ์ฉํ ํ onSwiped()๋ฅผ ํธ์ถํ๋ค.
์ด๋ ItemActionListener๋ก ์ด๋ํฐ์ ์ ๊ฑฐํ ์์ดํ ์ position์ ํ๋ผ๋ฏธํฐ์ ํจ๊ป ์ฝ๋ฐฑ์ ์ ๋ฌํ๋ค.
5-4 isLongPressDragEnabled()์ ์์ดํ ์ ๊ธธ๊ฒ ๋๋ฅด๋ฉด Drag & Drop ์์ ์ ์์ํด์ผ ํ๋์ง๋ฅผ ๋ฐํํ๋ค. ๋ํดํธ๋
true์ด๋ค
6.์ฌ์ฉ์ฒ(activity ํน์ fragment) ์์ ์กํฐ๋นํฐ์์๋ ItemDragListener ์ธํฐํ์ด์ค๋ฅผ ๊ตฌํ
class FollowerFragment : Fragment(), ItemDragListener {
private lateinit var itemTouchHelper : ItemTouchHelper
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
itemTouchHelper = ItemTouchHelper(ItemTouchHelperCallback(followerAdapter))
itemTouchHelper.attachToRecyclerView(binding.followerRecycle)
}
override fun onStartDrag(viewHolder: RecyclerView.ViewHolder) {
//์ด๋ถ๋ถ ์ฐธ๊ณ ํ ๋ธ๋ก๊ทธ์ ๋ค๋ฅด๊ฒ ์๋ฌด๊ฒ๋ ์์ด์ผ์ง๋ง ๋์๊ฐ๋ค ์ดํด์๋จ ์ด๋ถ๋ถ์ ์ถ๊ฐ์ ์ธ ๊ณต๋ถํด์ผ๊ฒ ๋ค
}
}
์ด๋ ๊ฒํ๋ฉด ์ํ๋ ๊ธฐ๋ฅ๋ค์ ๊ตฌํํ ์์๋ค.
๋ณด์ผ๋ฌ ํ๋ ์ดํธ ์ฝ๋ ์ด๋ป๊ฒ ์ก์๊ฒ์ธ๊ฐ?
๋ฑํ ๋น์ฅ ๋ง์ด ๊ฐ์ ํ ๋ถ๋ถ์ด ์๋๋ฐ ๋ณต์กํด์ ๋ฐฉ๋ฒ๋ง ์์๋๊ณ ๊ฐ์ผ๊ฒ ๋ค.
์ด๋ ธํ ์ด์ ํ๋ก์ธ์ ๊ฐ์๊ฑธ ์ฌ์ฉํด์ ์๋ํ ์์ ์ ํ๋๊ฒ์ด ์ข๋ค์ง๋ง ๋๋ฌด ๋ณต์กํ๋ค ๋น์ฅ ์ด๊ฑฐํ๋ค ๋จธ๋ฆฌํฐ์ง๋ค.
๊ทธ๋์ ๋ณต์กํ์ง ์์ ๋ฐฉ๋ฒ์ ๋ดค๋ค
base์ฝ๋ ์ฆ BaseActivity, BaseFragment๊ฐ์ ์ฝ๋๋ค์ ๋ง๋ค์ด๋๊ณ ์์ํด์ ์ฌ์ฉํ๋๊ฒ์ด๋ค.
์ฅ๋จ์ ์ ์ดํด๋ณด๋ฉด
์ฅ์
-๋ฐ์ดํฐ ๋ฐ์ธ๋ฉ๋ฟ๋ง ์๋๋ผ Activity๋ค์ด ๊ณตํต์ ์ผ๋ก ์ํํด์ผ ํ๋ ์ฝ๋๊ฐ ์๋ค๋ฉด BaseActivity๋ฅผ ์ด์ฉํ ์ ์๋ค.๋ฐ๋ผ์
-Bolierplate ์ฝ๋๊ฐ ์ค์ด๋ ๋ค.
๋จ์
-AppCompatActivity๊ฐ ์๋๋ผ ํน์ ํ Activity๋ฅผ ์์ํด์ผ๋ง ํ๋ ๊ฒฝ์ฐ๊ฐ ์๋ค. => ์ด์ฉ ์ ์์ด BaseActivity๋ฅผ ์ฐ์ง ๋ชปํ๋ค.
-BaseActivity๊ฐ ๋ณ๊ฒฝ๋๋ฉด ์ด๋ฅผ ์์ํ ๋ชจ๋ Activity๋ค์ด ๋ณ๊ฒฝ๋๋ ๊ฒ์ด๋ฏ๋ก ๋ถ๋ด์ด ํฌ๋ค. (Side Effect)
-๊ณต๋์์ ์ ํ ๋ ๋ค๋ฅธ ์ฌ๋์ด ์ฝ๋๋ฅผ ์ดํดํ๊ธฐ ์ด๋ ต๋ค.
์ด์ basecode์์ ์ ๋ค์ด๋์ ๋ธ๋ก๊ทธ ๋งํฌ๋ฅผ ๊ฑธ์ด๋๊ฒ ๋ค ๋์ค์ ์๋ํด๋ด์ผ๊ฒ ๋ค ใ ใ
notifyDataSetChanged์ ๋ฌธ์ ์ !!
๋ฆฌ์คํธ ์ ๋ฐ์ดํธ ํ๋๋ฐ 5๊ฐ์ง ๋ฐฉ๋ฒ์ด์๋ค
๋ฆฌ์คํธ๋ฅผ ์ ๋ฐ์ดํธ ํ๋๋ฐฉ๋ฒ์ค ๊ฐ์ฅ ํฐ๋ฒ์์ธ ๋ฆฌ์คํธ์ ํฌ๊ธฐ์ ์์ดํ ์ด ๋๋ค ๋ณ๊ฒฝ๋๋ ๊ฒฝ์ฐ์ ์ฌ์ฉํ๋ฉด ๋๋ ๊ฒ์ธ
notifyDataSetChanged๋ฅผ ๋ฌด์ง์ฑ์ผ๋ก ์ฐ๋ฉด ๋ชจ๋ ๊ฒฝ์ฐ์ ๋ค ์ ์ฉ์ด์ผ ๋๊ฒ ์ง๋ง ๋นํจ์จ์ ์ผ๋ก ์์ง์ผ๊ฒ์ด๋ค.
๊ทธ๋ฌ๋ฏ๋ก ๋๋จธ์ง 4๊ฐ์ง ๋ฐฉ๋ฒ์ ์ ์ฌ์ ์์ ์ด์ฉํด์ ์์์ ์๋ผ์
๊ด๋ จ ๋ฐฉ๋ฒ๋ค์ ์์ ๋ฆฌํด ๋์ ๋ธ๋ก๊ทธ๊ฐ์์ด ๋งํฌ๋ก ๋จ๊ฒจ๋๊ฒ ๋ค
์๊ทผ๋ฐ ์ด๋ฐฉ๋ฒ ๋ง๊ณ ๋ ์ฐธ์ ํ๊ฑฐ ์จ๋ณด์
๋ฐ๋ก DiffUtil ์ด๋ค.
https://velog.io/@deepblue/RecyclerView%EC%9D%98-notifyDataSetChanged
์ด๋ธ๋ก๊ทธ์ ์๋๊ฑฐ ๊ทธ๋๋ก ๊ตฌํํด๋ดค๋๋ฐ ๋ญ๊ฐ ํ๋ฆฐ๋ถ๋ถ์ด ๋ถ๋ช ์์๊ฑฐ๊ฐ๋ค ๊ธํ๊ฒํด์ ๋์๋๊ฐ๋๋ฐ
์ด์จ๋ ์ถํ์ ๋ค๋ฅธ์๋ฃ๋ค๊ณผ ๋น๊ตํด๋ณด๋ฉด์ ์ฒดํฌํด๋ด์ผ๊ฒ ๋ค
์ถ์ฒ:
https://kumgo1d.tistory.com/44
https://dudmy.net/android/2018/05/02/drag-and-swipe-recyclerview/
https://seunghyun.in/android/1/
https://youngest-programming.tistory.com/285
https://todaycode.tistory.com/55
https://velog.io/@deepblue/RecyclerView%EC%9D%98-notifyDataSetChanged
3์ฃผ์ฐจ
# -์คํํ๋ฉดbandicam.2021-10-30.03-25-12-833.mp4
level1๋คํ๊ณ
level2,3 ๋คํ๋ค.
2-1
NestedScrollableHost.kt
package changhwan.experiment.sopthomework
/*
* Copyright 2019 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import android.content.Context
import android.util.AttributeSet
import android.view.MotionEvent
import android.view.View
import android.view.ViewConfiguration
import android.widget.FrameLayout
import androidx.viewpager2.widget.ViewPager2
import androidx.viewpager2.widget.ViewPager2.ORIENTATION_HORIZONTAL
import kotlin.math.absoluteValue
import kotlin.math.sign
/**
* Layout to wrap a scrollable component inside a ViewPager2. Provided as a solution to the problem
* where pages of ViewPager2 have nested scrollable elements that scroll in the same direction as
* ViewPager2. The scrollable element needs to be the immediate and only child of this host layout.
*
* This solution has limitations when using multiple levels of nested scrollable elements
* (e.g. a horizontal RecyclerView in a vertical RecyclerView in a horizontal ViewPager2).
*/
class NestedScrollableHost : FrameLayout {
constructor(context: Context) : super(context)
constructor(context: Context, attrs: AttributeSet?) : super(context, attrs)
private var touchSlop = 0
private var initialX = 0f
private var initialY = 0f
private val parentViewPager: ViewPager2?
get() {
var v: View? = parent as? View
while (v != null && v !is ViewPager2) {
v = v.parent as? View
}
return v as? ViewPager2
}
private val child: View? get() = if (childCount > 0) getChildAt(0) else null
init {
touchSlop = ViewConfiguration.get(context).scaledTouchSlop
}
private fun canChildScroll(orientation: Int, delta: Float): Boolean {
val direction = -delta.sign.toInt()
return when (orientation) {
0 -> child?.canScrollHorizontally(direction) ?: false
1 -> child?.canScrollVertically(direction) ?: false
else -> throw IllegalArgumentException()
}
}
override fun onInterceptTouchEvent(e: MotionEvent): Boolean {
handleInterceptTouchEvent(e)
return super.onInterceptTouchEvent(e)
}
private fun handleInterceptTouchEvent(e: MotionEvent) {
val orientation = parentViewPager?.orientation ?: return
// Early return if child can't scroll in same direction as parent
if (!canChildScroll(orientation, -1f) && !canChildScroll(orientation, 1f)) {
return
}
if (e.action == MotionEvent.ACTION_DOWN) {
initialX = e.x
initialY = e.y
parent.requestDisallowInterceptTouchEvent(true)
} else if (e.action == MotionEvent.ACTION_MOVE) {
val dx = e.x - initialX
val dy = e.y - initialY
val isVpHorizontal = orientation == ORIENTATION_HORIZONTAL
// assuming ViewPager2 touch-slop is 2x touch-slop of child
val scaledDx = dx.absoluteValue * if (isVpHorizontal) .5f else 1f
val scaledDy = dy.absoluteValue * if (isVpHorizontal) 1f else .5f
if (scaledDx > touchSlop || scaledDy > touchSlop) {
if (isVpHorizontal == (scaledDy > scaledDx)) {
// Gesture is perpendicular, allow all parents to intercept
parent.requestDisallowInterceptTouchEvent(false)
} else {
// Gesture is parallel, query child if movement in that direction is possible
if (canChildScroll(orientation, if (isVpHorizontal) dx else dy)) {
// Child can scroll, disallow all parents to intercept
parent.requestDisallowInterceptTouchEvent(true)
} else {
// Child cannot scroll, allow all parents to intercept
parent.requestDisallowInterceptTouchEvent(false)
}
}
}
}
}
}
๊ตฌ๊ธ์์ ๋ณต๋ถํ๋ค
<changhwan.experiment.sopthomework.NestedScrollableHost
android:layout_width="match_parent"
android:layout_height="0dp"
app:layout_behavior="@string/appbar_scrolling_view_behavior"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintTop_toBottomOf="@+id/tl_fragment_home">
<androidx.viewpager2.widget.ViewPager2
android:id="@+id/vp_fragment_home"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</changhwan.experiment.sopthomework.NestedScrollableHost>
๊ทธํ NestedScrollableHost๋ก ๊ฐ์ธ์คฌ๋ค ๋ด๋ถ์์๋ viewpager2์
level2-2
์ฒ์์ ๊ทธ๋ฅ data์ ์ด๋ ๊ฒ uri์ถ๊ฐํด์ viewhilder๋ก Glide๋ก ๋ฃ์์๋๋ฐ databindingํ๋ฉด์ bindingadapter ๋ก ํด๊ฒฐํ๋ค
BindingAdapters.kt
package changhwan.experiment.sopthomework
import android.graphics.drawable.Drawable
import android.widget.ImageView
import androidx.databinding.BindingAdapter
import androidx.lifecycle.MutableLiveData
import com.bumptech.glide.Glide
object BindingAdapters {
@JvmStatic
@BindingAdapter("recyclerGlide")
fun setImage (imageview : ImageView, url : MutableLiveData<String>){
Glide.with(imageview.context)
.load(url.value)
.circleCrop()
.into(imageview)
}
}
follower_item.xml
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools">
<data>
<variable
name="profileRecycler"
type="changhwan.experiment.sopthomework.FollowerData" />
</data>
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/followerLayout"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<ImageView
android:id="@+id/imageView"
android:layout_width="49dp"
android:layout_height="0dp"
android:layout_marginLeft="21dp"
android:layout_marginTop="24dp"
android:layout_marginBottom="24dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintDimensionRatio="1:1"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:recyclerGlide="@{profileRecycler.followerImg}" />
<TextView
android:id="@+id/followerName"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="22dp"
android:layout_marginTop="25dp"
android:fontFamily="@font/noto_sans_kr"
android:includeFontPadding="false"
android:text="@{profileRecycler.followerName}"
android:textFontWeight="700"
android:textSize="16sp"
android:textStyle="normal"
app:layout_constraintStart_toEndOf="@+id/imageView"
app:layout_constraintTop_toTopOf="parent"
tools:text="์ด๋ฆ" />
<TextView
android:id="@+id/followerIntro"
android:layout_width="150dp"
android:layout_height="wrap_content"
android:layout_marginTop="5dp"
android:layout_marginBottom="24sp"
android:ellipsize="end"
android:fontFamily="@font/noto_sans_kr"
android:includeFontPadding="false"
android:maxLines="1"
android:text="@{profileRecycler.followerIntro}"
android:textFontWeight="400"
android:textSize="14sp"
android:textStyle="normal"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="@+id/followerName"
app:layout_constraintTop_toBottomOf="@+id/followerName"
tools:text="์๊ธฐ์๊ฐ" />
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>
3-1
์ค๋ช ์ ๊ณผ์ ์์ ๋ฐฐ์ด๊ฑฐ์์ ๋คํ์ต๋๋ค.
follower_Item.xml
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools">
<data>
<variable
name="profileRecycler"
type="changhwan.experiment.sopthomework.FollowerData" />
</data>
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/followerLayout"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<ImageView
android:id="@+id/imageView"
android:layout_width="49dp"
android:layout_height="0dp"
android:layout_marginLeft="21dp"
android:layout_marginTop="24dp"
android:layout_marginBottom="24dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintDimensionRatio="1:1"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:recyclerGlide="@{profileRecycler.followerImg}" />
<TextView
android:id="@+id/followerName"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="22dp"
android:layout_marginTop="25dp"
android:fontFamily="@font/noto_sans_kr"
android:includeFontPadding="false"
android:text="@{profileRecycler.followerName}"
android:textFontWeight="700"
android:textSize="16sp"
android:textStyle="normal"
app:layout_constraintStart_toEndOf="@+id/imageView"
app:layout_constraintTop_toTopOf="parent"
tools:text="์ด๋ฆ" />
<TextView
android:id="@+id/followerIntro"
android:layout_width="150dp"
android:layout_height="wrap_content"
android:layout_marginTop="5dp"
android:layout_marginBottom="24sp"
android:ellipsize="end"
android:fontFamily="@font/noto_sans_kr"
android:includeFontPadding="false"
android:maxLines="1"
android:text="@{profileRecycler.followerIntro}"
android:textFontWeight="400"
android:textSize="14sp"
android:textStyle="normal"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="@+id/followerName"
app:layout_constraintTop_toBottomOf="@+id/followerName"
tools:text="์๊ธฐ์๊ฐ" />
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>
FollowerAdapter.kt
package changhwan.experiment.sopthomework
import android.annotation.SuppressLint
import android.content.Intent
import android.view.KeyEvent.ACTION_DOWN
import android.view.LayoutInflater
import android.view.MotionEvent
import android.view.MotionEvent.ACTION_DOWN
import android.view.ViewGroup
import androidx.core.content.ContextCompat
import androidx.core.content.ContextCompat.startActivity
import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.RecyclerView
import changhwan.experiment.sopthomework.databinding.FollowerItemBinding
import com.bumptech.glide.Glide
import kotlinx.coroutines.processNextEventInCurrentThread
class FollowerAdapter(private val listener: ItemDragListener) :
RecyclerView.Adapter<FollowerAdapter.FollowerViewHolder>(), ItemActionListener {
var followerData = mutableListOf<FollowerData>()
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): FollowerViewHolder {
val binding =
FollowerItemBinding.inflate(LayoutInflater.from(parent.context), parent, false)
return FollowerViewHolder(binding, listener)
}
override fun onBindViewHolder(holder: FollowerViewHolder, position: Int) {
holder.onBind(followerData[position])
}
override fun getItemCount(): Int = followerData.size
//diffUtill ๋ถ๋ถ ์ด์ํ๋ฉด ๋์ค์ ๋ฐ๊ฟ์ผํจ
fun setContact(contacts: List<FollowerData>) {
val diffResult =
DiffUtil.calculateDiff(ContactDiffUtil(this.followerData, followerData), false)
diffResult.dispatchUpdatesTo(this)
this.followerData = followerData
}
//์ฌ๊ธฐ๊น์ง diffUtill
//์์ดํ
๋๋๊ทธ ๋๋กญ
override fun onItemMoved(from: Int, to: Int) {
if (from == to) {
return
}
val fromItem = followerData.removeAt(from)
followerData.add(to, fromItem)
notifyItemMoved(from, to)
}
override fun onItemSwiped(position: Int) {
followerData.removeAt(position)
notifyItemRemoved(position)
}
@SuppressLint("ClickableViewAccessibility")
inner class FollowerViewHolder(
private val binding: FollowerItemBinding,
listener: ItemDragListener
) : RecyclerView.ViewHolder(binding.root) {
fun onBind(data: FollowerData) {
binding.profileRecycler = data
// binding.executePendingBindings() -> ์์ด๋ ๋๋จ๋ค ์ด๊ฑฐ ๋ฐ์ธ๋ฉํ ๋ ์์
๋ค ๋น์ฅ๋น์ฅ ์ํํ๋ผ๊ณ ๊ฐ์ํ๋ ํจ์. ๊ทธ๋ฆฌ๊ณ lifecycle owner์ด๋ฐ๋ฃ๋ ์๋ฃ์ด ๋งํ
}
init {
binding.root.setOnClickListener {
val intent = Intent(binding.root?.context, DetailActivity::class.java).apply {
this.putExtra("name", followerData[adapterPosition].followerName.value)
this.putExtra("src", R.drawable.pig)
}
startActivity(binding.root.context, intent, null)
}
binding.root.setOnTouchListener { v, event ->
if (event.action == MotionEvent.ACTION_DOWN) {
listener.onStartDrag(this)
}
false
}
}
}
}
bindingadapter.kt
package changhwan.experiment.sopthomework
import android.graphics.drawable.Drawable
import android.widget.ImageView
import androidx.databinding.BindingAdapter
import androidx.lifecycle.MutableLiveData
import com.bumptech.glide.Glide
object BindingAdapters {
@JvmStatic
@BindingAdapter("recyclerGlide")
fun setImage (imageview : ImageView, url : MutableLiveData<String>){
Glide.with(imageview.context)
.load(url.value)
.circleCrop()
.into(imageview)
}
}
3-2
์ด๊ฒ๋ ์ค๋ช ์ ์ด๋ฒ๊ณผ์ ์์ ๋ฐฐ์ด๊ฒ์ ๋คํ์ต๋๋ค.
์ฌ์ง์ ๋ฐ์ดํฐ๋ฐ์ธ๋ฉ์ผ๋ก ๋ฃ์๋ค glide์์ฐ๊ณ ๊ทธ๋ฅ uri๋ณ์์ ๋ด๊ณ ๋ณ์๋ฐ๋ก ์ด๋ฏธ์ง๋ทฐ์ ์ฐ๊ฒฐํ๋ค
fragment _camera.xml
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools">
<data>
<variable
name="camera"
type="changhwan.experiment.sopthomework.CameraData" />
</data>
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".CameraFragment">
<ImageView
android:id="@+id/camera_img"
android:layout_width="160dp"
android:layout_height="0dp"
android:layout_marginTop="90dp"
android:src="@{camera.picUri}"
app:layout_constraintDimensionRatio="1"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:src="@tools:sample/avatars" />
<TextView
android:id="@+id/camera_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="30dp"
android:fontFamily="@font/noto_sans_kr"
android:includeFontPadding="false"
android:text="์ฌ์ง์ ์ฒจ๋ถํด์ฃผ์ธ์"
android:textColor="#333333"
android:textFontWeight="700"
android:textSize="20sp"
app:layout_constraintEnd_toEndOf="@+id/camera_img"
app:layout_constraintStart_toStartOf="@+id/camera_img"
app:layout_constraintTop_toBottomOf="@+id/camera_img" />
<Button
android:id="@+id/camera_button"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginLeft="30dp"
android:layout_marginTop="30dp"
android:layout_marginRight="30dp"
android:background="@drawable/button_border_pink"
android:text="์ฒจ๋ถํ๊ธฐ"
android:textColor="#FFFFFF"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/camera_text" />
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>
CameraFragment.kt
package changhwan.experiment.sopthomework
import android.Manifest
import android.app.Activity
import android.content.Context
import android.content.Intent
import android.content.pm.PackageManager
import android.database.DatabaseUtils
import android.net.Uri
import android.os.Bundle
import android.provider.MediaStore
import androidx.fragment.app.Fragment
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.activity.result.ActivityResultLauncher
import androidx.activity.result.contract.ActivityResultContracts
import androidx.core.app.ActivityCompat
import androidx.core.content.ContextCompat
import androidx.databinding.DataBindingUtil
import androidx.lifecycle.MutableLiveData
import changhwan.experiment.sopthomework.databinding.FragmentCameraBinding
class CameraFragment : Fragment() {
private var _binding : FragmentCameraBinding? = null
private val binding get() = _binding!!
private lateinit var getContent: ActivityResultLauncher<Intent>
private lateinit var fContext : Context
override fun onAttach(context: Context) {
super.onAttach(context)
fContext = context
}
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
_binding = DataBindingUtil.inflate(inflater,R.layout.fragment_camera, container, false)
binding.lifecycleOwner = viewLifecycleOwner
initPicUri()
initIntent()
return binding.root
}
private fun initPicUri(){
getContent = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) {
val cameraData = CameraData(picUri = MutableLiveData<Uri>().apply { value = it.data?.data })
binding.camera = cameraData
}
}
private fun initIntent(){
val intent = Intent(Intent.ACTION_PICK).apply {
type = MediaStore.Images.Media.CONTENT_TYPE
type = "image/*"
}
binding.cameraButton.setOnClickListener{
var permission = ContextCompat.checkSelfPermission(fContext, Manifest.permission.READ_EXTERNAL_STORAGE)
if(permission == PackageManager.PERMISSION_DENIED) {
ActivityCompat.requestPermissions(requireActivity(),arrayOf(Manifest.permission.READ_EXTERNAL_STORAGE), REQUEST_CODE)
} else {
getContent.launch(intent)
}
}
}
override fun onDestroyView() {
super.onDestroyView()
_binding = null
}
companion object{
const val REQUEST_CODE = 1
}
}
selector์ state_checked ์์ฑ์ ์ด์ฉํ๊ธฐ์ํด ๊ทธ๋ฅ ๋ฒํผ์ด์๋ ๋ผ๋์ค ๋ฒํผ์ ์ด์ฉํด์ ๋ฒํผ์ ๋ง๋ค์ด์คฌ๋ค.
- android:button="@null"
๋ผ๋์ค ๋ฒํผ์ ๋๊ทธ๋ผ๋ฏธ ๋ฒํผ ๋ถ๋ถ ์์ ๋ ค๋ฉด ์์ฑ์ ์ด๋ ๊ฒ ๋ฃ์ด์ฃผ๋ฉด๋๋ค.
fragment์์์ fragment ์ฒ๋ฆฌํ ๋๋ activity์์ ์ฒ๋ฆฌํ ๋์ ๋ค๋ฅด๊ฒ supportFragmentManager ๋ฅผ ์ฌ์ฉํ๋๊ฒ์ด ์๋
childFragmentManager๋ฅผ ์ฌ์ฉํด์ผํ๋ค.
๋ํ ํ๋๊ทธ๋จผํธ์์ ๋ถ๋ชจ์ ํ๋๊ทธ๋จผํธ ๋งค๋์ ๋ฅผ ์ ๊ทผํ๋ ค๋ฉด ex)fragment1์์ activity์ fragment๋ก ์ ๊ทผ
์ด๋ด๊ฒฝ์ฐ์๋ parentFragmentManager๋ฅผ ์ฌ์ฉํ๋ค.
https://ddangeun.tistory.com/127
์์ธํ ์ค๋ช ์ ์ด ๋ธ๋ก๊ทธ๋ฅผ ์ฐธ๊ณ ํ์
dependency๋ฅผ ์ถ๊ฐ
implementation 'com.github.bumptech.glide:glide:4.11.0'
annotationProcessor 'com.github.bumptech.glide:compiler:4.11.0'
์ต์ ์๋ ์ด๋ฏธ์ง๋ก๋
/* Activity์์ ์ฌ์ฉํ ๊ฒฝ์ฐ */
Glide.with(this)
.load(R.drawable.img_file_name)
.into(imageView)
/* ViewHolder์์ ์ฌ์ฉํ ๊ฒฝ์ฐ */
Glide.with(itemView)
.load(R.drawable.img_file_name)
.into(itemView.imageView)
๋ทฐํ๋์์๋ itemview ์ binding.root๋ก ๋์ฒดํ๋ฉด๋๋ค.
level 2-2 ์ฒ๋ผ ๊ทธ๋ฅ ์ด๋ฏธ์ง url๋ฃ์ด์ฃผ๋ฉด ์์์ ํ์๋จglide
๊ฐํจ์ ์ค๋ช
- with() : View, Fragment ํน์ Activity๋ก๋ถํฐ Context๋ฅผ ๊ฐ์ ธ์จ๋ค.
- load() : ์ด๋ฏธ์ง๋ฅผ ๋ก๋ํ๋ค. ๋ค์ํ ๋ฐฉ๋ฒ์ผ๋ก ์ด๋ฏธ์ง๋ฅผ ๋ถ๋ฌ์ฌ ์ ์๋ค. (Bitmap, Drawable, String, Uri, File, ResourId(Int), ByteArray)
- into() : ์ด๋ฏธ์ง๋ฅผ ๋ณด์ฌ์ค View๋ฅผ ์ง์ ํ๋ค.
- placeholder() : Glide ๋ก ์ด๋ฏธ์ง ๋ก๋ฉ์ ์์ํ๊ธฐ ์ ์ ๋ณด์ฌ์ค ์ด๋ฏธ์ง๋ฅผ ์ค์ ํ๋ค.
- error() : ๋ฆฌ์์ค๋ฅผ ๋ถ๋ฌ์ค๋ค๊ฐ ์๋ฌ๊ฐ ๋ฐ์ํ์ ๋ ๋ณด์ฌ์ค ์ด๋ฏธ์ง๋ฅผ ์ค์ ํ๋ค.
- fallback() : loadํ url์ด null์ธ ๊ฒฝ์ฐ ๋ฑ ๋น์ด์์ ๋ ๋ณด์ฌ์ค ์ด๋ฏธ์ง๋ฅผ ์ค์ ํ๋ค.
- skipMemoryCache() : ๋ฉ๋ชจ๋ฆฌ์ ์บ์ฑํ์ง ์์ผ๋ ค๋ฉด true๋ก ์ค๋ค.
- diskCacheStrategy() : ๋์คํฌ์ ์บ์ฑํ์ง ์์ผ๋ ค๋ฉด DiskCacheStrategy.NONE๋ก ์ค๋ค. ๋ค์๊ณผ ๊ฐ์ ์ต์ ์ด ์๋ค. (ALL, AUTOMATIC, DATA, NONE, RESOURCE)
gif๋ก๋ฉ๊ธฐ๋ฅ ์๋๋ฐ ์ด๊ฑด ๋กํฐ์ฐ๋๊ฒ ๋ ์ข์๊ฑฐ์๋?
https://blog.yena.io/studynote/2020/06/10/Android-Glide.html
๊ธฐํ ์ฐธ๊ณ ์ฌํญ์ ์ด๋ธ๋ก๊ทธ๋ฅผ ์ฐธ๊ณ ํ์
๊ตฌ๊ธ ๊ณต์๋ฌธ์์ ๋์์๋ ๋ฐฉ๋ฒ๋๋กํ๋ค
https://developer.android.com/training/animation/vp2-migration?hl=ko
๊ตฌ์ฒด์ ์ธ ๋ฐฉ๋ฒ์
1.NestedScrollableHost.kt ํ์ผ์ถ๊ฐ
๋งํฌ์ ๋ด์ฉ์ ๊ธ์ด์ NestedScrollableHost.kt ๋ฅผ ์ถ๊ฐ์์ผ์ค๋ค.
2.xml ์์ ์ค์ฒฉ๋๋ ์ฆ ๋ด๋ถ์ ์คํฌ๋กค๋ทฐ(viewpager2) ์ NestedScrollableHost ์์์ฃผ๊ธฐ
<androidx.coordinatorlayout.widget.CoordinatorLayout>
<com.google.android.material.appbar.AppBarLayout
android:id="@+id/appBarLayout">
<androidx.appcompat.widget.Toolbar
android:id="@+id/toolbar">
<com.google.android.material.tabs.TabLayout
android:id="@+id/chipsLayout" />
</androidx.appcompat.widget.Toolbar>
</com.google.android.material.appbar.AppBarLayout>
<com.nasrabadiam.widget.widget.NestedScrollableHost
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_behavior="@string/appbar_scrolling_view_behavior">
<androidx.viewpager2.widget.ViewPager2
android:id="@+id/chipsViewPager"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</com.nasrabadiam.widget.NestedScrollableHost>
</androidx.coordinatorlayout.widget.CoordinatorLayout>
์ด๋ฐ์์ผ๋ก
๋ด๋ถ์ viewpager2๋ฅผ NestedScrollableHost๋ก ๊ฐ์ธ์ค๋ค.
์ด๋ฌ๋ฉด ๋!!!!
์ฐธ๊ณ ๋ธ๋ก๊ทธ:
https://medium.com/@nasrabadiam/support-nested-scrollable-elements-inside-viewpager2-59fa34978899
๋ฆฌํํ ๋ง์ ํด๋ณด์
\1. ๋น์ฐํ gradle์ถ๊ฐํด์ฃผ๊ณ
android {
...
dataBinding {
enabled = true
}
}
2.๋ฆฌ์ฌ์ดํด๋ฌ์ ์์ดํ ๋ทฐ ์ xml์ ์ผ๋ก ๊ฐ์ผ๋ค.
์์ดํ ์ ๊ฐ์๋ค.
3.data variable ์ถ๊ฐ
layout์์ ์ถ๊ฐํด์ค๋ค
<data>
<variable
name="profileRecycler"
type="changhwan.experiment.sopthomework.FollowerData" />
</data>
4.view์ data๋ฅผ @{}๋ก bindํด์ค๋ค.
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools">
<data>
<variable
name="profileRecycler"
type="changhwan.experiment.sopthomework.FollowerData" />
</data>
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/followerLayout"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<ImageView
android:id="@+id/imageView"
android:layout_width="49dp"
android:layout_height="0dp"
android:layout_marginLeft="21dp"
android:layout_marginTop="24dp"
android:layout_marginBottom="24dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintDimensionRatio="1:1"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:recyclerGlide="@{profileRecycler.followerImg}" />
<TextView
android:id="@+id/followerName"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="22dp"
android:layout_marginTop="25dp"
android:fontFamily="@font/noto_sans_kr"
android:includeFontPadding="false"
android:text="@{profileRecycler.followerName}"
android:textFontWeight="700"
android:textSize="16sp"
android:textStyle="normal"
app:layout_constraintStart_toEndOf="@+id/imageView"
app:layout_constraintTop_toTopOf="parent"
tools:text="์ด๋ฆ" />
<TextView
android:id="@+id/followerIntro"
android:layout_width="150dp"
android:layout_height="wrap_content"
android:layout_marginTop="5dp"
android:layout_marginBottom="24sp"
android:ellipsize="end"
android:fontFamily="@font/noto_sans_kr"
android:includeFontPadding="false"
android:maxLines="1"
android:text="@{profileRecycler.followerIntro}"
android:textFontWeight="400"
android:textSize="14sp"
android:textStyle="normal"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="@+id/followerName"
app:layout_constraintTop_toBottomOf="@+id/followerName"
tools:text="์๊ธฐ์๊ฐ" />
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>
5.viewholder ํด๋์ค์์ binding๊ฐ์ฒด์ ๋ฐ์ดํฐ ๋ด์์ค๋ค.
onbind ํจ์์์ ์ผ์ผํ ๋ค ๋ฃ์๋๊ฑฐ ๊ทธ๋ฅ itemview์์ ์ค์ ํด๋์ ๋ฐ์ดํฐ ๋ณ์์ ๋งค๋ฒ ๋ค์ด์ค๋ data๋ฅผ ๋ฃ์ด์ฃผ๋๊ฑฐ๋ก ๋ฐ๊ฟ์ค๋ค.
inner class FollowerViewHolder(
private val binding: FollowerItemBinding,
listener: ItemDragListener
) : RecyclerView.ViewHolder(binding.root) {
fun onBind(data: FollowerData) {
binding.profileRecycler = data
// binding.executePendingBindings() -> ์์ด๋ ๋๋จ๋ค ์ด๊ฑฐ ๋ฐ์ธ๋ฉํ ๋ ์์
๋ค ๋น์ฅ๋น์ฅ ์ํํ๋ผ๊ณ ๊ฐ์ํ๋ ํจ์.
}
//์ค๋ต
}
6.์ด๋ฏธ์ง ๊ฐ์๊ฑฐ ์ฒ๋ฆฌ๋ฅผ์ํด bindingadpter ๋ง๋ค์ด์ฃผ๊ธฐ
package changhwan.experiment.sopthomework
import android.graphics.drawable.Drawable
import android.widget.ImageView
import androidx.databinding.BindingAdapter
import androidx.lifecycle.MutableLiveData
import com.bumptech.glide.Glide
object BindingAdapters {
@JvmStatic
@BindingAdapter("recyclerGlide")
fun setImage (imageview : ImageView, url : MutableLiveData<String>){
Glide.with(imageview.context)
.load(url.value)
.circleCrop()
.into(imageview)
}
}
๊ธฐ์กด์ bindingadapter ๊ณต๋ถํ๋๊ฒ์ฒ๋ผ ์ฒ๋ฆฌ ๋ถ๊ฐ๋ฅํ๊ฑฐ ๋ง๋ค์ด์ค๋ค. -> ์ด๋ฏธ์ง์ฒ๋ฆฌ ์ด์ glide ์ถ๊ฐํ๊ธฐ์ ๊ทธ๊ฑธ๋ก ์ฒ๋ฆฌํ๋ค.
๊ทธ๋์ ์ด๋ฏธ์ง๋ทฐ์์ ์ด๋ฏธ์ง ๋ฃ๋๋ถ๋ถ์ด ์ด๋ ๋ค
app:recyclerGlide="@{profileRecycler.followerImg}"
๊ฒฐ๋ก ์ ์ผ๋ก layout ๊ฐ์ธ์ฃผ๊ณ ๋ฐ์ดํฐ ๋ง๋ค์ด์ฃผ๋๊ณณ์ item์ด๊ณ
viewholder์์ ๋ฐ์ดํฐ ์ง์ด๋ฃ์ด์ฃผ๋ํํ์ด๋ค
์ฐธ๊ณ ๋ธ๋ก๊ทธ:
https://salix97.tistory.com/244
์๋ ์ ํ๋ก์ ํธ ํ ๋๋ ๋ผ์ด๋ธ๋ฌ๋ฆฌ ์ด์ฉํด์ ํฌ๋กญ๊ธฐ๋ฅ๊ณผ ์จ๋ฒ์ ๊ทผ๊น์งํด์ ๊ถํ์ค์ ๊น์ง ๋คํด์ ํธํ๋๋ฐ ๊ทธ๋ฅ๊ฐ๋จํ๊ฒ ํ๋ ค๊ณ ๋ผ์ด๋ธ๋ฌ๋ฆฌ ์ด์ฉ ์ํ๋ ค๋ ์คํ๋ ค ๋๋ณต์กํ๋ค. ์ด์จ๋ ์ด๋ฏธ์ง ๋๊ณ ์ค๋๊ฑฐ ์์ฒด๋ ๊ฐํธํ๋ ๊ฐค๋ฌ๋ฆฌ๋ก intent๋ฅผ ํตํด์ ๊ทผํด์ 1์ฅ uri๋ก ๋ฐ์์ค๋ ๊ฒ์ ๋ค๋ค๋ณด๋ คํ๋ค.
package changhwan.experiment.sopthomework
import android.Manifest
import android.app.Activity
import android.content.Context
import android.content.Intent
import android.content.pm.PackageManager
import android.database.DatabaseUtils
import android.net.Uri
import android.os.Bundle
import android.provider.MediaStore
import androidx.fragment.app.Fragment
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.activity.result.ActivityResultLauncher
import androidx.activity.result.contract.ActivityResultContracts
import androidx.core.app.ActivityCompat
import androidx.core.content.ContextCompat
import androidx.databinding.DataBindingUtil
import androidx.lifecycle.MutableLiveData
import changhwan.experiment.sopthomework.databinding.FragmentCameraBinding
class CameraFragment : Fragment() {
private var _binding : FragmentCameraBinding? = null
private val binding get() = _binding!!
private lateinit var getContent: ActivityResultLauncher<Intent>
private lateinit var fContext : Context
override fun onAttach(context: Context) {
super.onAttach(context)
fContext = context
}
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
_binding = DataBindingUtil.inflate(inflater,R.layout.fragment_camera, container, false)
binding.lifecycleOwner = this
initPicUri()
initIntent()
return binding.root
}
private fun initPicUri(){
getContent = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) {
val cameraData = CameraData(picUri = MutableLiveData<Uri>().apply { value = it.data?.data })
binding.camera = cameraData
}
}
private fun initIntent(){
val intent = Intent(Intent.ACTION_PICK).apply {
type = MediaStore.Images.Media.CONTENT_TYPE
type = "image/*"
}
binding.cameraButton.setOnClickListener{
var permission = ContextCompat.checkSelfPermission(fContext, Manifest.permission.READ_EXTERNAL_STORAGE)
if(permission == PackageManager.PERMISSION_DENIED) {
ActivityCompat.requestPermissions(requireActivity(),arrayOf(Manifest.permission.READ_EXTERNAL_STORAGE), REQUEST_CODE)
} else {
getContent.launch(intent)
}
}
}
override fun onDestroyView() {
super.onDestroyView()
_binding = null
}
companion object{
const val REQUEST_CODE = 1
}
}
์ฐ์ ์ ์ฒด ์ฝ๋๋ ์ด๋ ๊ณ ์ด๋ฏธ์ง๋ uriํํ๋ก ๋ฐ์์์ ๋ณ์์ ๋ฃ๊ณ ๊ทธ๋ณ์๋ฅผ ๊ทธ๋ฅ ๋ฐ๋ก databinding์ ํตํด์ ๋ฃ์ด์คฌ๋ค.
์ชผ๊ฐ์ ๋ณด์๋ฉด
private lateinit var getContent: ActivityResultLauncher<Intent>
\1. getContent๋ผ๋ ๋ณ์ ์์ฑ
๋์ค์ ์ด๋ ๊ฒ ์ด๊ธฐํํด์ค๋ค
private fun initPicUri(){
getContent = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) {
val cameraData = CameraData(picUri = MutableLiveData<Uri>().apply { value = it.data?.data })
binding.camera = cameraData
}
}
์๋ ๊ตฌ๊ธ์์๋ startActivityForResult๊ฐ ์๋ ๊ธฐ๋ณธ์ผ๋ก ์ ๊ณตํด์ฃผ๋ Contrat ํจ์์ธ GetContent()๋ฅผ ์ฐ๋ผ ํ์ง๋ง ์ด๊ฑธ ์ด์ฉํ๋ฉด ๋ถ๋ณํ UI IMAGE PICKER ๊ฐ ๋จ๊ฒ๋๋ค ๊ทธ๋์ ๊ทธ๋ฅ ActivityResultContracts.StartActivityForResult() ๋ฅผ ์ด์ฉํด์ ์จ๋ฒ์ ์ ๊ทผํ์๋ค.
2.์ธํ ํธ ๋ณ์ ๋ง๋ค๊ธฐ
private fun initIntent(){
val intent = Intent(Intent.ACTION_PICK).apply {
type = MediaStore.Images.Media.CONTENT_TYPE
type = "image/*"
}
์ด๋ ๊ฒ intent๋ฅผ ์ค์ ํด์ค๋ค.
3.button์ ๋๋ฆฌ๋ฉด launch๋๋๋ก ์ค์
binding.cameraButton.setOnClickListener{
var permission = ContextCompat.checkSelfPermission(fContext, Manifest.permission.READ_EXTERNAL_STORAGE)
if(permission == PackageManager.PERMISSION_DENIED) {
ActivityCompat.requestPermissions(requireActivity(),arrayOf(Manifest.permission.READ_EXTERNAL_STORAGE), REQUEST_CODE)
} else {
getContent.launch(intent)
}
}
getContent.Launch(intent)๋ฅผ ํตํด์ ์จ๋ฒ์ผ๋ก ๋์ด๊ฐ๋ค.
๋๋จธ์ง ์ฝ๋๋ ๊ถํ ๊ด๋ จ์ฝ๋์ด๋ค ๊ถํ์ ๋ํ๊ฒ์ ๋ฐ์ ์์๋ณผ๊ฒ์ด๋ค.
์ฐธ๊ณ ๋ธ๋ก๊ทธ:
https://youngest-programming.tistory.com/517
์ฐธ๊ณ ์กํฐ๋นํฐ์ ํ๋๊ทธ๋จผํธ์์ ๊ถํ ๋ฐ์์ค๋๊ฒ ์๊ทผ ๋ง์ด ๋ค๋ฅด๋ค ์ด๊ฑธ ์ข ์ธ์งํ๊ณ ๊ฐ์ ํ๋๊ทธ๋จผํธ์์๋ ๊ณ ๋ คํด์ผํ ๊ฒ๋ค์ด์๋ค -> ์ปจํ ์คํธ, ์กํฐ๋นํฐ
๊ฐค๋ฌ๋ฆฌ ์ ๊ทผํ๋ ค๋ฉด ๊ถํ์ ์ป์ด์ผํ๋ค.
์ฐ์ manifest์ storage ์ฝ๊ธฐ์ฐ๊ธฐ ๊ถํ์ ์ถ๊ฐํ๋ค.
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
์ผ๋จ ์์ธํ๊ฒ ํด์ผํ ๊ฒ ๋ชจ๋ ๋์์๋ ๋ธ๋ก๊ทธ๊ธ๊ณผ ๊ฐ๋จํ๊ฒ ์ ๋ฆฌ๋ ๋ธ๋ก๊ทธ๊ธ ํ๋์ฉ ๋งํฌ๋ฅผ ๋จ๊ฒจ๋๋๋ค.
๋ณต์ก:
https://manorgass.tistory.com/74
๊ฐ๋จ:
https://superwony.tistory.com/101
์ ์ฉ์ ๊ฐ๋จํ๊ฑธ๋ก ํ๋ค.
์ฝ๋๋ฅผ ๋ด๋ณด์
binding.cameraButton.setOnClickListener{
var permission = ContextCompat.checkSelfPermission(fContext, Manifest.permission.READ_EXTERNAL_STORAGE)
if(permission == PackageManager.PERMISSION_DENIED) {
ActivityCompat.requestPermissions(requireActivity(),arrayOf(Manifest.permission.READ_EXTERNAL_STORAGE), REQUEST_CODE)
} else {
getContent.launch(intent)
}
}
}
ContextCompat.checkSelfPermission(fContext, Manifest.permission.READ_EXTERNAL_STORAGE)
์ด๋ถ๋ถ์ ์ฝ๊ธฐ ๊ถํ์ ์ฌ์ฉ์์๊ฒ ๋ฐ์๋์ง ์๋ฐ์๋์ง ๊ฐ์ ธ์ค๋ ๋ถ๋ถ์ธ๋ฐ ์ฒซ๋ฒ์งธ ์ธ์๋ก context๋ฅผ ๋ฃ์ด์ค์ผํ๋ค.
๊ทผ๋ฐ fragment๋ context๊ฐ ์๊ธฐ์ ๋ถ๋ชจ ์กํฐ๋นํฐ์ context๋ฅผ ๊ฐ์ ธ์์ผํ๋๋ฐ 2๊ฐ์ง๋ฐฉ๋ฒ์ด์๋ค
-๋ถ์ fragment์์ context ๊ฐ์ ธ์ค๊ธฐ
1.requireContext() ํจ์์ด์ฉ ์ดํจ์๋ฅผ ์ฐ๋ฉด getcontext ์๋ ๋ค๋ฅด๊ฒ notnullํ context๋ฅผ ๋ฐํํ๋ค.
2.onAttach ํจ์ ์ค๋ฒ๋ผ์ด๋ฉํด์ context ๋ฐ์์ค๊ธฐ
onAttach์ ์ธ์๋ก ๋ถ๋ชจ์ context๊ฐ ๋ค์ด์ค๊ธฐ์ ๊ฑฐ๊ธฐ์ ์ ์ญ๋ณ์์ ๋ด์์ ์ฌ์ฉํด๋๋๋ค.
์ด์จ๋ ์ด๋ ๊ฒ context ๊ฐ์ ธ์์ ์ฒซ๋ฒ์งธ ์ธ์์๋ฃ๊ณ ๋๋ฒ์จฐ ์ธ์์ ๋ฌด์จ ๊ถํ์ธ์ง ๋ฃ์ด์ฃผ๋ฉด ๊ถํ์ ๋ฐ์์ง ์๋ฐ์์ง ์ฌ๋ถ๋ฅผ ์๋ ค์ค๋ค.
๊ทธ๊ฑฐ๋ฅผ ์กฐ๊ฑด๋ฌธ์ผ๋ก ๊ถํ ์๋ฐ์์ผ๋ฉด ๋ฐ๋ ์ฝ๋๋ฅผ
์ด๋ฏธ ๋ฐ์์ ธ์๋ค๋ฉด ๋ฐ๋ก ์คํํด์ฃผ๋ ์ฝ๋๋ฅผ ์ง๊ณ
๊ถํ ๋ฐ์์ค๋ ๋ถ๋ถ์ ๋ด๋ณด์
ActivityCompat.requestPermissions(requireActivity(),arrayOf(Manifest.permission.READ_EXTERNAL_STORAGE), REQUEST_CODE)
์ด๋ ๊ฒ
ActivityCompat.requestPermissions ๋ฅผ ์ฌ์ฉํ๋๋ฐ ์ฒซ๋ฒ์งธ ์ธ์๊ฐ activity๋ฅผ ์๊ตฌํ๋ค.
๊ทธ๋์ ํ๋๊ทธ ๋จผํธ์์ ์ฌ์ฉํ๋๊ฒ์ ๊ฒ์ํด๋ณด๋ ๊ทธ๋ฅ requestPermissions๋ฅผ ์ด์ฉํ๋ ๋ฐฉ๋ฒ์ด๋์ค๋๋ฐ ์คํ์ ๋์ง๋ง ์ด๊ฑด deprecated ๋์๊ธฐ์
ํ๋๊ทธ๋จผํธ์ ์กํฐ๋นํฐ๋ฅผ ๊ฐ์ ธ์ฌ์์๋ ๋ฐฉ๋ฒ์ ๋ด๋ณด์
-๋ถ์ fragment์์ ๋ถ๋ชจ activity๊ฐ์ ธ์ค๊ธฐ
requireActivity() ํจ์๋ฅผ ์ด์ฉํ๋ค๋ฉด ๋ถ๋ชจ Activity๋ฅผ ๊ฐ์ ธ์จ๋ค.
getActivity์ ๋ค๋ฅธ์ ์ notNullํ Activity๋ฅผ ๋ฐํํ๋ค.
๊ทธ๋์ ์ด๋ ๊ฒ ActivityCompat.requestPermissions ๋ฅผ ์ด์ฉํด์ ๊ถํ์ ๋ฐ์์จ๋ค.
4์ฃผ์ฐจ
bandicam.2021-11-12.16-58-11-620.mp4
level1 ๋คํ๊ณ ์๋ฌผ๋ก ๋ก๊ทธ์ธ๊ณผ ํ์๊ฐ์ ๋งํจ
level2,3๋คํจ
์ค๋ช ์ ์ด๋ฒ๊ณผ์ ๋ฅผ ํตํด ๋ฐฐ์ด๋ด์ฉ์ ๋ค์ ์ด๋จ๋ค.
level1 sign in์ด ์ฃผ๋ผ sign in๋ง ๋ฃ๊ฒ ์ต๋๋ค ใ ใ ์ง์ก
RequestSignInData.kt
package changhwan.experiment.sopthomework
data class RequestSignInData(
val email : String,
val password : String
)
ResponseSigninData
package changhwan.experiment.sopthomework
import android.provider.ContactsContract
data class ResponseSignInData(
val id: Int,
val name: String,
val email: String
)
SignInService.kt
package changhwan.experiment.sopthomework
import retrofit2.Call
import retrofit2.Response
import retrofit2.http.Body
import retrofit2.http.Headers
import retrofit2.http.POST
interface SignInService {
@POST("user/login")
suspend fun postSignIn(
@Body body: RequestSignInData
):Response<ResponseWrapper<ResponseSignInData>>
}
serviceCreator.kt
package changhwan.experiment.sopthomework
import okhttp3.Interceptor
import okhttp3.OkHttpClient
import retrofit2.Retrofit
import retrofit2.converter.gson.GsonConverterFactory
object ServiceCreator {
private val headerInterceptor = Interceptor{
val request = it.request()
.newBuilder()
.addHeader("Content-Type","application/json")
.build()
return@Interceptor it.proceed(request)
}
val client: OkHttpClient = OkHttpClient.Builder()
.addInterceptor(headerInterceptor)
.build()
private const val SOPT_BASE_URL= "https://asia-northeast3-we-sopt-29.cloudfunctions.net/api/"
private val SoptRetrofit :Retrofit = Retrofit.Builder()
.baseUrl(SOPT_BASE_URL)
.client(client)
.addConverterFactory(GsonConverterFactory.create())
.build()
val signUpService :SignUpService = SoptRetrofit.create(SignUpService::class.java)
val signInService :SignInService = SoptRetrofit.create(SignInService::class.java)
private const val GITHUB_BASE_URL="https://api.github.com/"
private val GitHubRetrofit : Retrofit = Retrofit.Builder()
.baseUrl(GITHUB_BASE_URL)
.addConverterFactory(GsonConverterFactory.create())
.build()
val gitHubService :GitHubService = GitHubRetrofit.create(GitHubService::class.java)
}
SignViewModel.kt
package changhwan.experiment.sopthomework
import android.net.Network
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import kotlinx.coroutines.*
import retrofit2.Call
import retrofit2.Callback
import retrofit2.Response
import kotlin.coroutines.CoroutineContext
class SignViewModel : ViewModel() {
private val _viewEmail = MutableLiveData<String>()
private val _viewName = MutableLiveData<String>()
private val _viewPassword = MutableLiveData<String>()
private val _conSuccess = MutableLiveData<Boolean>()
val viewEmail: LiveData<String>
get() = _viewEmail
val viewName: LiveData<String>
get() = _viewName
val viewPassword: LiveData<String>
get() = _viewPassword
val conSuccess: LiveData<Boolean>
get() = _conSuccess
fun getEmail(email: String) {
_viewEmail.value = email
}
fun getName(name: String) {
_viewName.value = name
}
fun getPassword(password: String) {
_viewPassword.value = password
}
fun startSignUp() {
val requestSignUpData = RequestSignUpData(
email = _viewEmail.value!!,
name = _viewName.value!!,
password = _viewPassword.value!!
)
val call: Call<ResponseSignUpData> =
ServiceCreator.signUpService.postSignUp(requestSignUpData)
call.enqueue(object : Callback<ResponseSignUpData> {
override fun onResponse(
call: Call<ResponseSignUpData>,
response: Response<ResponseSignUpData>
) {
if (response.isSuccessful) {
val data = response.body()?.data
if (data != null) {
_viewName.value = data.name
_viewEmail.value = data.email
}
_conSuccess.value = true
} else {
_conSuccess.value = false
}
}
override fun onFailure(call: Call<ResponseSignUpData>, t: Throwable) {
_conSuccess.value = false
}
})
}
fun startSignIn() {
val requestSignInData = RequestSignInData(
email = _viewEmail.value!!,
password = _viewPassword.value!!
)
// val call : Call<ResponseSignInData> = ServiceCreator.signInService.postSignIn(requestSignInData)
//
// call.enqueue(object : Callback<ResponseSignInData>{
// override fun onResponse(
// call: Call<ResponseSignInData>,
// response: Response<ResponseSignInData>
// ) {
// val data = response.body()?.data
//
// if(response.isSuccessful){
// if (data != null) {
// _viewName.value = data.name
// }
// _conSuccess.value = true
// } else {
// _conSuccess.value = false
// }
// }
//
// override fun onFailure(call: Call<ResponseSignInData>, t: Throwable) {
// _conSuccess.value = false
// }
//
// })
viewModelScope.launch {
val response = ServiceCreator.signInService.postSignIn(requestSignInData)
val data = response.body()?.data
if (response.isSuccessful) {
if (data != null) {
_viewName.value = data.name
}
_conSuccess.value = true
} else {
_conSuccess.value = false
}
}
}
}
SignInActivity
package changhwan.experiment.sopthomework
import android.content.Intent
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.widget.Toast
import androidx.activity.result.ActivityResultLauncher
import androidx.activity.result.contract.ActivityResultContracts
import androidx.activity.viewModels
import androidx.databinding.DataBindingUtil
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.Observer
import changhwan.experiment.sopthomework.databinding.ActivitySignInBinding
class SignInActivity : AppCompatActivity() {
private lateinit var binding: ActivitySignInBinding
private lateinit var getResult : ActivityResultLauncher<Intent>
private val signInViewModel by viewModels<SignViewModel>()
val signInEmail = MutableLiveData<String>()
val signInPassword = MutableLiveData<String>()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = DataBindingUtil.setContentView(this,R.layout.activity_sign_in)
binding.signInData = this
binding.lifecycleOwner = this
startLogin()
startSignUp()
observeSuccess()
}
private fun startLogin(){
binding.loginButton.setOnClickListener {
signInEmail.value?.let { signInViewModel.getEmail(it) }
signInPassword.value?.let { signInViewModel.getPassword(it) }
signInViewModel.startSignIn()
}
}
private fun observeSuccess(){
signInViewModel.conSuccess.observe(this, Observer {
if (signInViewModel.conSuccess.value == true) {
val intent = Intent(this,HomeActivity::class.java)
startActivity(intent)
Toast.makeText(this, "${signInViewModel.viewName.value}๋ ํ์ํฉ๋๋ค", Toast.LENGTH_SHORT).show()
} else {
binding.inEditId.text.clear()
binding.inEditPw.text.clear()
Toast.makeText(this, "๋ก๊ทธ์ธ์คํจ", Toast.LENGTH_SHORT).show()
}
})
}
private fun startSignUp(){
binding.signUpButton.setOnClickListener {
val intent = Intent(this,SignUpActivity::class.java)
getResult.launch(intent)
}
getResult = registerForActivityResult(
ActivityResultContracts.StartActivityForResult()){
if(it.resultCode == RESULT_OK) {
binding.inEditId.text.clear()
binding.inEditId.text.append(it.data?.getStringExtra("Id"))
binding.inEditPw.text.clear()
binding.inEditPw.text.append(it.data?.getStringExtra("Pw"))
}
}
}
}
level2-1
ResponseGitHubFollowerData.kt
package changhwan.experiment.sopthomework
data class ResponseGitHubFollowerData(
val login: String,
val id: Int,
val node_id: String,
val avatar_url: String,
val gravatar_id: String,
val url: String,
val html_url: String,
val followers_url: String,
val following_url: String,
val gists_url: String,
val starred_url: String,
val subscriptions_url: String,
val organizations_url: String,
val repos_url: String,
val events_url: String,
val received_events_url: String,
val type: String,
val site_admin: Boolean,
)
ResponseGitHubUserData.kt
package changhwan.experiment.sopthomework
data class ResponseGithubUserData(
val login: String,
val id: Int,
val node_id: String,
val avatar_url: String,
val gravatar_id: String,
val url: String,
val html_url: String,
val followers_url: String,
val following_url: String,
val gists_url: String,
val starred_url: String,
val subscriptions_url: String,
val organizations_url: String,
val repos_url: String,
val events_url: String,
val received_events_url: String,
val type: String,
val site_admin: Boolean,
val name: String,
val company: String?,
val blog: String?,
val location: String?,
val email: String?,
val hireable: String?,
val bio: String?,
val twitter_username: String?,
val public_repos: Int?,
val public_gists: Int?,
val followers: Int,
val following: Int,
val created_at: String,
val updated_at: String
)
GitHubService.kt
package changhwan.experiment.sopthomework
import retrofit2.Call
import retrofit2.http.GET
import retrofit2.http.Headers
import retrofit2.http.Path
interface GitHubService {
@GET("users/{userId}/followers")
fun getGitHubFollowers(
@Path("userId") userId:String
): Call<List<ResponseGitHubFollowerData>>
@GET("users/{userId}")
fun getGitHubUsers(
@Path("userId") userId:String
): Call<ResponseGithubUserData>
}
serviceCreator.kt
package changhwan.experiment.sopthomework
import okhttp3.Interceptor
import okhttp3.OkHttpClient
import retrofit2.Retrofit
import retrofit2.converter.gson.GsonConverterFactory
object ServiceCreator {
private val headerInterceptor = Interceptor{
val request = it.request()
.newBuilder()
.addHeader("Content-Type","application/json")
.build()
return@Interceptor it.proceed(request)
}
val client: OkHttpClient = OkHttpClient.Builder()
.addInterceptor(headerInterceptor)
.build()
private const val SOPT_BASE_URL= "https://asia-northeast3-we-sopt-29.cloudfunctions.net/api/"
private val SoptRetrofit :Retrofit = Retrofit.Builder()
.baseUrl(SOPT_BASE_URL)
.client(client)
.addConverterFactory(GsonConverterFactory.create())
.build()
val signUpService :SignUpService = SoptRetrofit.create(SignUpService::class.java)
val signInService :SignInService = SoptRetrofit.create(SignInService::class.java)
private const val GITHUB_BASE_URL="https://api.github.com/"
private val GitHubRetrofit : Retrofit = Retrofit.Builder()
.baseUrl(GITHUB_BASE_URL)
.addConverterFactory(GsonConverterFactory.create())
.build()
val gitHubService :GitHubService = GitHubRetrofit.create(GitHubService::class.java)
}
GitHubViewModel.kt
package changhwan.experiment.sopthomework
import android.util.Log
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import retrofit2.Call
import retrofit2.Callback
import retrofit2.Response
class GitHubViewModel : ViewModel() {
private val _followerList = mutableListOf<MutableLiveData<String>>()
private val _followerAvatarUrl = mutableListOf<MutableLiveData<String>>()
private val _bio = mutableListOf<MutableLiveData<String>>()
private val _conclusionData = mutableListOf<FollowerData>()
private val _getFollowerDataDone = MutableLiveData<Event<String>>()
private val _getUserDataDone = MutableLiveData<Event<String>>()
private val _getConclusionDataDone = MutableLiveData<Event<String>>()
val followerList: List<MutableLiveData<String>>
get() = _followerList
val followerAvatarUrl: List<MutableLiveData<String>>
get() = _followerAvatarUrl
val bio: List<MutableLiveData<String>>
get() = _bio
val conclusionData: List<FollowerData>
get() = _conclusionData
val getFollowerDataDone: LiveData<Event<String>>
get() = _getFollowerDataDone
val getUserDataDone: LiveData<Event<String>>
get() = _getUserDataDone
val getConclusionDataDone: LiveData<Event<String>>
get() = _getConclusionDataDone
fun getGitHubFollowerData() {
val call: Call<List<ResponseGitHubFollowerData>> =
ServiceCreator.gitHubService.getGitHubFollowers("2chang5")
call.enqueue(object : Callback<List<ResponseGitHubFollowerData>> {
override fun onResponse(
call: Call<List<ResponseGitHubFollowerData>>,
response: Response<List<ResponseGitHubFollowerData>>
) {
if (response.isSuccessful) {
val data = response.body()
if (data != null) {
_followerList.clear()
_followerAvatarUrl.clear()
for (i in data) {
_followerList.add(MutableLiveData<String>().apply { value = i.login })
_followerAvatarUrl.add(MutableLiveData<String>().apply { value = i.avatar_url })
}
}
_getFollowerDataDone.value = Event("followerDone")
}
}
override fun onFailure(call: Call<List<ResponseGitHubFollowerData>>, t: Throwable) {
}
})
}
fun getGitHubUserData() {
for (i in _followerList) {
val call: Call<ResponseGithubUserData>? = i.value?.let {
ServiceCreator.gitHubService.getGitHubUsers(
it
)
}
if (call != null) {
call.enqueue(object : Callback<ResponseGithubUserData> {
override fun onResponse(
call: Call<ResponseGithubUserData>,
response: Response<ResponseGithubUserData>
) {
if (response.isSuccessful) {
val data = response.body()?.bio
_bio.add(MutableLiveData<String>().apply { value = data })
if(i == _followerList.last()){
_getUserDataDone.value = Event("UserDone")
}
} else{
}
}
override fun onFailure(call: Call<ResponseGithubUserData>, t: Throwable) {
}
})
}
}
}
fun getConclusionData() {
_conclusionData.clear()
for (i in _followerList.indices) {
_conclusionData.add(
FollowerData(
followerName = _followerList[i],
followerImg = _followerAvatarUrl[i],
followerIntro = _bio[i]
)
)
}
_getConclusionDataDone.value = Event("ConclusionDone")
}
}
FollowerFragmnet.kt
package changhwan.experiment.sopthomework
import android.graphics.Color
import android.os.Bundle
import androidx.fragment.app.Fragment
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.fragment.app.activityViewModels
import androidx.fragment.app.viewModels
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.Observer
import androidx.recyclerview.widget.ItemTouchHelper
import androidx.recyclerview.widget.RecyclerView
import changhwan.experiment.sopthomework.databinding.FragmentFollowerBinding
class FollowerFragment : Fragment(), ItemDragListener {
private var _binding: FragmentFollowerBinding? = null
private val binding get() = _binding!!
private lateinit var followerAdapter: FollowerAdapter
private lateinit var itemTouchHelper : ItemTouchHelper
private val gitHubViewModel by activityViewModels<GitHubViewModel>()
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
gitHubViewModel.getGitHubFollowerData()
gitHubViewModel.getFollowerDataDone.observe(viewLifecycleOwner, EventObserver{
gitHubViewModel.getGitHubUserData()
})
gitHubViewModel.getUserDataDone.observe(viewLifecycleOwner, EventObserver{
gitHubViewModel.getConclusionData()
})
_binding = FragmentFollowerBinding.inflate(layoutInflater, container, false)
return binding.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
gitHubViewModel.getConclusionDataDone.observe(viewLifecycleOwner,EventObserver{
siteFollowerRecycler()
binding.followerRecycle.addItemDecoration(CustomMarginDecoration(24))
binding.followerRecycle.addItemDecoration(CustomDividerDecoration(1f,10f, resources.getColor(R.color.divider),40))
itemTouchHelper = ItemTouchHelper(ItemTouchHelperCallback(followerAdapter))
itemTouchHelper.attachToRecyclerView(binding.followerRecycle)
})
}
fun siteFollowerRecycler(){
followerAdapter = FollowerAdapter(this)
binding.followerRecycle.adapter = followerAdapter
followerAdapter.followerData.clear()
followerAdapter.followerData.addAll(
gitHubViewModel.conclusionData
)
//diffUtill๋ถ๋ถ ์๋๋ followerAdapter.notifyDataSetChanged()์์
followerAdapter.setContact(followerAdapter.followerData)
//์ฌ๊ธฐ๊น์ง
}
override fun onStartDrag(viewHolder: RecyclerView.ViewHolder) {
}
override fun onDestroyView() {
super.onDestroyView()
_binding = null
}
}
2-2
ServiceCreator.kt
package changhwan.experiment.sopthomework
import okhttp3.Interceptor
import okhttp3.OkHttpClient
import retrofit2.Retrofit
import retrofit2.converter.gson.GsonConverterFactory
object ServiceCreator {
private val headerInterceptor = Interceptor{
val request = it.request()
.newBuilder()
.addHeader("Content-Type","application/json")
.build()
return@Interceptor it.proceed(request)
}
val client: OkHttpClient = OkHttpClient.Builder()
.addInterceptor(headerInterceptor)
.build()
private const val SOPT_BASE_URL= "https://asia-northeast3-we-sopt-29.cloudfunctions.net/api/"
private val SoptRetrofit :Retrofit = Retrofit.Builder()
.baseUrl(SOPT_BASE_URL)
.client(client)
.addConverterFactory(GsonConverterFactory.create())
.build()
val signUpService :SignUpService = SoptRetrofit.create(SignUpService::class.java)
val signInService :SignInService = SoptRetrofit.create(SignInService::class.java)
private const val GITHUB_BASE_URL="https://api.github.com/"
private val GitHubRetrofit : Retrofit = Retrofit.Builder()
.baseUrl(GITHUB_BASE_URL)
.addConverterFactory(GsonConverterFactory.create())
.build()
val gitHubService :GitHubService = GitHubRetrofit.create(GitHubService::class.java)
}
2-3
ResponseWrapper.kt
package changhwan.experiment.sopthomework
data class ResponseWrapper<T>(
val status: Int,
val success: Boolean,
val message: String,
val data: T?
)
ResponseSignInData.kt
package changhwan.experiment.sopthomework
import android.provider.ContactsContract
data class ResponseSignInData(
val id: Int,
val name: String,
val email: String
)
SignInService.kt
package changhwan.experiment.sopthomework
import retrofit2.Call
import retrofit2.Response
import retrofit2.http.Body
import retrofit2.http.Headers
import retrofit2.http.POST
interface SignInService {
@POST("user/login")
suspend fun postSignIn(
@Body body: RequestSignInData
):Response<ResponseWrapper<ResponseSignInData>>
}
3
SignInService.kt
package changhwan.experiment.sopthomework
import retrofit2.Call
import retrofit2.Response
import retrofit2.http.Body
import retrofit2.http.Headers
import retrofit2.http.POST
interface SignInService {
@POST("user/login")
suspend fun postSignIn(
@Body body: RequestSignInData
):Response<ResponseWrapper<ResponseSignInData>>
}
SignViewModel.kt
package changhwan.experiment.sopthomework
import android.net.Network
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import kotlinx.coroutines.*
import retrofit2.Call
import retrofit2.Callback
import retrofit2.Response
import kotlin.coroutines.CoroutineContext
class SignViewModel : ViewModel() {
private val _viewEmail = MutableLiveData<String>()
private val _viewName = MutableLiveData<String>()
private val _viewPassword = MutableLiveData<String>()
private val _conSuccess = MutableLiveData<Boolean>()
val viewEmail: LiveData<String>
get() = _viewEmail
val viewName: LiveData<String>
get() = _viewName
val viewPassword: LiveData<String>
get() = _viewPassword
val conSuccess: LiveData<Boolean>
get() = _conSuccess
fun getEmail(email: String) {
_viewEmail.value = email
}
fun getName(name: String) {
_viewName.value = name
}
fun getPassword(password: String) {
_viewPassword.value = password
}
fun startSignUp() {
val requestSignUpData = RequestSignUpData(
email = _viewEmail.value!!,
name = _viewName.value!!,
password = _viewPassword.value!!
)
val call: Call<ResponseSignUpData> =
ServiceCreator.signUpService.postSignUp(requestSignUpData)
call.enqueue(object : Callback<ResponseSignUpData> {
override fun onResponse(
call: Call<ResponseSignUpData>,
response: Response<ResponseSignUpData>
) {
if (response.isSuccessful) {
val data = response.body()?.data
if (data != null) {
_viewName.value = data.name
_viewEmail.value = data.email
}
_conSuccess.value = true
} else {
_conSuccess.value = false
}
}
override fun onFailure(call: Call<ResponseSignUpData>, t: Throwable) {
_conSuccess.value = false
}
})
}
fun startSignIn() {
val requestSignInData = RequestSignInData(
email = _viewEmail.value!!,
password = _viewPassword.value!!
)
// val call : Call<ResponseSignInData> = ServiceCreator.signInService.postSignIn(requestSignInData)
//
// call.enqueue(object : Callback<ResponseSignInData>{
// override fun onResponse(
// call: Call<ResponseSignInData>,
// response: Response<ResponseSignInData>
// ) {
// val data = response.body()?.data
//
// if(response.isSuccessful){
// if (data != null) {
// _viewName.value = data.name
// }
// _conSuccess.value = true
// } else {
// _conSuccess.value = false
// }
// }
//
// override fun onFailure(call: Call<ResponseSignInData>, t: Throwable) {
// _conSuccess.value = false
// }
//
// })
viewModelScope.launch {
val response = ServiceCreator.signInService.postSignIn(requestSignInData)
val data = response.body()?.data
if (response.isSuccessful) {
if (data != null) {
_viewName.value = data.name
}
_conSuccess.value = true
} else {
_conSuccess.value = false
}
}
}
}
๊ณผ์ 4์ฃผ์ฐจ ๋ณด๋ฉด ์๋ฏ์ด
๋ทฐ๋ชจ๋ธ์์ ๋น๋๊ธฐ์ ์ผ๋ก ์ ๋ณด ๋์ด์ค๋๊ฑฐ ์ฒ๋ฆฌํด์ ๊ฐ์ ธ์์ ์ ์ฅํ์๊ณ ๊ทธ์ ๋ฐ๋ฅธ ์ฒ๋ฆฌ
์ธํ ํธ๋ ํ ์คํธ ๋ฉ์ธ์ง ๋ฑ๋ฑ์ ์กํฐ๋นํฐ์์ ์คํํ๋ค
๊ทผ๋ฐ ์ ๋ณด๊ฐ์ ธ์ค๋ ๋ฒํผ ๋ฆฌ์ค๋ ์์ ๋ทฐ๋ชจ๋ธ์ ๋น๋๊ธฐ์ฒ๋ฆฌ๋ฅผํ๊ณ ๋์์
์ ๋ณด๊ฐ์ ธ์ค๋๊ฑฐ ๋ฟ๋ ค์ฃผ๋๊ฑธํ๋ฉด ๋น๋๊ธฐ์ฒ๋ฆฌ๊ฐ ๋๋๊ธฐ๋ ์ ์ ์คํํด์
nullpointexception์ด๋ ๋ด๊ฐ ์ํ๋๊ฑธ ์ฒ๋ฆฌํ์ง ๋ชปํ๋ ์ผ์ด ๋ฐ์ํ๋ค.
๊ทธ๋๋ ํ ์คํธ๋ฉ์ธ์ง๋ ์ธํ ํธ๊ฐ์ ์กํฐ๋นํฐ์์ ์ฒ๋ฆฌํด์ผํ ๊ฒ๋ค์ ๋ฐ๋ก์๊ธฐ์ ๋ฐฉ๋ฒ์ ์๊ฐํด์
๋น๋๊ธฐ ์๋ฃ ์ฌ๋ถ๋ฅผ ๋ํ๋ด๋ ๋ณ์๋ฅผ booleanํํ๋ก ๋ผ์ด๋ธ๋ฐ์ดํฐ๋ก ๋๊ณ ์ต์ ๋ฒ๋ฅผ ๋ฌ์์ ๋ณํ๊ฐ์์๊ฒฝ์ฐ ์ฒ๋ฆฌํ๋๋กํ๋ค ๊ทธ๋ฆฌ๊ณ ๋น๋๊ธฐ์ฒ๋ฆฌ ์ฑ๊ณต์ฌ๋ถ 200์ธ์ง 400์ธ์ง๋ ๋ณ์์ true/false์ฌ๋ถ๋ก ํ๋จํ์๋ค.
๊ทธ๋์ ํด๊ฒฐ์ ํ์ง๋ง ๋ญ๊ฐ ๋ ์ข์ ๋ฐฉ๋ฒ์ด์๋์ถ์ด ๋ฌธ๋ค๋น์๊ฒ ๋ฌผ์ด๋ดค๋๋ฐ
๋ง๋ ๋ฐฉ๋ฒ์ธ๋ฐ booleanํํ๋ก ์ค์ ์ฌ์ฉ๋๋ ๋ฐ์ดํฐ ๊ฐ์ด์๋ ์ด๋ฒคํธ์ฒ๋ฆฌ์๋ง ์ฌ์ฉ๋๋ ๋ณ์๋ Event wapper๊ฐ๋ ์
์ฌ์ฉํ๋๊ฒ์ด ๋ง๋ค๊ณ ํ๋ค
-> ์ถํ์ ๊ด๋ จ ์ ๋ฆฌ๊ธ์ ์จ์ผ๊ฒ ๋ค.
๊ด๋ จ ๋ธ๋ก๊ทธ๊ธ์ด๋ค ๋ํ ๋์ฑ ๋ฐ์ ํ ๋ด์ฉ๋ค๋์์ผ๋ ์ฐธ๊ณ ํ์
๋ํ ๋ก๊ทธ์ธํ ๋ edittext์ ๋ด๊ธด text๊ฐ์๊ฒ๋ค์ sharedPreference์ ์ ์ฅํด์ ๊ฐ์ง๊ณ ์ค๋๊ฒ์ด ์ข๋ค๊ณ ํ๋ค.
์ด๋ถ๋ถ๋ ์ฐธ๊ณ ํด์ ์์ ํด๋ณด์
๋ํ ์ด๋ฐ๋ถ๋ถ์์ ๋ทฐ๋ชจ๋ธ์์ ๋น๋๊ธฐ์ฒ๋ฆฌ๊ฐ ๋๋ํ ์กํฐ๋นํฐ์์ ์ฝ๋๋ฅผ ์ฒ๋ฆฌํ๋๊ฒ์ ๋ค๋ฅธ ์ข์ ๋ฐฉ๋ฒ์ด ์๋ ๋ฌผ์ด๋ณธ๊ฒฐ๊ณผ
StateFlow๋ผ๋ ์ฝ๋ฃจํด ๊ฐ๋ ์ค์ ํ๋๊ฐ ์๋๋ฐ ๋๋ฌด์ด๋ ค์ธ๊ฒ์ด๋ ๋์ค์ ์ฌ์ฉํด๋ณด๋ผ๋ ์กฐ์ธ์ด์์๋ค.
์ํ ์ฐฉ์ค๊ฐ ์ ๋ง ๋ง์๋ค ์ฌ๊ธฐ์ ๊ฐ์ ธ๊ฐ์ผํ ๊ฒ๋ค์ ์ดํด๋ณด์
1.get์ฌ์ฉ๋ฐฉ๋ฒ
package changhwan.experiment.sopthomework
import retrofit2.Call
import retrofit2.http.GET
import retrofit2.http.Headers
import retrofit2.http.Path
interface GitHubService {
@GET("users/{userId}/followers")
fun getGitHubFollowers(
@Path("userId") userId:String
): Call<List<ResponseGitHubFollowerData>>
@GET("users/{userId}")
fun getGitHubUsers(
@Path("userId") userId:String
): Call<ResponseGithubUserData>
}
์ด๋ฐ์์ผ๋ก ์ธํฐํ์ด์ค ํ๋์ ์ฌ๋ฌ๊ฐ์ http๋ฉ์๋ ๋ฃ์์์๊ณ ์ค๊ฐ์ path ๋ถ๋ถ์ ๋ผ์๋ฃ์์์๋ค
"users/{userId}/followers" ์์์ฒ๋ผ
2.์ฌ๋ฌ๊ฐ์ api์ฌ์ฉํด์ baseurl์ฌ๋ฌ๊ฐ์ธ๊ฒฝ์ฐ
package changhwan.experiment.sopthomework
import retrofit2.Retrofit
import retrofit2.converter.gson.GsonConverterFactory
object ServiceCreator {
private const val SOPT_BASE_URL= "https://asia-northeast3-we-sopt-29.cloudfunctions.net/api/"
private val SoptRetrofit :Retrofit = Retrofit.Builder()
.baseUrl(SOPT_BASE_URL)
.addConverterFactory(GsonConverterFactory.create())
.build()
val signUpService :SignUpService = SoptRetrofit.create(SignUpService::class.java)
val signInService :SignInService = SoptRetrofit.create(SignInService::class.java)
private const val GITHUB_BASE_URL="https://api.github.com/"
private val GitHubRetrofit : Retrofit = Retrofit.Builder()
.baseUrl(GITHUB_BASE_URL)
.addConverterFactory(GsonConverterFactory.create())
.build()
val gitHubService :GitHubService = GitHubRetrofit.create(GitHubService::class.java)
}
retrofit ๊ฐ์ฒด ๋๊ฐ๋ง๋ค์ด์ ๊ทธ๋ฅ ์ฐ๋ฉด๋๋ค.
3.์๋ฒํต์ ํ ๋๋ ์์ ๋ฐ์ดํฐ ๋ถ๋ฌ์ค๋ ์์ ์ด ๋๋๊ณ ๋ค์๊ฒ์ ๋ถ๋ฌ์ค๋๋ก(์๋ฐ์ดํฐ๊ฐ ๋ค์ชฝ์ ๋ถ๋ฌ์ค๋๊ฒ์ ๊ด์ฌํ ๋) ์ ๋ก์ง์ ์ง์ผํ๋ค ๋จธ๋ฆฌ๋ฅผ ๊ตด๋ฆฌ์
-> ๋ํ ์ด๋ด๋ ์์๋๋ฉด
Log.d๋ฅผ ์ด์ฉํด์ ์ฐ์ด๋ณด์
์ด๋ ๊ฒ ๊ฒ์ํด์ ๋ด๋ผ
๊ทธ๋ฆฌ๊ณ ์ค๋ฅ๋๋ฉด
logcat์์
error๋ก ์ ํํ๊ณ exception๋์ผ๋ฉด ์ค๋ฅ ๋ญ์ง ๋์จ๋ค ๊ทธ๊ฑฐ๋ณด๊ณ ํด๊ฒฐํด๋ณด์
์ด๋ฒ์ ์๋ฒํต์ ํ๋ฉฐ ๋ฐ์ดํฐ ๊ฐ์ ธ์ค๋ฉด์ ๊ณจ์น ์ํ ๋๊ฑฐ
fun getGitHubUserData() {
for (i in _followerList) {
val call: Call<ResponseGithubUserData>? = i.value?.let {
ServiceCreator.gitHubService.getGitHubUsers(
it
)
}
if (call != null) {
call.enqueue(object : Callback<ResponseGithubUserData> {
override fun onResponse(
call: Call<ResponseGithubUserData>,
response: Response<ResponseGithubUserData>
) {
if (response.isSuccessful) {
val data = response.body()?.bio
_bio.add(MutableLiveData<String>().apply { value = data })
if(i == _followerList.last()){
_getUserDataDone.value = Event("UserDone")
}
} else{
}
}
override fun onFailure(call: Call<ResponseGithubUserData>, t: Throwable) {
}
})
}
}
}
getUserDataDone ์ ๋ฐ์ดํธ ์๊ธฐ ์ฒซ๋ฒ์งธ ๋น๋๊ธฐ์ฒ๋ฆฌ๋ ์๋๋ฉด for๋ฌธ ๋์ ๋ถ์ฌ๋์ผ๋ฉด ๋ฌธ์ ์์์ ๋น๋๊ธฐ๋ผ ๋จผ์ ์ฒ๋ฆฌ๋์
๋ค์ชฝ ์ฝ๋์์ ์ค๋ฅ๋ฌ์ ๊ทธ๋์ ๋ง์ง๋ง ๋น๋๊ธฐ ์ฒ๋ฆฌ์์ ๋ฐ๊ฟ์์๋๋ก
if(i == _followerList.last()){
_getUserDataDone.value = Event("UserDone")
}
์ด๋ถ๋ถ์ ์ถ๊ฐํด์ ๋ง์ง๋ง ์์์ผ๋ Event์ ๋ณํ๊ฐ ์ค๋๋กํ๋ค.
๊ทธ๋ฆฌ๊ณ Event๋ฅผ ์ด๋ค๊ณ ๋ผ์ด๋ธ๋ฐ์ดํฐ๊ฐ ๋ณํ๋ฅผ ์ํ๋๊ฒ ์๋๋ค null๊ฐ์ผ๋ก ๊ณ์ ์ ๋ฐ์ดํธ๋๊ณ
๊ทธ์๋ฐ๋ผ ์ด๋ฒคํธ ๊ณ์ ๋ฐ์ํ๋ค.
-> ๋ฆฌ์คํธ๊ฐ ๊ณ์ ์ถ๊ฐ๋๋ ๋ฌธ์ ๊ฐ ์๊ฒผ์๋ค.
ํด๊ฒฐ๋ฐฉ๋ฒ
๋งค๋ฒ list์ ๋ฐ์ดํธ ํ๊ธฐ์ clearํ๋๋ก ์ฃ๋ค clear๋ถ์ฌ์คฌ๋ค. ๊ทธ๋ผ ์ด๊ธฐํํ ๋ค์ด๊ฐ๋ฏ๋ก ๊ด์ฐฎ์์ก๋ค.
okhttp์ interceptor๋ก ์ค๊ฐ์์ ์ฒ๋ฆฌ๋ฅผํด์ ์๋ฒ๋ก ๋ณด๋ผ์์๋ค.
์ฐ์ gradle์ ์ถ๊ฐํด์ฃผ๊ณ
//okhttp3
implementation 'com.squareup.okhttp3:okhttp:3.14.9'
implementation 'com.squareup.okhttp3:logging-interceptor:3.11.0'
๋ค์์ ์ค์ ๊ฐ์ฒด ๊ตฌํํด์ฃผ๋ ๋ถ๋ถ์์
okhttp ๊ฐ์ฒด๋ ๊ตฌํํด์ฃผ๊ณ
interceptor๋ ๋ง๋ค์ด์
retrofit ๊ฐ์ฒด๋ฅผ ๋ง๋ค๋ client์๋ค๊ฐ ์ถ๊ฐํด์ฃผ๋ฉด๋๋ค.
header์ถ๊ฐ์ธ์๋ ๋ ๋ง์ ๊ธฐ๋ฅ์ด์๋ค ์ถํ์ ์ฐพ์๋ด์ผ๊ฒ ๋ค.
object ServiceCreator {
private val headerInterceptor = Interceptor{
val request = it.request()
.newBuilder()
.addHeader("Content-Type","application/json")
.build()
return@Interceptor it.proceed(request)
}
val client: OkHttpClient = OkHttpClient.Builder()
.addInterceptor(headerInterceptor)
.build()
private const val SOPT_BASE_URL= "https://asia-northeast3-we-sopt-29.cloudfunctions.net/api/"
private val SoptRetrofit :Retrofit = Retrofit.Builder()
.baseUrl(SOPT_BASE_URL)
.client(client)
.addConverterFactory(GsonConverterFactory.create())
.build()
val signUpService :SignUpService = SoptRetrofit.create(SignUpService::class.java)
val signInService :SignInService = SoptRetrofit.create(SignInService::class.java)
interceptor์ addHeader๋ฅผ ํตํด ํค๋๋ฅผ ๋ค๋๋ถ๋ถ์ ๋ง๋ค์ด์ฃผ๊ณ ๋น๋ํํ
OkHttpClient๋ ์๊น ๋ง๋ interceptor๋ฅผ addInterceptor๋ฅผ ํตํด ์ถ๊ฐํด์ buildํด์คํ
retrofit ๊ฐ์ฒด๋ง๋ค๋ client์ ์ถ๊ฐํด์ค๋ค.
์ฐธ๊ณ ๋ธ๋ก๊ทธ
https://hwanine.github.io/android/Retrofit-OkHttp/
์์ชฝ์ ๊ณ์ ์ค๋ณต๋๋ ๋ถ๋ถ๋ค ์ด๋ป๊ฒ ์ฒ๋ฆฌํ ์์๋๊ฐ?
์ด์งํผ ์์ ๋ ค๋ ๋ถ๋ถ์ ๊ณ์ํด์ ๊ฐ์๊ฒ ๋์ฌ๊ฒ์ด๋ค ๊ทธ๊ฑฐ ๋ฏธ๋ฆฌ wrapper class ๋ฅผ ํตํด์ ์์ฑํด๋๊ณ
๋๋จธ์ง ๋ค๋ฅธ๋ถ๋ถ๋ง ์๋ก ์์ฑํด์ ๋ฃ์ด์ฃผ๋ ๋ฐฉ์์ผ๋ก ์ฒ๋ฆฌํ๋ค.
๊ณผ์ ์์๋ signin ๋ถ๋ถ๋ง ์ ์ฉํ๋ค.
์ด๊ฑธ ๊ณ ์น๋๊ฒ์ด๋ค.
ResponseWrapper.kt
package changhwan.experiment.sopthomework
data class ResponseWrapper<T>(
val status: Int,
val success: Boolean,
val message: String,
val data: T?
)
์ด๋ ๊ฒ ๋ฏธ๋ฆฌ wrapper class๋ก ์ค๋ณต๋๋ถ๋ถ์ ์์ฑํด๋๋๋ค.
ResponseSignInData.kt
package changhwan.experiment.sopthomework
import android.provider.ContactsContract
data class ResponseSignInData(
val id: Int,
val name: String,
val email: String
)
๋ค์ ์ด๋ ๊ฒ ์ค๋ ์ ๋ณด๋ค ์์๋ค ๋ฃ์ด์ค๊บผ ์์ฑํด์
์ฌ์ฉํ ๋๋ ํ๋๋ก ํฉ์ณ์ ๋ฃ์ด์ค๋ค.
SignInService.kt
package changhwan.experiment.sopthomework
import retrofit2.Call
import retrofit2.Response
import retrofit2.http.Body
import retrofit2.http.Headers
import retrofit2.http.POST
interface SignInService {
@POST("user/login")
suspend fun postSignIn(
@Body body: RequestSignInData
):Response<ResponseWrapper<ResponseSignInData>>
}
<ResponseWrapper<ResponseSignInData>>
์๋ฐ์์ผ๋ก ์ฌ์ฉํ๋๊ฒ์ด๋ค.
์ง์ง์ง์ง ๋ง๋ฐฐ๊ธฐ๋ก๋งํด์ ๋ญ๊ฐ ๋ง๋์ง๋ ์๋ชจ๋ฅธ๋ค. ๋์ค์ ์ถํ ๊ณต๋ถ๋ฅผ ๋ํ ๊ฑฐ์ง๋ง
์คํ๋ ์๋๊ธดํ๊ณ ์ฌ๊ธฐ์ ๊ธฐ ๋ฌผ์ด๋ณธ๊ฒฐ๊ณผ ๋ง๋๋ฐฉ์์ธ๊ฑฐ๊ฐ๋ค.
์ฌ์ฉ๋ฐฉ๋ฒ์ ์์ธ๋ก ๊ฐ๋จํ๋ค view model์ ์จ์ viewmodelscope๋ฅผ ์ฌ์ฉํด์ ์ก๋คํ๊ฑฐ ์์์ ๋์ ์ฌ์ ๋๊ฒ๋ ์๋๊ฑฐ๊ฐ๋ค.
\1. gradle ์ถ๊ฐ
// ViewModel coroutine ์ค์ฝํ๋ฅผ ์ํ๊ฑฐ
implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:2.1.0"
//coroutine
implementation ("org.jetbrains.kotlinx:kotlinx-coroutines-android:1.5.0")
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.3.3'
2.suspendํค์๋ ์ฌ์ฉํ ํจ์์ ๋ฌ์์ฃผ๊ธฐ + Call๊ฐ์ฒด๊ฐ์๋ Response๊ฐ์ฒด ๊ฐ์ ธ์ค๋๊ฑธ๋ก ๋ฐ๊ฟ์ฃผ๊ธฐ
SignInService.kt
package changhwan.experiment.sopthomework
import retrofit2.Call
import retrofit2.Response
import retrofit2.http.Body
import retrofit2.http.Headers
import retrofit2.http.POST
interface SignInService {
@POST("user/login")
suspend fun postSignIn(
@Body body: RequestSignInData
):Response<ResponseWrapper<ResponseSignInData>>
}
postSignIn ํจ์์ suspend ํค์๋๋ฅผ ๋ฌ์์คฌ๋ค.
๊ทธ๋ฆฌ๊ณ Call๊ฐ์ฒด๋ฅผ ๋ฐํํ๋๊ฒ์ด์๋ Response๊ฐ์ฒด๋ฅผ ๋ฐํํ๋๊ฑธ๋ก ๋ฐ๊ฟ์คฌ๋ค.
3.์ด์ callback์์ ์ฒ๋ฆฌํ๋๊ฑฐ ๋์ฒดํ๊ธฐ
์๋ callback ์ผ๋ก ์ฐจ๋ฆฌํ๋ฉด ์ฝ๋๊ฐ ์ด๋ฌ๋ค.
fun startSignIn() {
val requestSignInData = RequestSignInData(
email = _viewEmail.value!!,
password = _viewPassword.value!!
)
val call : Call<ResponseSignInData> = ServiceCreator.signInService.postSignIn(requestSignInData)
call.enqueue(object : Callback<ResponseSignInData>{
override fun onResponse(
call: Call<ResponseSignInData>,
response: Response<ResponseSignInData>
) {
val data = response.body()?.data
if(response.isSuccessful){
if (data != null) {
_viewName.value = data.name
}
_conSuccess.value = true
} else {
_conSuccess.value = false
}
}
override fun onFailure(call: Call<ResponseSignInData>, t: Throwable) {
_conSuccess.value = false
}
})
}
๊ทผ๋ฐ ์ด๊ฑฐ ์กํฐ๋นํฐ๋ฉด ๊ธฐํ๋ฑ๋ฑ ์ฌ์ ์์ ๋ค์ ํ๊ณ ์ฌ์ฉํด์ผํ๋๋ฐ
๊ทธ๋ฅ viewmodel๋ด๋ผ์ viewModelScope๋ฅผ ์ฌ์ฉํด์
๊ทธ๋ฅ viewModelScope.launch{}๋ด๋ถ์ ๋ถ๋ฌ์ค๋๊ฑฐ๋ ๊ทธ๋ค์์ ๋ฐ์ดํฐ ๋ถ๋ฌ์จ๊ฑฐ ์ฒ๋ฆฌํ๋ ๋ก์ง ๋ฃ์ด์ฃผ๋ฉด ๋น๋๊ธฐ๋ก ์์์ ์ฒ๋ฆฌํ๋ค.
viewModelScope.launch {
val response = ServiceCreator.signInService.postSignIn(requestSignInData)
val data = response.body()?.data
if (response.isSuccessful) {
if (data != null) {
_viewName.value = data.name
}
_conSuccess.value = true
} else {
_conSuccess.value = false
}
}
์ด๋ ๊ฒ ์ฒ๋ฆฌํ๋ฉด๋๋ค ์ฝ๋ ๊ฐ๊ฒฐํด์ง๋ค ์ง์ง.
๊ทธ๋ฆฌ๊ณ response๋ ์๊น ์ธํฐํ์ด์ค์์ ๋ฐํํ๋๊ฑฐ Response๋ก ๋ฐ๊ฟ๋จ์ผ๋ ์ด์ ์ ๋ ๊ฒ ๋ฐ์์ค๋ฉด Response๋ฅผ ๋ฐ์์์ง๋ค
๊ทธ๋์ ๊ทธ์ค์ํ๋๊ฑฐ body๊ฐ ์ data ๋นผ์์ ์ฒ๋ฆฌํด์ฃผ๋ฉด๋๋ค.
์ฐธ๊ณ ๋ธ๋ก๊ทธ
https://enant.tistory.com/23 <-๊ทผ๋ฐ ์ด๋ถ๋ถ์ ์ธ๋ชจ๊ฐ์์๋ค ์ฌ์ค ๊ทธ๋ฅ ์ด๋ฐ๋๋๋ง ๊ฐ์ง๋์ฉ๋๋ก ๋ณด๋ฉด๋๋ค.
7์ฃผ์ฐจ
# ์คํํ๋ฉดbandicam.2021-12-17.19-20-51-312.mp4
level1,2 ํ๊ณ 3์ ํ๋ค๋ชปํจ
level1-1
OnboardingActivity.kt
package changhwan.experiment.sopthomework.ui.view.onboarding
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import androidx.appcompat.widget.Toolbar
import androidx.navigation.Navigation.findNavController
import androidx.navigation.findNavController
import androidx.navigation.fragment.NavHostFragment
import androidx.navigation.ui.AppBarConfiguration
import androidx.navigation.ui.setupWithNavController
import changhwan.experiment.sopthomework.R
import changhwan.experiment.sopthomework.databinding.ActivityOnBoardingBinding
import org.koin.android.ext.android.bind
class OnBoardingActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val binding = ActivityOnBoardingBinding.inflate(layoutInflater)
setContentView(binding.root)
val navHostFragment =
supportFragmentManager.findFragmentById(R.id.container_on_boarding) as NavHostFragment
val navController = navHostFragment.navController
val appBarConfiguration = AppBarConfiguration(
topLevelDestinationIds = setOf(),
fallbackOnNavigateUpListener = ::onSupportNavigateUp
)
findViewById<Toolbar>(R.id.tb_on_boarding)
.setupWithNavController(navController, appBarConfiguration)
}
}
OnboardingFirstFragment.kt
package changhwan.experiment.sopthomework.ui.view.onboarding.fragment
import android.os.Bundle
import androidx.fragment.app.Fragment
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.databinding.DataBindingUtil
import androidx.navigation.fragment.findNavController
import changhwan.experiment.sopthomework.R
import changhwan.experiment.sopthomework.databinding.FragmentOnBoardingFirstBinding
import org.koin.android.ext.android.bind
class OnBoardingFirstFragment : Fragment() {
private var _binding: FragmentOnBoardingFirstBinding? = null
private val binding get() = _binding!!
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
_binding = FragmentOnBoardingFirstBinding.inflate(inflater, container, false)
binding.btnOnBoardFirst.setOnClickListener{
findNavController().navigate(R.id.action_onBoardingFirstFragment_to_onBoardingSecondFragment)
}
return binding.root
}
override fun onDestroyView() {
super.onDestroyView()
_binding = null
}
}
ํ๋๊ทธ๋จผํธ๋ ์์๋ก ํ๋๋ง ์ฌ๋ฆฌ๊ฒ ์ต๋๋ค.
<?xml version="1.0" encoding="utf-8"?>
<navigation xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/nav_on_boarding"
app:startDestination="@id/onBoardingFirstFragment">
<fragment
android:id="@+id/onBoardingFirstFragment"
android:name="changhwan.experiment.sopthomework.ui.view.onboarding.fragment.OnBoardingFirstFragment"
android:label="์ฒซ๋ฒ์จฐ"
tools:layout="@layout/fragment_on_boarding_first" >
<action
android:id="@+id/action_onBoardingFirstFragment_to_onBoardingSecondFragment"
app:destination="@id/onBoardingSecondFragment" />
</fragment>
<fragment
android:id="@+id/onBoardingSecondFragment"
android:name="changhwan.experiment.sopthomework.ui.view.onboarding.fragment.OnBoardingSecondFragment"
android:label="๋๋ฒ์จฐ"
tools:layout="@layout/fragment_on_boarding_second" >
<action
android:id="@+id/action_onBoardingSecondFragment_to_onBoardingThirdFragment"
app:destination="@id/onBoardingThirdFragment" />
</fragment>
<fragment
android:id="@+id/onBoardingThirdFragment"
android:name="changhwan.experiment.sopthomework.ui.view.onboarding.fragment.OnBoardingThirdFragment"
android:label="์ธ๋ฒ์งธ"
tools:layout="@layout/fragment_on_boarding_third" >
<action
android:id="@+id/action_pop_onBoardingThirdFragment_to_onBoardingFirstFragment"
app:destination="@id/onBoardingFirstFragment"
app:popUpTo="@id/onBoardingFirstFragment"
app:popUpToInclusive="true"/>
</fragment>
</navigation>
๋ค๋น๊ฒ์ด์ ์ ๋ณด๋ฉด level 2์ ๋ฐฑ์คํ ๊ฐ๋๊ฒ๊น์ง ๋ฃ์ด๋จ์ต๋๋ค.
level1-2
MainActivity.kt
package changhwan.experiment.sopthomework
import android.app.Application
import changhwan.experiment.sopthomework.data.remote.api.SignInService
import changhwan.experiment.sopthomework.data.remote.api.SignUpService
import changhwan.experiment.sopthomework.di.HeaderInterceptor
import changhwan.experiment.sopthomework.ui.viewmodel.SignViewModel
import changhwan.experiment.sopthomework.util.PreferenceUtil
import okhttp3.OkHttpClient
import org.koin.android.ext.koin.androidContext
import org.koin.androidx.viewmodel.dsl.viewModel
import org.koin.core.context.startKoin
import org.koin.dsl.module
import retrofit2.Converter
import retrofit2.Retrofit
import retrofit2.converter.gson.GsonConverterFactory
class MainApplication : Application() {
companion object{
lateinit var prefs: PreferenceUtil
}
override fun onCreate() {
super.onCreate()
//shared preferences
prefs = PreferenceUtil(applicationContext)
startKoin {
androidContext(this@MainApplication)
modules(soptNetworkModule,viewModelModule)
}
}
}
val soptNetworkModule = module {
single {
OkHttpClient.Builder()
.addInterceptor(HeaderInterceptor())
.build()
}
single {
GsonConverterFactory.create() as Converter.Factory
}
single<Retrofit> {
Retrofit.Builder()
.client(get())
.addConverterFactory(get())
.baseUrl("https://asia-northeast3-we-sopt-29.cloudfunctions.net/api/")
.build()
}
single<SignUpService> {
get<Retrofit>().create(SignUpService::class.java)
}
single<SignInService> {
get<Retrofit>().create(SignInService::class.java)
}
}
val viewModelModule = module {
viewModel {
SignViewModel(get(),get())
}
}
์ฌ๊ธฐ์ shared preference ์ ๋ฆฌํ์ต๋๋ค.
PreferenceUtill.kt
package changhwan.experiment.sopthomework.util
import android.content.Context
import android.content.SharedPreferences
class PreferenceUtil(context: Context) {
private val prefs: SharedPreferences =
context.getSharedPreferences("main_prefs",Context.MODE_PRIVATE)
fun getBoolean (key:String,defvalue:Boolean): Boolean{
return prefs.getBoolean(key,defvalue)
}
fun setBoolean (key:String, value:Boolean){
return prefs.edit().putBoolean(key,value).apply()
}
}
์ฌ์ฉํ์์
SigninActivity์์ sharedpreference๋ฅผ ํตํด ์๋๋ก๊ทธ์ธ ์ฌ๋ถ ์ ์ฅ
private fun startLogin() {
binding.loginButton.setOnClickListener {
signInViewModel.getEmail("")
signInViewModel.getPassword("")
signInEmail.value?.let { signInViewModel.getEmail(it) }
signInPassword.value?.let { signInViewModel.getPassword(it) }
signInViewModel.startSignIn()
if(binding.cbAutoLogin.isChecked){
MainApplication.prefs.setBoolean("auto_login",true)
// val db = SoptDatabase.getInstance(applicationContext)
// CoroutineScope(Dispatchers.IO).launch {
// db!!.soptDao().insert(SoptEntity(autoLogin = true))
// }
}
}
}
SettingFragment.kt๋ฅผ ๋ง๋ค์ด ํ๊ฒฝ์ค์ ์ฐฝ์ ๋ง๋ค์์ต๋๋ค.
package changhwan.experiment.sopthomework.ui.view.profile.autologin
import android.os.Bundle
import androidx.fragment.app.Fragment
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import changhwan.experiment.sopthomework.MainApplication
import changhwan.experiment.sopthomework.R
import changhwan.experiment.sopthomework.databinding.FragmentSettingBinding
import changhwan.experiment.sopthomework.ui.view.profile.follower.FollowerFragment
class SettingFragment : Fragment() {
private var _binding: FragmentSettingBinding? = null
private val binding get() = _binding!!
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
_binding = FragmentSettingBinding.inflate(layoutInflater,container,false)
updataAutoLoginState()
backSetting()
actionAutoLoginStateChange()
return binding.root
}
private fun updataAutoLoginState(){
binding.cbAutoLoginState.isChecked = MainApplication.prefs.getBoolean("auto_login",false)
}
private fun actionAutoLoginStateChange(){
binding.cbAutoLoginState.setOnCheckedChangeListener { buttonView, isChecked ->
if(isChecked){
MainApplication.prefs.setBoolean("auto_login",true)
}else{
MainApplication.prefs.setBoolean("auto_login",false)
}
}
}
private fun backSetting(){
binding.btnBack.setOnClickListener{
binding.btnBack.setOnClickListener {
val followerFragment = FollowerFragment()
requireParentFragment().childFragmentManager.beginTransaction()
.replace(R.id.fragmentFrame,followerFragment ).commit()
}
}
}
override fun onDestroyView() {
super.onDestroyView()
_binding = null
}
}
๊ณผ์ 1-3
Mascota ์ฐธ๊ณ
1-3 ์ ๋ต ์ถ๊ฐ
์ฐ์ ๊ฐ์ฅ ์์์ data,di,ui,utill๋ก ๋๋๊ณ
data์๋
local๊ณผ remote๋ก ๋๋ ์
local์ ๊ทธ์ผ๋ง๋ก ๋ก์ปฌ room๊ฐ์๊ฑฐ
remote์๋ retrofit๊ฐ์๊ฑฐ ๋ชฐ์๋ฃ์๋ค
di์๋ ์์กด์ฑ ์ฃผ์ ์ ์ฌ์ฉ๋๋
์ธํฐ์ ํฐ๋ผ๋ ์ง ๋ชจ๋๋ ๋ถ๋ฆฌํด์ ๋ฃ์ด์ผํ๋๋ฐ ๋ชจ๋์ ๋ณต์กํด์ ๋ถ๋ฆฌ๋ฅผ ๋ชปํ๋ค ใ
ui
uiํ์์๋ view,viewmodel์ด์๋๋ฐ ๋ค์ด๋ฐ๊ณผ๊ฐ์ด ์ ์ ํ๊ฒ ๋ฃ์ด์ฃผ๊ณ
view์๋
๊ธฐ๋ฅ ๋ณ๋ก ๋๋ด์ผ๋ฉฐ ์ด๋ํฐ ๊ฐ์๊ฒ๋ ๋ค์ด๊ฐ๋ค
๋ง์ง๋ง์ผ๋กutill์๋
diffutill๊ฐ์ ์ ์ญ์์ ์ฐ์ด๋utill๋ค์ ๋ชฐ์๋จ๋ค
2-1
์์ชฝ์์ ๋ค๋น๊ฒ์ด์ ์ ์ ํ์๊ณ
OnBoardingThiedFragmnet.kt์์
private fun setBackButton(){
requireActivity().onBackPressedDispatcher.addCallback(this){
findNavController().navigate(R.id.action_pop_onBoardingThirdFragment_to_onBoardingFirstFragment)
}
}
back๋ฒํผ ๋๋ ธ์๋ ์คํ๋๋๋ถ๋ถ ๋ง๋ค์ด๋์๊ฒ
2-2
OnBoardingActivity.kt
package changhwan.experiment.sopthomework.ui.view.onboarding
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import androidx.appcompat.widget.Toolbar
import androidx.navigation.Navigation.findNavController
import androidx.navigation.findNavController
import androidx.navigation.fragment.NavHostFragment
import androidx.navigation.ui.AppBarConfiguration
import androidx.navigation.ui.setupWithNavController
import changhwan.experiment.sopthomework.R
import changhwan.experiment.sopthomework.databinding.ActivityOnBoardingBinding
import org.koin.android.ext.android.bind
class OnBoardingActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val binding = ActivityOnBoardingBinding.inflate(layoutInflater)
setContentView(binding.root)
val navHostFragment =
supportFragmentManager.findFragmentById(R.id.container_on_boarding) as NavHostFragment
val navController = navHostFragment.navController
val appBarConfiguration = AppBarConfiguration(
topLevelDestinationIds = setOf(),
fallbackOnNavigateUpListener = ::onSupportNavigateUp
)
findViewById<Toolbar>(R.id.tb_on_boarding)
.setupWithNavController(navController, appBarConfiguration)
}
}
์ฌ๊ธฐ์ toolbar์ ์ฐ๊ฒฐํ๋ ์ฝ๋ ์์ฑ๋์ด์์ต๋๋ค






















