안드로이드 : diary – Fragment Navigation (1)

주요 내용

  • 프래그먼트 전환
  • 프래그먼트 뷰
  • 프래그먼트에서 MainActivity 함수 호출

diary 웹 프로그램은 총 3개의 화면으로 구성되어 있어.

로그인, 일기 목록, 일기 내용 작성(수정)

안드로이드 앱도 웹 프로그램과 마찬가지로 3개 화면으로 구성해볼거야.

안드로이드 앱에서 화면간의 전환을 네비게이션이라고 불러. 3개의 화면을 액티비티(Activity)라는 구성요소로 만들 수도 있지만, 이 포스트에서는 프래그먼트(Fragment)라는 구성요소를 이용해서 구성해볼거야.

프래그먼트는 하나의 액티비티 안에서 처리될 수 있는 화면 단위야. 액티비티 3개를 이용해서 화면을 구성하는 것보다 프래그먼트 3개를 이용해서 화면을 구성하는 편이 장점이 더 많아. 이 부분에 대해서는 나중에 추가로 자세하게 알아볼께.

안드로이드 : diary – 프로젝트 생성 포스트에서 언급했던 MainActivity 와 연결된 activity_main.xml 의 관계를 기억할거야. 마찬가지로 프래그먼트를 이용해서 화면을 구성하는 경우에도 각 프래그먼트는 연결된 화면 레이아웃을 필요로 해.

프래그먼트를 생성하면서 그 연결관계를 알아볼께.

일단 프래그먼트를 관리하기 위해서 fragments 라는 이름의 패키지를 만들어보자.




그림에서 보는 것처럼 fragments 라는 이름의 패키지가 생성되었어. 이 패키지 안에 로그인, 일기 목록, 일기 내용 작성(수정) 등 3개 화면에 대한 프래그먼트 클래스를 생성해볼께. 각 프래그먼트 클래스 이름은 아래와 같이 정해봤어.

  • 로그인 : LoginFragment
  • 일기 목록 : ListFragment
  • 일기 내용 작성(수정) : EditFragment


프래그먼트를 생성할 때는 위 그림에서처럼 Fragment 메뉴를 사용하면 돼. 여러가지 종류의 프래그먼트가 있는데, Fragment (Blank) 항목을 선택해서 아무것도 없는 빈 프래그먼트를 생성해볼께.


Fragment Name 으로 BlankFragment 라고 입력되어 있고, 이 프래그먼트에 대한 레이아웃 이름은 fragment_blank 로 입력되어 있어. Blank 라는 프래그먼트 이름의 관계를 눈여겨보면 돼.

기본 이름인 Blank 를 Login, List, Edit 로 각각 변경해서 총 3개의 프래그먼트를 만든 결과는 아래와 같아.


이렇게 만든 3개의 프래그먼트를 MainActivity 에서 보여주도록 할건데, 로그인 작업이 필요할 때는 LoginFragment 를, 일기 목록이 필요할 때는 ListFragment 를 일기내용을 작성하거나 수정할 때는 EditFragment 를 보여줄거야.

필요한 순간에 필요한 각 프래그먼트를 보여주는 작업(프래그먼트 네비게이션)을 하기 위해서 메인 액티비티에 FragmentContainerView 를 배치하고 이 FragmentContainerView 에 필요한 프래그먼트를 보여주는 작업을 코딩할거야.

activity_main.xml 을 열고 가운데에 배치되어 있는 TextView 를 삭제한 다음에 Palette 에서 Common 하위의 FragmentContainerView 를 배치해볼께.


FragmentContainerView 항목을 뷰 레이아웃으로 드래그앤드롭하자마자 앞에서 생성했던 3개의 프래그먼트가 표시되는 화면이 나타나지.


이 3개의 프래그먼트 클래스 중에서 첫 화면이 표시할 프래그먼트를 선택해 주면 돼.

activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout 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/main"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">


    <androidx.fragment.app.FragmentContainerView
        android:id="@+id/fragmentContainerView"
        android:name="com.woohahaapps.androiddiary.fragments.ListFragment"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        tools:layout_editor_absoluteX="134dp"
        tools:layout_editor_absoluteY="200dp" />
    
</androidx.constraintlayout.widget.ConstraintLayout>

나는 ListFragment 를 선택했어. 로그인이 맨 첫 화면이 되어야 하는게 아니냐고 생각할 수도 있겠지만, ListFragment 를 보여주는 과정에서 로그인이 되어 있지 않으면 로그인 프래그먼트로 이동했다가 다시 돌아오도록 로직을 구현할 생각이기 때문이야.


배치된 FragmentContainerView 의 레이아웃을 activity_main 레이아웃에 맞춰주는 작업을 해 주어야겠지.


FragmentContainerView 의 id 가 fragmentContainerView 로 지정되어 있는 것을 확인해줘.

여기까지 작업한 상태에서 프로그램을 실행시켜보면 프로그램이 실행된 화면에 Hello blank fragment 라는 텍스트가 표시될거야.


그런데 3개의 프래그먼트는 모두 동일한 텍스트를 보여주도록 설정되어 있는 상태라서 ListFragment 가 보여지는 것이라고 구분할 수는 없어. 그래서 fragment_list.xml 을 열어서 TextView 의 text 속성값을 Hello List Fragment 라고 변경한 뒤에 실행해보면 정확하게 구분할 수 있게 되지.

그러면 이제 MainActivity 에 프래그먼트간의 이동을 처리하는 코드를 작성해볼께. 대략 아래와 같은 흐름으로 프래그먼트간 전환이 되도록 할거야.


프로그램이 시작되자마자 ListFragment 가 보여질텐데, 그 전에 로그인 상태를 확인해서 로그인되어있지 않으면 LoginFragment 로 전환해서 로그인 처리를 할 수 있도록 해야 해.

fragment_login.xml 의 TextView 의 text 속성값을 Hello Login Fragment 로 변경해서 LoginFragment 로 전환이 되었을 때 정확히 확인할 수 있도록 해주자.

MainActivity 클래스에 아래 코드를 추가해볼께.

MainActivity.kt
class MainActivity : AppCompatActivity() {

    private var loginState : Boolean = false

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        enableEdgeToEdge()
        setContentView(R.layout.activity_main)
        ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.main)) { v, insets ->
            val systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars())
            v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom)
            insets
        }

        if (!loginState) {
            val fragmentTransaction = supportFragmentManager.beginTransaction()
            fragmentTransaction.replace(R.id.fragmentContainerView, LoginFragment())
            fragmentTransaction.commit()
        }
    }
}

우선 loginState 라는 변수를 추가해서 로그인 여부를 관리할거야. 초기값은 false 로 로그인하지 않은 상태로 설정해둘거야.

로그인되지 않은 상태라면 LoginFragment 로 전환을 해야 하지. 프래그먼트의 전환시에는 supportFragmentManager 로 프래그먼트 매니저(FragmentManager)를 구해서 사용해야 해.


supportFragmentManager 는 FragmentActivity 클래스의 멤버함수인데 MainActivity 가 FragmentActivity 로부터 상속되고 있는 AppCompatActivity 클래스형이기 때문에 사용이 가능하지.

AppCompatActivity.java
public class AppCompatActivity extends FragmentActivity implements AppCompatCallback,
        TaskStackBuilder.SupportParentable, ActionBarDrawerToggle.DelegateProvider {

    private static final String DELEGATE_TAG = "androidx:appcompat";

...

beginTransaction() 함수는 FragmentTransaction 을 리턴하는데, 프래그먼트 전환 관련 코드를 작성하고 반드시 commit() 을 호출해서 마무리해야 해.

위 코드에서는 replace 함수를 사용해서 프래그먼트 관리뷰 id(R.id.fragmentContainerView) 를 첫번째 파라미터로 전달하고, 두번째 파라미터로는 전환할 대상 프래그먼트 인스턴스를 전달하고 있어.

이렇게하면 activity_main 에 배치한 FragmentContainerView 의 name 속성으로 지정된 ListFragment 가 LoginFragment 로 대체가 되지.

그래서 프로그램이 시작되면 Hello Login Fragment 텍스트가 표시가 돼.


프래그먼트 전환 방법으로 replace 말고 add 가 있는데, add 와 replace 의 차이점은 아래 포스트를 참고하면 잘 이해할 수 있어.

[안드로이드 Q&A] 프래그먼트에서 add()와 replace()의 차이점이 무엇일까?

자, 그럼 프로그램이 실행되고 로그인하지 않은 상태에서 LoginFragment 까지 이동하는것은 성공했는데, LoginFragment 에서 로그인을 진행하고, 성공하면 다시 ListFragment 로 전환되어야 하잖아?

LoginFragment 에 실제 로그인 로직을 작성하는 대신 프래그먼트 전환 테스트를 위해서 버튼 하나를 배치하고 ListFragment 로 전환하는 코드를 작성해볼께.


id 를 btn_login 으로 설정하고 LoginFragment 클래스에 이 버튼을 클릭했을 때 수행할 동작을 코딩해볼께.

LoginFragment.kt
...
class LoginFragment : Fragment() {
...
    private lateinit var mainActivity: MainActivity

    override fun onAttach(context: Context) {
        super.onAttach(context)
        mainActivity = context as MainActivity
    }

    override fun onCreateView(
        inflater: LayoutInflater, container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {
        // Inflate the layout for this fragment
        val v: View = inflater.inflate(R.layout.fragment_login, container, false)

        val btnLogin : Button? = v.findViewById(R.id.btn_login)
        btnLogin?.setOnClickListener{
            mainActivity.setLoginState(true)
            mainActivity.moveFragmentToList()
        }

        return v
    }

MainActivity 클래스의 멤버 함수에 접근할 수 있도록 하기 위해서 mainActivity 변수를 선언하고 onAttach 에서 할당해 주고 있어.

새로 추가한 btn_login 버튼에 대해서 Click 리스너를 등록해서 MainActivity 클래스의 setLoginState 함수와 moveFragmentToList 함수를 호출하고 있어.

MainActivity 에 작성한 2개의 함수를 살펴볼께.

MainActivity.kt
...
    fun setLoginState(state: Boolean) {
        loginState = state
    }

    fun moveFragmentToList() {
        val fragmentTransaction = supportFragmentManager.beginTransaction()
        fragmentTransaction.replace(R.id.fragmentContainerView, ListFragment())
        fragmentTransaction.commit()
    }
...

setLoginState 함수는 로그인 여부를 저장하고 있는 loginState 변수의 값을 설정하고, moveFragmentToList 함수는 ListFragment 프래그먼트로 전환하는 동작을 수행하고 있지.

프로그램을 실행시키면 LoginFragment 에 새로 배치한 버튼이 하나 보이게 되고, 이 버튼을 클릭하면 ListFragment 로 이동하게 돼.

지금까지 기초적인 프래그먼트 간의 전환 방법에 대해서 알아보았는데, 다음 포스트에서는 각 프래그먼트의 디자인과 더불어 실질적인 동작 코딩을 해보려고 해.

Leave a Comment