💡 이번 주차 과제에서 주로 사용한 개념 정리 및 코드 리뷰
// BaseActivity.kt
abstract class BaseActivity<T : ViewDataBinding>(@LayoutRes private val layoutResId: Int) :
AppCompatActivity() {
lateinit var binding: T
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = DataBindingUtil.setContentView(this, layoutResId)
}
}
// BaseFragment.kt
abstract class BaseFragment<T : ViewDataBinding>(@LayoutRes private val layoutResId: Int) :
Fragment() {
private var _binding: T? = null
val binding get() = _binding ?: error("View를 참조하기 위해 binding이 초기화되지 않았습니다.")
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View {
_binding = DataBindingUtil.inflate(inflater, layoutResId, container, false)
return binding.root
}
override fun onDestroyView() {
super.onDestroyView()
_binding = null
}
}
// MainActivity.kt
class MainActivity : BaseActivity<ActivityMainBinding>(R.layout.activity_main) {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
...
}
}
ViewBinding
ViewBinding 을 사용하면 뷰와 상호작용하는 코드를 쉽게 작성할 수 있다. 모듈에서 사용 설정된 ViewBinding 은 모듈에 있는 각 XML 레이아웃 파일의 바인딩 클래스를 생성한다. 바인딩 클래스의 인스턴스에는 상응하는 레이아웃에 ID가 있는 모든 뷰의 직접 참조가 포함된다. 대부분의 경우 ViewBinding 이 findViewById()
를 대체한다.
android {
...
viewBinding {
enabled = true
}
}
onCreate()
메서드에서 inflate()
를 호출 - 이 결과로 바인딩 클래스 인스턴스가 생성getRoot()
를 호출하거나 Kotlin 속성 구문을 사용하여 루트 뷰 참조를 가져오고, 루트 뷰를 setContentView()
에 전달하여 화면 상의 활성 뷰로 만듦 private lateinit var binding: ResultProfileBinding
override fun onCreate(savedInstanceState: Bundle) {
super.onCreate(savedInstanceState)
binding = ResultProfileBinding.inflate(layoutInflater)
val view = binding.root
setContentView(view)
}
binding.name.text = viewModel.name
binding.button.setOnClickListener { viewModel.userClicked() }
findViewById()
와의 차이점이러한 차이점은 레이아웃과 코드 사이의 비호환성으로 인해 런타임이 아닌 컴파일 시간에 빌드가 실패하게 된다는 것을 의미함
ViewHolder
RecyclerView Adapter 는 개별 데이터에 대응하는 ViewHolder 클래스를 사용한다. 이 때 ViewHolder 는 ViewHolder 클래스를 상속받아서 만든다.
ViewHolder 는 현재 화면에 보이는 아이템 레이아웃 개수만큼 생성되고 스크롤 동작으로 인해 새롭게 그려져야 할 아이템 레이아웃이 있다면 가장 위의 ViewHolder 를 재사용해서 데이터만 바꾼다. 데이터 개수만큼의 아이템 레이아웃을 생성하면 자원 낭비가 발생하므로 ViewHolder 의 재사용성을 통해 이를 방지하여 앱의 효율을 향상시킨다.
// HomeInfoAdapter.kt
override fun onCreateViewHolder(
parent: ViewGroup,
viewType: Int
): HomeMenuListViewHolder {
val binding =
ItemHomeMenulistBinding.inflate(LayoutInflater.from(parent.context), parent, false)
return HomeMenuListViewHolder(binding)
}
inner class HomeMenuListViewHolder(
private val binding: ItemHomeMenulistBinding
) : RecyclerView.ViewHolder(binding.root) {
fun onBind(menuList: MenuListInfo, position: Int) {
binding.apply {
homeMenulistName.text = menuList.menuName
homeMenulistPrice.text = menuList.menuPrice
itemView.setOnClickListener {
itemClickListener.onClick(menuListInfo[position])
}
}
}
}
ViewPager
스와이프 형식으로 뷰 또는 프래그먼트를 표시하며 RecyclerView 를 기반으로 사용한다.
ViewPager2 에서 업데이트 된 부분
notifyDataSetChanged()
기능// HomePagerAdapter.kt
override fun createFragment(position: Int): Fragment {
return when (position) {
0 -> HomeMenuFragment()
1 -> HomeInfoFragment()
2 -> HomeReviewFragment()
else -> throw IllegalStateException("Unexpected position: $position")
}
}
TabLayoutMediator
TabLayout 을 ViewPager2 와 연결하는 역할로, 탭이 선택될 때 ViewPager2 의 위치를 선택된 탭과 동기화하고 사용자가 ViewPager2 를 끌 때 TabLayout의 스크롤 위치를 동기화한다.
이 클래스의 인스턴스를 만들어 링크를 설정하고 ViewPager2 에 어댑터가 있는지 확인한 다음 이를 호출 attach()
한다. TabLayoutMediator 를 인스턴스화하면 중재자 개체만 생성 attach()
되고 TabLayout 과 ViewPager2 가 함께 연결된다. ViewPager2 의 어댑터를 변경하려면 detach()
다음에 attach()
를 호출해야 하고, ViewPager2 또는 TabLayout 을 변경하려면 TabLayoutMediator 의 새로운 인스턴스화가 필요하다.
// HomeFragment.kt
private fun setHomeViewPager() {
binding.vp.apply {
adapter = HomePagerAdapter(this@HomeFragment)
}
TabLayoutMediator(binding.tabLayout, binding.vp) { tab, position ->
when (position) {
0 -> {
tab.text = "메뉴"
}
1 -> {
tab.text = "정보"
}
2 -> {
tab.text = "리뷰"
}
}
}.attach()
}
FadeInScrolling
// HomeFragment.kt
private fun fadeInAtScrolling() {
binding.menuConstraintTop.alpha = 0f
binding.menuScrollview.setOnScrollChangeListener { v, scrollX, scrollY, oldScrollX, oldScrollY ->
if (scrollY in 700..900 && binding.menuConstraintTop.alpha < 1f) {
binding.menuConstraintTop.alpha += 0.04f
} else if (scrollY <= 700 && binding.menuConstraintTop.alpha > 0f) {
binding.menuConstraintTop.alpha -= 0.04f
}
if (scrollY < 200) {
binding.menuConstraintTop.alpha = 0f
} else if (scrollY > 900) {
binding.menuConstraintTop.alpha = 1f
}
}
}
BottomNavigation
// MainActivity.kt
private fun setOnBottomNavigationClick() {
binding.bottomNaviMain.setOnNavigationItemSelectedListener {
binding.vpMain.currentItem = when (it.itemId) {
R.id.menu_bottom_navi_home -> 0
R.id.menu_bottom_navi_search -> 1
R.id.menu_bottom_navi_wish -> 2
R.id.menu_bottom_navi_order -> 3
R.id.menu_bottom_navi_mypage -> 4
else -> throw IndexOutOfBoundsException()
}
true
}
}
클론 코딩 앱 캡처 사진