处理 activity 中多个片段的方向变化

Handle orientation change for multiple fragments in an activity

我有一个 activity,它有一个片段容器。

我在那个 activity 中有三个片段。当我旋转屏幕时,它会出现第一个片段。如果它是当前屏幕,我如何让它显示第二个片段。

我尝试添加这个方法Handle Fragment On Screen Orientation Changes?

但我有错误:

java.lang.IllegalStateException: Fragment VerifyOtp{d123b5f} (79fc7cef-725d-48cb-9591-3fa9da9f864f) is not currently in the FragmentManager

MainActivity 代码:

class MainActivity : AppCompatActivity() {

    private lateinit var binding: ActivityMainBinding


    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        var binding: ActivityMainBinding = ActivityMainBinding.inflate(layoutInflater)

        setContentView(binding.root)

        val sharedPref = getSharedPreferences("loggedUser", Context.MODE_PRIVATE)
        val userPhone = sharedPref.getString("userPhone", null)
        val location = sharedPref.getString("location", null)

        if(userPhone == null){
            val loginFragment = LoginFragment()
            supportFragmentManager.beginTransaction().replace(R.id.loginFrameLayout, loginFragment).commit()
        }
    }

    override fun onRestoreInstanceState(savedInstanceState: Bundle) {
        super.onRestoreInstanceState(savedInstanceState)
        instantiateFragment(savedInstanceState)
    }

    private fun instantiateFragment(inState: Bundle){
        val manager = supportFragmentManager
        val transaction = manager.beginTransaction()

        if(inState != null){
            manager.getFragment(inState, "VERIFY_OTP")
        }

    }
}

登录片段:

class LoginFragment : Fragment() {

    private var _binding: FragmentLoginBinding? = null
    private val binding get() = _binding!!

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

        _binding = FragmentLoginBinding.inflate(inflater, container, false)
        val view = binding.root


        ArrayAdapter.createFromResource(
            view.context,
            R.array.country_code,
            android.R.layout.simple_spinner_item
        ).also { adapter ->
            adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item)
            binding.countryCode.adapter = adapter
        }


        val fragmentVerifyOTP = VerifyOtp()


        binding.btnLogin.setOnClickListener {
            var userPhoneNumber = binding.userPhoneNumber.text.toString()

            if(userPhoneNumber.isEmpty() || userPhoneNumber.length < 10) {
                binding.userPhoneNumber.error = "Phone Number Required"
            }else{
                val arguments = Bundle()
                arguments.putString("Phone", userPhoneNumber)
                fragmentVerifyOTP.arguments = arguments

                val transaction = activity?.supportFragmentManager?.beginTransaction()
                if (transaction != null) {
                    transaction.replace(R.id.loginFrameLayout, fragmentVerifyOTP, "VERIFY_OTP")
                    transaction.addToBackStack("")
                    transaction.commit()
                }
            }
        }
        return view
    }

    override fun onDestroyView() {
        super.onDestroyView()
        _binding = null
    }
    
}

验证OTP:

class VerifyOtp : Fragment() {

    private var _binding: FragmentVerifyOtpBinding? = null
    private val binding get() = _binding!!


    private lateinit var phone: String


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

        _binding = FragmentVerifyOtpBinding.inflate(inflater, container, false)
        val view = binding.root

        val bundle = this.arguments

        if(bundle != null){
            phone = bundle.getString("Phone").toString()
        }

        val application = requireNotNull(this.activity).application
        val dataSource = AppDatabase.getInstance(application)

        val viewModelFactory = LoginSharedViewModelFactory(phone, dataSource)
        val viewModel = ViewModelProvider(requireActivity(), viewModelFactory).get(LoginSharedViewModel::class.java)
        viewModel.setPhoneNumber(phone)


        viewModel.phoneNumber.observe(viewLifecycleOwner, { phone ->
            binding.tvOTPPhoneNumber.text = phone
        })

        binding.btnVerify.setOnClickListener {
            val isFieldsSet = checkAllFields()
            if(isFieldsSet && viewModel.newUser()){
                val transaction = fragmentManager?.beginTransaction()
                if (transaction != null) {
                    transaction.replace(R.id.loginFrameLayout, UserDetails())
                    transaction.addToBackStack("")
                    transaction.commit()
                }
            }else if(isFieldsSet && !viewModel.newUser()){
                insertToSharedPreference(viewModel.phoneNumber.value,
                    viewModel.currentUser.value?.location
                )
                val user = viewModel.phoneNumber.value
                val intent = Intent(view.context, Home::class.java).apply {
                    putExtra("USER_PHONE", user)
                }
                startActivity(intent)
            }
        }


        return view
    }

    override fun onSaveInstanceState(outState: Bundle) {
        super.onSaveInstanceState(outState)
        val manager = activity?.supportFragmentManager
        manager?.putFragment(outState, "VERIFY_OTP", VerifyOtp())
    }

您必须在屏幕开始旋转时使用 onSaveInstanceState 保存当前屏幕,并在屏幕旋转后使用 onRestoreInstanceState 恢复片段。

首先确保将其添加到 activity。这将允许我们使用状态更改。

android:configChanges="orientation|screenSize"

然后,

@Override
public void onSaveInstanceState(Bundle savedInstanceState) {
    // Save the current fragment tag or id
    savedInstanceState.putString("current_fragment", SCREEN_TAG_OR_ID);
    super.onSaveInstanceState(savedInstanceState);
}

@Override
public void onRestoreInstanceState(Bundle savedInstanceState) {
    super.onRestoreInstanceState(savedInstanceState);
    current_fragment = savedInstanceState.getString("current_fragment");
    // use this to show the current screen 
}