위와 같이 컴포넌트의 레이아웃이 똑같은 화면들을 구현해야할 때, 사용하면 유용한 ViewPager를 적용하는 방법에 대해 리뷰 해보려한다.
1. xml에서 ViewPager2를 원하는 영역만큼 정의한다.
현재 전체 레이아웃을 사용하므로 가로, 세로 모두 match_parent로 지정
<androidx.viewpager2.widget.ViewPager2
android:id="@+id/viewPager"
android:layout_width="match_parent"
android:layout_height="match_parent" />
이때, viewPager와 함께 TabLayout 등을 같이 정의해주면 페이지 간의 탐색과 진행도를 알 수 있는 UI도 제공할 수 있다.
탭에 텍스트를 빼서 진행도나 애니메이션으로 써도 괜찮을 듯
상단에 Tab을 둘것인지 아래에 둘것인지에 따라 순서를 조정하면 될 것 같다.
2. 기존 방식대로 findViewById로 컴포넌트를 변수에 할당한 뒤, viewPager에 adapter를 지정해준다.
import androidx.viewpager2.widget.ViewPager2
class TestActivity : AppCompatActivity() {
private lateinit var viewPager: ViewPager2
val questionnaireResults = QuestionnaireResults()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_test)
viewPager = findViewById(R.id.viewPager)
viewPager.adapter = ViewPagerAdapter(this) // 어댑터에 액티비티 연결
viewPager.isUserInputEnabled = false // 좌우 스크롤을 통해 다른 페이지로 이동하는 기능
}
*adapter는 데이터를 ViewPager와 연결하고, 각 페이지를 표시하는 데 필요한 뷰를 생성 및 관리하는 역할이라고 한다.
3. ViewPager를 관리하기 위한 adapter class 생성
class ViewPagerAdapter(fragmentActivity: FragmentActivity) : FragmentStateAdapter(fragmentActivity) {
override fun getItemCount() = 4 // 전체 페이지수 지정
override fun createFragment(position: Int): Fragment {
// 전달 받은 position값으로 n번째 Fragment를 생성하는 newInstance()를 호출 한 뒤, 생성된 Fragment 반환
return QuestionFragment.newInstance(position)
}
}
adapter에는 여러 종류가 있는데 이번 실습때 사용한 것은 FragmentStateAdapter이다.
이는 각 페이지를 Fragment로 표현하고, 사용자가 페이지를 넘길 때, Fragment의 상태를 저장하고 복원하는 기능을 제공한다. 이 adapter는 여러 Fragment가 메모리 상에 유지될 때 메모리 관리를 효율적으로 하기 위해 사용된다.
[GPT - 이외의 adapter 설명]
- PagerAdapter: 가장 기본적인 어댑터로, 뷰를 직접 관리합니다. 데이터가 정적일 때 적합합니다.
- FragmentPagerAdapter/FragmentStatePagerAdapter: 각 페이지를 Fragment로 관리합니다. 이는 페이지 간에 복잡한 상호 작용이나 동적인 데이터가 있는 경우 유용합니다. FragmentStatePagerAdapter는 페이지를 넘길 때 상태를 저장하고 복원하는 추가 기능을 제공하여 메모리 사용을 더 효율적으로 관리합니다.
*Fragment : Android App의 UI 일부를 나타내는 컴포넌트 -> 커스텀 컴포넌트 같은? 개념인 것 같다.
Fragment의 특징으로는 재사용성, 모듈성, 독립적인 라이프사이클, 유연한 트랜잭션 등이 있었다.
// Fragment를 상속받는 QuestionFragment 클래스의 일부 코드
companion object {
private const val ARG_QUESTION_TYPE = "questionType"
fun newInstance(questionType: Int): QuestionFragment {
val fragment = QuestionFragment() // QuestionFragment 클래스 객체 생성 = Fragment 생성
// 전달 받은 int값으로 argument를 지정한뒤, 객체 반환
val args = Bundle().apply {
putInt(ARG_QUESTION_TYPE, questionType)
}
fragment.arguments = args
return fragment
}
}
QuestionFragment()로 Fragment를 생성하면 바로 onCreate가 호출될 줄 알았는데 그렇지는 않았다.
-> 생성된 후 FragmentManager에 의해 생명 주기대로 호출됨
4. Fragment 생성된 후 init, setup 코드 구현
순서 : Fragment 생성 -> onCreate() -> onCreateView() -> onViewCreated()
onCreateView()와 onViewCreated()는 awake와 start랑 비슷한 듯
// onCreate() : 프래그먼트가 생성될 때 호출되는 메소드
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
arguments?.let {
questionType = it.getInt(ARG_QUESTION_TYPE)
}
}
전달받은 arguments 유무를 체크하고, 있다면 클래스의 멤버 변수인 questionType에 type을 백업 총 4개의 Fragment에 ID를 할당한 셈이다.
// onCreateView() : 프래그먼트가 사용자 인터페이스를 만들 때 호출되는 메소드
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
// inflate : 레이아웃으로 만든 xml을 코드로 가져온다는 뜻
val view = inflater.inflate(R.layout.fragment_question, container, false)
// ... view에서 사용하는 컴포넌트 속성(텍스트, 이미지 등)들을 셋업하는 코드 ...
return view
}
fragment_question.xml을 view라는 변수에 할당한 셈이다.
// onViewCreated() : onCreateView() 메소드가 수행된 뒤, 바로 호출되는 메소드
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
val answerRadioGroup = listOf<RadioGroup>(
view.findViewById(R.id.rg_answer_1),
view.findViewById(R.id.rg_answer_2),
view.findViewById(R.id.rg_answer_3),
)
val nextButton = view.findViewById<TextView>(R.id.btn_next)
nextButton.setOnClickListener {
val isAllAnswered = answerRadioGroup.all { it.checkedRadioButtonId != -1 }
require(isAllAnswered) {
Toast.makeText(context, "모든 문항에 답변을 해주세요", Toast.LENGTH_SHORT).show()
return@setOnClickListener
}
val response = answerRadioGroup.map { it.checkedRadioButtonId }
// 다음 화면으로 넘어가기 전에 현재 Fragment에서 응답한 결과를 백업
(activity as TestActivity)?.questionnaireResults?.addResponses(response)
(activity as TestActivity)?.moveToNextQuestion() // 화면 전환
}
}
5. Fragment 간의 전환(이동)
기존 설정에서 스와이프를 통한 전환은 막았기 때문에, UI의 버튼을 이용해서 다른 Fragment로 이동함
fun moveToNextQuestion() {
if (viewPager.currentItem == 3) {
val intent = Intent(this, ResultActivity::class.java)
intent.putIntegerArrayListExtra("results", ArrayList(questionnaireResults.results))
startActivity(intent)
}
val nextItem = viewPager.currentItem + 1
if (nextItem < viewPager.adapter?.itemCount ?: 0) {
// 애니메이션 효과를 포함하거나 현재 아이템이 설정될 때 추가 작업 처리를 할 수 있음(로깅, 검증 등)
viewPager.setCurrentItem(nextItem, true)
// 위와 동일한 코드?
//.viewPager.currentItem = nextItem
}
}
03/24 복습함!
'Android' 카테고리의 다른 글
[학습 1장] findViewById / Kotlin Extensions / ViewBinding / DataBinding 정리 (0) | 2024.03.28 |
---|---|
[docs] 앱 아키텍처 가이드 읽어보기 (1) | 2024.03.22 |
호출한 액티비티에서 결과 가져오기, registerForActivityResult (0) | 2024.03.20 |
안드로이드 앱개발 입문 1~4주차 학습 (0) | 2024.03.18 |
많이 사용했지만, 까먹는 단축키 (1) | 2024.02.16 |