반응형

Fragment

설명

  • Activity의 화면을 여러 영역으로 나누어 관리하고자 하는 목적으로 사용

코드

res/layout/fragment_first.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">

    <EditText
        android:id="@+id/editText"
        android:layout_width="match_parent"
        android:layout_height="wrap_content" />

    <Button
        android:id="@+id/submitButton"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="submit" />
</LinearLayout>
FirstFragment
class FirstFragment : Fragment() {
    override fun onCreateView(
        inflater: LayoutInflater,
        container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {
        val mainActivity = activity as MainActivity
        val view = inflater.inflate(R.layout.fragment_first, container, false)

        val editText = view.findViewById<EditText>(R.id.editText)
        val submitButton = view.findViewById<Button>(R.id.submitButton)

        submitButton.setOnClickListener {
            mainActivity.text = editText.text.toString()
            mainActivity.setFragment("second")
        }

        return view
    }
}
res/layout/fragment_second.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">

    <TextView
        android:id="@+id/displayTextView"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="" />

    <Button
        android:id="@+id/backButton"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="back" />
</LinearLayout>
SecondFragment
class SecondFragment : Fragment() {
    override fun onCreateView(
        inflater: LayoutInflater,
        container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {
        val mainActivity = activity as MainActivity
        val view = inflater.inflate(R.layout.fragment_second, container, false)

        val displayTextView = view.findViewById<TextView>(R.id.displayTextView)
        val backButton = view.findViewById<Button>(R.id.backButton)

        displayTextView.text = mainActivity.text

        backButton.setOnClickListener {
            mainActivity.supportFragmentManager.popBackStack()
        }

        return view
    }
}
res/layout/activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">

    <FrameLayout
        android:id="@+id/frameLayout"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />

</LinearLayout>
MainActivity
class MainActivity : AppCompatActivity() {
    private val first = FirstFragment()
    private val second = SecondFragment()

    var text: String? = null

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        setFragment("first")
    }

    fun setFragment(name: String) {
        val transaction = supportFragmentManager.beginTransaction()
        // transaction.add(R.id.frameLayout, first) // 여러 fragment가 겹쳐 보이도록 설정
        when (name) {
            "first" -> {
                transaction.replace(R.id.frameLayout, first) // 한 fragment만 보이도록 설정
            }
            "second" -> {
                transaction.replace(R.id.frameLayout, second)
                transaction.addToBackStack(null) // back button 터치시 이전 화면으로 이동
            }
        }
        transaction.commit()

        // transaction 반영은 비동기적으로 처리되기 때문에
        // 처리 완료 대기를 위해서는 executePendingTransactions() 메소드를 호출해야 한다.
        // 아래는 transaction 반영 완료 이후의 fragment를 출력하는 코드
        if (supportFragmentManager.executePendingTransactions()) {
            val fragment = supportFragmentManager.findFragmentById(R.id.frameLayout)
            Log.d("TEST", fragment.toString())
        }
    }
}

참고

Fragment Animation

설명

  • Fragment 변경시 Animation 효과 적용이 가능하다.

예제

res/animator/slide_up.xml
<?xml version="1.0" encoding="utf-8"?>
<objectAnimator xmlns:android="http://schemas.android.com/apk/res/android"
    android:duration="@android:integer/config_mediumAnimTime"
    android:interpolator="@android:anim/decelerate_interpolator"
    android:propertyName="translationY"
    android:valueFrom="1280"
    android:valueTo="0"
    android:valueType="floatType" />
res/animator/slid_down.xml
<?xml version="1.0" encoding="utf-8"?>
<objectAnimator xmlns:android="http://schemas.android.com/apk/res/android"
    android:duration="@android:integer/config_mediumAnimTime"
    android:interpolator="@android:anim/decelerate_interpolator"
    android:propertyName="translationY"
    android:valueFrom="0"
    android:valueTo="1280"
    android:valueType="floatType" />
DefaultFragment
class DefaultFragment : Fragment() {
    override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
        return TextView(activity).apply { text = "default" }
    }
}
res/layout/activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">

    <Button
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:onClick="onClick"
        android:text="click" />

    <FrameLayout
        android:id="@+id/frameLayout"
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:layout_weight="1" />

</LinearLayout>
MainActivity
class MainActivity : AppCompatActivity() {
    private val defaultFragment = DefaultFragment()

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
    }

    fun onClick(view: View) {
        val transaction = supportFragmentManager.beginTransaction()
        transaction.setCustomAnimations(R.animator.slide_up, R.animator.slide_down) // enter일 경우에만 animation 적용
        // transaction.setCustomAnimations(R.animator.slide_up, R.animator.slide_down, R.animator.slide_up, R.animator.slide_down) // enter, pop일 경우 모두 animation 적용
        transaction.replace(R.id.frameLayout, defaultFragment)
        transaction.addToBackStack(null)
        transaction.commitAllowingStateLoss()
    }
}

참고

[이슈] Can not perform this action after onSaveInstanceState

설명

  • Activity 화면 회전과 같은 이벤트 발생시 Activity의 상태는 초기화 되고 레이아웃도 다시 그리게 된다.
  • 이 때 상태를 저장하고 처리할 수 있도록 onSaveInstanceState() 메소드를 제공하고 있다.
  • 어떤 이벤트가 발생했을 때 비동기적으로 transaction.commit()을 하게될 경우 commit() 전에 onSaveInstanceState()가 먼저 호출되었으면 해당 오류가 발생한다.

이슈 해결

  • transaction.commit()을 비동기적으로 하지 않도록 개발한다.
  • transaction.commit() 대신에 transaction.commitAllowingStateLoss() 메소드를 사용한다.
    • state 변경이 일어나지 않은 경우 commit()을 수행하고, 변경이 일어난 경우에는 commit()을 수행하지 않는 방식

예시 코드

FirstFragment
class FirstFragment : Fragment() {
    override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
        return TextView(activity).apply { text = "first" }
    }
}
SecondFragment
class SecondFragment : Fragment() {
    override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
        return TextView(activity).apply { text = "second" }
    }
}
res/layout/activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">

    <Button
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:onClick="onClick"
        android:text="click" />

    <FrameLayout
        android:id="@+id/frameLayout"
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:layout_weight="1" />

</LinearLayout>
MainActivity
class MainActivity : AppCompatActivity() {
    private val handler = Handler()
    private val first = FirstFragment()
    private val second = SecondFragment()

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        setFragment(first)
    }

    fun onClick(view: View) {
        // 1. onClick() 호출
        // 2. onSaveInstanceState() 호출 (ex. Activity 회전 이벤트 발생)
        // 3. transaction.commit() 호출시 오류 발생 (java.lang.IllegalStateException: Can not perform this action after onSaveInstanceState)
        handler.postDelayed({
            setFragment(second)
        }, 3000)
    }

    private fun setFragment(fragment: Fragment) {
        val transaction = supportFragmentManager.beginTransaction()
        transaction.replace(R.id.frameLayout, fragment)
        transaction.addToBackStack(null)
        transaction.commit()
        // transaction.commitAllowingStateLoss()
    }
}

참고

반응형

'Development > Android' 카테고리의 다른 글

[Android] DialogFragment  (0) 2021.02.09
[Android] ListFragment  (0) 2021.02.09
[Android] DatePicker  (0) 2021.02.09
[Android] ListDialog  (0) 2021.02.09
[Android] ProgressDialog  (0) 2021.02.09

+ Recent posts