3rd Development Log (Code Review)

26782 단어 SSACandroidSSAC

💡 이번 주차 과제에서 주로 사용한 개념 정리 및 코드 리뷰

  • BaseActivity
    액티비티와 프래그먼트가 많아질수록 ViewBinding 을 세팅하기에 번거로워진다. 이 때, BaseActivity 를 사용하면 보다 효율적으로 구현할 수 있다.
    • 모든 액티비티에서 공통적으로 사용하는 변수들이 있는 경우, 이 변수들을 파일마다 만드는 번거로운 과정 생략
    • Base 패키지에 필수적인 내용이 들어있는 파일을 만들어놓고 Activity 등에서 해당 파일을 상속하여 사용
    • BaseActivity 는 추상 클래스로 정의
  // 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
        	}
    	}
    • 사용
      모듈에 ViewBinding 을 사용하도록 설정하면 모듈에 포함된 각 XML 레이아웃 파일의 결합 클래스가 생성된다. 각 결합 클래스에는 루트 뷰 및 ID가 있는 모든 뷰의 참조가 포함된다.
      • 활동에 사용할 바인딩 클래스 인스턴스를 설정하려면 활동의 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() 와의 차이점
      • Null 안전
        ViewBinding 은 뷰의 직접 참조를 생성하므로 유효하지 않은 뷰 ID로 인해 null 포인터 예외가 발생할 위험이 없다. 또한 레이아웃의 일부 구성에만 뷰가 있는 경우 결합 클래스에서 참조를 포함하는 필드가 @Nullable로 표시됩니다.
      • 유형 안전
        각 바인딩 클래스에 있는 필드의 유형이 XML 파일에서 참조하는 뷰와 일치하므로 클래스 변환 예외가 발생할 위험이 없습니다.

        이러한 차이점은 레이아웃과 코드 사이의 비호환성으로 인해 런타임이 아닌 컴파일 시간에 빌드가 실패하게 된다는 것을 의미함


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
       }
 }

클론 코딩 앱 캡처 사진



좋은 웹페이지 즐겨찾기