片段中的ViewPager导致片段在方向更改时加载两次

时间:2019-07-16 09:51:17

标签: android android-viewpager fragment

我有一个片段类,其中包含带有两个站点的ViewPager。这些网站包含一些小工具,例如CheckBoxes,我希望它们在方向改变时保持选中状态。

MainFragment:

class StatsFragment: Fragment() {

    private val fragmentStatsCat = StatsCategoryFragment()
    private val fragmentStatsMon = StatsMonthFragment()
    private lateinit var pager: ViewPager

    override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
        val view: View = inflater.inflate(R.layout.fragment_stats, container, false)

        pager = view.findViewById(R.id.stats_container)
        val pagerAdapter = StatsScreenSlidePagerAdapter(childFragmentManager)
        pager.adapter = pagerAdapter

        return view
    }

    private inner class StatsScreenSlidePagerAdapter(fm: FragmentManager): FragmentStatePagerAdapter(fm, BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT) {
        override fun getItem(position: Int): Fragment {
            return if (position == 0) {
                fragmentStatsCat
            } else {
                fragmentStatsMon
            }
        }

        override fun getCount(): Int = 2

        override fun saveState(): Parcelable? {
            return null
        }
    }
}

其中一个ViewPagerFragments:

class StatsMonthFragment: Fragment() {

    companion object {
        private const val CHECKBOX_KEY = "isChecked"
        private const val CATEGORY_KEY = "category"
    }

    private lateinit var btnPrevCat: ImageButton
    private lateinit var btnNextCat: ImageButton
    private lateinit var cbAllCats: CheckBox
    private lateinit var categories: List<Category>
    private var i: Int = 0
    private var isCbChecked: Boolean? = null

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        if(savedInstanceState != null) {
            i = savedInstanceState.getInt(CATEGORY_KEY)
            isCbChecked = savedInstanceState.getBoolean(CHECKBOX_KEY)
        }
    }

    override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
        val view = inflater.inflate(R.layout.fragment_stats_months, container, false)

        btnNextCat = view.findViewById(R.id.btn_next_cat)
        btnPrevCat = view.findViewById(R.id.btn_prev_cat)
        cbAllCats = view.findViewById(R.id.cb_all_cats)

        categories = DatabaseInitializer.getInstance().getAllCategories(AppDatabase.getInstance(context).categoryDao())

        if(isCbChecked == null || isCbChecked!!) {
            val initFragment = StatsSelectedFilterFragment()    //This is just a fragment with a TextView; I use fragments for that for the CustomAnimations
            initFragment.setText(categories[i].cat_name)
        childFragmentManager.beginTransaction().add(R.id.container_stats_categories, initFragment).commit()
        } else {
            val fragment = StatsSelectedFilterFragment()
            fragment.setText(getString(R.string.all_categories))
            childFragmentManager.beginTransaction()
                .replace(R.id.container_stats_categories, fragment)
                .commit()
        }


        btnNextCat.setOnClickListener {
            val fragment = StatsSelectedFilterFragment()
            fragment.setText(categories[i].cat_name)
            childFragmentManager.beginTransaction()
                .setCustomAnimations(R.anim.slide_in_right, R.anim.slide_out_left)
                .replace(R.id.container_stats_categories, fragment)
                .commit()
        }
        btnPrevCat.setOnClickListener {
            val fragment = StatsSelectedFilterFragment()
            fragment.setText(categories[i].cat_name)
            childFragmentManager.beginTransaction()
                .setCustomAnimations(R.anim.slide_in_left, R.anim.slide_out_right)
                .replace(R.id.container_stats_categories, fragment)
                .commit()
        }

        cbAllCats.setOnCheckedChangeListener{_,checked->
            if(checked) {
                val fragment = StatsSelectedFilterFragment()
                fragment.setText(getString(R.string.all_categories))
                childFragmentManager.beginTransaction()
                    .replace(R.id.container_stats_categories, fragment)
                    .commit()
            } else {
                val fragment = StatsSelectedFilterFragment()
                fragment.setText(categories[i].cat_name)
                childFragmentManager.beginTransaction()
                    .replace(R.id.container_stats_categories, fragment)
                    .commit()
            }
        }

        return view
    }

    override fun onSaveInstanceState(outState: Bundle) {
        super.onSaveInstanceState(outState)
        val isAllCatsChecked = cbAllCats.isChecked
        outState.putBoolean(CHECKBOX_KEY, isAllCatsChecked)
        outState.putInt(CATEGORY_KEY, i)
    }
}

现在,每次屏幕旋转时,片段都会被重新创建两次,首先使用savedInstanceState != null,然后使用savedInstanceState == null,这意味着我无权访问Fragments中的旧设置。据我所知,这是因为重新创建了片段,然后还重新创建了包含viewpager的MainFragment,这意味着第二次创建了viewPager中的片段。我已经在MainFragment中尝试过类似的事情:

private lateinit var fragmentStatsCat: StatsCategoryFragment
private lateinit var fragmentStatsMon: StatsMonthFragment

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    if(savedInstanceState == null) {
        fragmentStatsMon = StatsMonthFragment()
        fragmentStatsCat = StatsCategoryFragment()
    } else {
        //Finding old fragments
    }
}

也许这可以解决问题(我不确定),但是我不知道如何找到这些片段,因为它们位于ViewPager中。

编辑 我现在找到了一种在重新创建活动时查找碎片的解决方案:

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    if(savedInstanceState == null)
    {
        fragmentStatsMon = StatsMonthFragment()
        fragmentStatsCat = StatsCategoryFragment()
    } else {
        fragmentStatsMon = childFragmentManager.getFragment(savedInstanceState, FRAG_MONTH_TAG) as StatsMonthFragment
        fragmentStatsCat = childFragmentManager.getFragment(savedInstanceState, FRAG_CAT_TAG) as StatsCategoryFragment
    }
}
override fun onSaveInstanceState(outState: Bundle) {
    super.onSaveInstanceState(outState)
    childFragmentManager.putFragment(outState, FRAG_CAT_TAG, fragmentStatsCat)
    childFragmentManager.putFragment(outState, FRAG_MONTH_TAG, fragmentStatsMon)
}

这会在Bundle中添加一个引用,之后可以使用该引用获取片段。但是现在,我还有另一个问题。重新创建活动时,它将引发以下异常: java.lang.IllegalStateException: Fragment already added: StatsCategoryFragment{f06ec65 (a1991f39-3af4-4a74-9aeb-79274beae04a) id=0x7f09014a} 我不知道如何解决这个问题,因为我并没有真正手动添加片段。另外,我真的不明白,因为viewpager和适配器也被重新创建了,不是吗?那么,为什么碎片仍然附着在适配器上呢? 我也不知道发生在哪一行代码,我既无法在logcat中找到它,也无法通过调试找到它。

2 个答案:

答案 0 :(得分:0)

将此行放在要加载片段的“活动”条目的AndroidManifest.xml中:

decimal

通过这种方式,在任何情况下重新创建您的片段时,数据都不会重置。

示例:

android:configChanges="layoutDirection|keyboardHidden|orientation|screenSize"

答案 1 :(得分:0)