[연재] 코틀린 프로젝트 - 02 - 2단계: Fragment의 구성

 2단계: Fragment의 구성

이제 액티비티를 통해서 프레그먼트를 나타낼 수 있게 구성합니다. 먼저 기존에 사용되지 않는 자동 생성된 파일을 삭제합시다. java/ 하위에 MainActivityFragment.kt 파일과 res/layout/의 fragment_main.xml을 삭제합니다. 

기본 메인 Activity에서 불러들이는 frag_recycler.xml은 Fragment를 만들고 RecyclerView를 배치해 영화 목록을 보여줄 것입니다. 먼저 MovieFragment를 java/하위에 기본 패키지명에 ui를 생성하고 MovieFragment를 추가합시다. 만들기 위해 필요한 것은 앞서 만든 frag_recycler.xml 레이아웃을 연결할 것입니다. 

1. 패키지명 com.acaroom.themovieapp에서 Alt +Insert 를 누르고 [Package]를 선택한 후 'ui'를 입력해 새로운 패키지를 만듭니다. 이 ui 패키지명에서 다시 Alt +Insert 를 누르고 [Kotlin File/Class]를 선택한 후 'MovieFragment'라고 입력하고 [OK]를 누릅니다.


새로운 ui 패키지와 MovieFragment의 생성

 
2. 여기서 우리가 사용할 RecyclerView에 id인 rv_movie_list를 부여해 향후 코드에서 사용할 수 있도록 합니다. 보통 다음과 같은 순서로 MovieFragment 클래스의 onCreateView()에서 정의합니다.

MovieFragment의 정의 ui/MovieFragment.kt
...
class MovieFragment : Fragment() {

    override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {

        return inflater.inflate(R.layout.frag_recycler, container, false) // 레이아웃 전개
    }
}...

Fragment의 생명주기 콜백 메서드 중 onCreateView()는 Fragment를 생성 시키기 위해 View를 레이아웃 frag_recycler로부터 띄우고 반환 합니다. inflate()의 세번째 인자는 루트로 사용할지를 결정하는데 XML에 루트를 사용하므로 보통 false입니다. 

3. 이것을 좀 더 단순화 하기 위해 코틀린의 확장 함수 개념을 사용해 봅시다. 같은 방법으로 이번에는 utils 패키지를 새로 만들고 그안에 Extensions.kt 파일을 생성합니다. 

확장함수 만들기 utils/Extensions.kt
/**
 * ViewGroup에 확장함수 inflate를 정의 
 * attachToRoot는 기본값 false 지정해 생략 가능
 */
fun ViewGroup.inflate(layoutId: Int, attachToRoot: Boolean = false): View {
    return LayoutInflater.from(context).inflate(layoutId, this, attachToRoot)
}

4. 이제 ViewGroup는 inflate()라는 확장 함수를 가지게 되었으므로 다음과 같이 축소됩니다.

MovieFragment의 변경 ui/MovieFragment.kt
...
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
    return container?.inflate(R.layout.frag_recycler)
}

ViewGroup의 객체 변수인 container를 이용해 추가된 확장 함수 inflate()를 호출하고 인자로 레이아웃 ID만 넘겨주면 되는 것입니다. 

Lazy를 사용한 위임자 구성

이제 RecyclerView의 레이아웃 속성을 지정하기 위해서 다음과 같이 선언해 사용할 수 있습니다. 

private var movieList: RecyclerView? = null

이것을 UI에서는 생명주기에 따라 RecylerView가 생성 시기가 다르므로 lazy를 사용해 프로퍼티 초기화 시점을 초기 접근 시점으로 늦춰 볼 수 있습니다. 

private val movieList: RecyclerView by lazy {
    view?.findViewById(R.id.rv_movie_list) as RecyclerView
}

이것을 안드로이드 코틀린 확장의 합성 프로퍼티 기법을 사용해 fnidViewById를 생략하고 다음과 같이 간략화 할 수 있습니다. 

private val movieList by lazy { rv_movie_list }

4. 이것을 다시 MovieFragment의 적용하면 다음과 같이 (1)번과 (2)번을 추가 작성합니다. 

MovieFragment의 변경 ui/MovieFragment.kt
...
class MovieFragment : Fragment() {

    private val movieList by lazy { rv_movie_list } // (1)

    override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
        return container?.inflate(R.layout.frag_recycler)
    }

    override fun onActivityCreated(savedInstanceState: Bundle?) { // (2)
        super.onActivityCreated(savedInstanceState)
        movieList.setHasFixedSize(true)  // lazy 접근 실행
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
            movieList.layoutManager = LinearLayoutManager(context)
        }
    }
}

RecyclerView의 메서드인 setHasFixedSize()는 고정된 크기를 가질 때 최적화 할 수 있습니다. 이 메서드가 호출되는 순간 lazy에 의해 movieList 가 초기화 됩니다. 이후에는 초기화된 변수가 그대로 사용되기 때문에 레이아웃 지정을 위해 layoutManager 프로퍼티를 사용할 수 있습니다. LinearLayoutManager()에 의해 기본값 vertical을 사용하는 순차 레이아웃이 설정 됩니다. 이 때 안드로이드의 최소 API레벨이 21 이하인 경우에는 사용할 수 없으므로 if문을 통해 M버전 이상인 경우에만 적용되도록 합니다.

5. lazy {...} 블록은 다음과 같이 onActivityCreated() 콜백 함수 안에 넣을 수도 있습니다. 

MovieFragment의 변경 ui/MovieFragment
override fun onActivityCreated(savedInstanceState: Bundle?) {
    super.onActivityCreated(savedInstanceState)
    val movieList by lazy {
        movie_list.setHasFixedSize(true)
        movie_list.layoutManager = LinearLay outManager(context)
        movie_list // 리턴 자료형으로서 사용된다.
    }
}

 

apply() 의 사용

6. lazy를 사용하지 않고 리소스id를 바로 사용해 다음과 같이 apply 블록을 지정해서 변경할 수 있습니다. 

MovieFragment의 변경 ui/MovieFragment
override fun onActivityCreated(savedInstanceState: Bundle?) {
    super.onActivityCreated(savedInstanceState)
    // RecyclerView의 리소스 id
    rv_movie_list.apply {
        setHasFixedSize(true) // this.. 즉 rv_movie_list.setHasFixedSize()와 같다
        val linearLayout = LinearLayoutManager(context)
        layoutManager = linearLayout // this.layoutManager
    }
}

RecyclerView의 리소스 id인 rv_movie_list는 안드로이드 코틀린 확장에 의해 findViewById() 없이 리소스 id를 사용했고 여기에 apply {...} 블록을 통해 rv_movie_list를 전달 받아 this를 사용해 관련 메서드를 호출할 수 있습니다. this.setHasFixedSize() 가 사용되는데 this는 생략 가능합니다. 또한 null 검사를 하기 때문에 문장이 간단해 집니다. apply 문은 끝날 때 this 즉, rv_movie_list를 반환 하게 됩니다.

7. 이제 MainActivity에 MovieFragment를 불러들일 수 있도록 다음 코드를 작성합니다. 

프레그먼트의 호출 부분 추가 MainActivity.kt
import android.support.v4.app.Fragment
...
class MainActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        setSupportActionBar(toolbar)
...
        if (savedInstanceState == null) {
            changeFragment(MovieFragment()) // (1)
        }
    }

    fun changeFragment(f: Fragment, cleanStack: Boolean = false) {
        val ft = supportFragmentManager.beginTransaction() // (2) 프레그먼트 관리자를 통한 제어
        ft.replace(R.id.base_content, f) // (3) 프레그먼트의 변경
        ft.addToBackStack(null) // (4) 백스택에 넣기
        ft.commit() // (5) 최종 프레그먼트의 적용
    }
...

먼저 (1)에서 사용자 함수를 통해 MovieFragment()객체를 전달합니다. 만일 (1)번에서 캐스팅 오류가 난다면 import 구문이 MovieFragment에서 지정된 것과 같은 것이 임포트 되었는지 확인합니다. 여기서는 다음과 같이 v4의 Fragment가 임포트됩니다.

import android.support.v4.app.Fragment

(2)번에서 FragmentManager의 beginTransaction()에 의해 제어 가능한 객체 ft를 생성합니다. 자바에서는 다음과 같이 생성합니다. 

FragmentManager fragmentManager = getFragmentManager();
FragmentTransaction ft = fragmentManager.beginTransaction();

하지만 코틀에서 다음과 같이 한 줄로 축약해서 생성 했습니다. 

val ft = supportFragmentManager.beginTransaction()

이 ft 객체로부터 프레그먼트를 생성하거나 교체, 삭제 등을 할 수 있는데 (3)번에서는 replace()를 사용해 교체하는 것을 호출했습니다. 이제 이것을 백스택에 넣고 최종 프레그먼트에 적용하기 위해 commit()을 호출합니다. (3)번처럼 addToBackStack()을 사용해 백스택에 넣는 것은 이전 상태를 백스택에 보존함으로서 사용자가 Back 버튼을 눌렀을 때 이전 프레그먼트를 다시 가져올 수 있습니다. 

이번 글은 Fragment 구성에 대한 내용만 간단히 살펴 봤는데요. 실행하면 아직까지 화면에 나오는 정보가 없습니다. 이제 정보를 RecyclerView에 나타내기 위한 어댑터를 구성해야 합니다. 소스는 최종 단계에서 공개합니다.  최대한 한 단계씩 따라해 보시고 최종 단계에서 이상 없이 잘 구동된다면 뿌듯할 것입니다!  

다음 글에서 계속해서 알아보겠습니다!

 

youngdeok의 이미지

Language

Get in touch with us

"어떤 것을 완전히 알려거든 그것을 다른 이에게 가르쳐라."
- Tryon Edwards -