reorder pages in FragmentStatePagerAdapter using getItemPosition(Object object)

前端 未结 4 449
梦谈多话
梦谈多话 2020-12-04 20:08

I believe that FragmentStatePagerAdapter does not behave correctly when overriding getItemPosition(Object object) with the purpose of reordering the pages.

4条回答
  •  难免孤独
    2020-12-04 20:26

    I've reimplemented the existing solution in Kotlin such that it allows you to return a String instead of a long for the item id. You can find it here or below:

    import android.annotation.SuppressLint
    import android.os.Bundle
    import android.os.Parcelable
    import android.support.v4.app.Fragment
    import android.support.v4.app.FragmentManager
    import android.support.v4.app.FragmentTransaction
    import android.view.View
    import android.view.ViewGroup
    import java.util.HashSet
    import java.util.LinkedHashMap
    
    /**
     * A PagerAdapter that can withstand item reordering. See
     * https://issuetracker.google.com/issues/36956111.
     *
     * @see android.support.v4.app.FragmentStatePagerAdapter
     */
    abstract class MovableFragmentStatePagerAdapter(
            private val manager: FragmentManager
    ) : NullablePagerAdapter() {
        private var currentTransaction: FragmentTransaction? = null
        private var currentPrimaryItem: Fragment? = null
    
        private val savedStates = LinkedHashMap()
        private val fragmentsToItemIds = LinkedHashMap()
        private val itemIdsToFragments = LinkedHashMap()
        private val unusedRestoredFragments = HashSet()
    
        /** @see android.support.v4.app.FragmentStatePagerAdapter.getItem */
        abstract fun getItem(position: Int): Fragment
    
        /**
         * @return a unique identifier for the item at the given position.
         */
        abstract fun getItemId(position: Int): String
    
        /** @see android.support.v4.app.FragmentStatePagerAdapter.startUpdate */
        override fun startUpdate(container: ViewGroup) {
            check(container.id != View.NO_ID) {
                "ViewPager with adapter $this requires a view id."
            }
        }
    
        /** @see android.support.v4.app.FragmentStatePagerAdapter.instantiateItem */
        override fun instantiateItem(container: ViewGroup, position: Int): Any {
            val itemId = getItemId(position)
    
            val f = itemIdsToFragments[itemId]
            if (f != null) {
                unusedRestoredFragments.remove(f)
                return f
            }
    
            if (currentTransaction == null) {
                // We commit the transaction later
                @SuppressLint("CommitTransaction")
                currentTransaction = manager.beginTransaction()
            }
    
            val fragment = getItem(position)
            fragmentsToItemIds.put(fragment, itemId)
            itemIdsToFragments.put(itemId, fragment)
    
            val fss = savedStates[itemId]
            if (fss != null) {
                fragment.setInitialSavedState(fss)
            }
            fragment.setMenuVisibility(false)
            fragment.userVisibleHint = false
    
            currentTransaction!!.add(container.id, fragment)
    
            return fragment
        }
    
        /** @see android.support.v4.app.FragmentStatePagerAdapter.destroyItem */
        override fun destroyItem(container: ViewGroup, position: Int, fragment: Any) {
            (fragment as Fragment).destroy()
        }
    
        /** @see android.support.v4.app.FragmentStatePagerAdapter.setPrimaryItem */
        override fun setPrimaryItem(container: ViewGroup, position: Int, fragment: Any?) {
            fragment as Fragment?
            if (fragment !== currentPrimaryItem) {
                currentPrimaryItem?.let {
                    it.setMenuVisibility(false)
                    it.userVisibleHint = false
                }
    
                fragment?.setMenuVisibility(true)
                fragment?.userVisibleHint = true
                currentPrimaryItem = fragment
            }
        }
    
        /** @see android.support.v4.app.FragmentStatePagerAdapter.finishUpdate */
        override fun finishUpdate(container: ViewGroup) {
            if (!unusedRestoredFragments.isEmpty()) {
                for (fragment in unusedRestoredFragments) fragment.destroy()
                unusedRestoredFragments.clear()
            }
    
            currentTransaction?.let {
                it.commitAllowingStateLoss()
                currentTransaction = null
                manager.executePendingTransactions()
            }
        }
    
        /** @see android.support.v4.app.FragmentStatePagerAdapter.isViewFromObject */
        override fun isViewFromObject(view: View, fragment: Any): Boolean =
                (fragment as Fragment).view === view
    
        /** @see android.support.v4.app.FragmentStatePagerAdapter.saveState */
        override fun saveState(): Parcelable? = Bundle().apply {
            putStringArrayList(KEY_FRAGMENT_IDS, ArrayList(savedStates.keys))
            putParcelableArrayList(
                    KEY_FRAGMENT_STATES,
                    ArrayList(savedStates.values)
            )
    
            for ((f, id) in fragmentsToItemIds.entries) {
                if (f.isAdded) {
                    manager.putFragment(this, "$KEY_FRAGMENT_STATE$id", f)
                }
            }
        }
    
        /** @see android.support.v4.app.FragmentStatePagerAdapter.restoreState */
        override fun restoreState(state: Parcelable?, loader: ClassLoader?) {
            if ((state as Bundle?)?.apply { classLoader = loader }?.isEmpty == false) {
                state!!
    
                fragmentsToItemIds.clear()
                itemIdsToFragments.clear()
                unusedRestoredFragments.clear()
                savedStates.clear()
    
                val fragmentIds: List = state.getStringArrayList(KEY_FRAGMENT_IDS)
                val fragmentStates: List =
                        state.getParcelableArrayList(KEY_FRAGMENT_STATES)
    
                for ((index, id) in fragmentIds.withIndex()) {
                    savedStates.put(id, fragmentStates[index])
                }
    
                for (key: String in state.keySet()) {
                    if (key.startsWith(KEY_FRAGMENT_STATE)) {
                        val itemId = key.substring(KEY_FRAGMENT_STATE.length)
    
                        manager.getFragment(state, key)?.let {
                            it.setMenuVisibility(false)
                            fragmentsToItemIds.put(it, itemId)
                            itemIdsToFragments.put(itemId, it)
                        }
                    }
                }
    
                unusedRestoredFragments.addAll(fragmentsToItemIds.keys)
            }
        }
    
        private fun Fragment.destroy() {
            if (currentTransaction == null) {
                // We commit the transaction later
                @SuppressLint("CommitTransaction")
                currentTransaction = manager.beginTransaction()
            }
    
            val itemId = fragmentsToItemIds.remove(this)
            itemIdsToFragments.remove(itemId)
            if (itemId != null) {
                savedStates.put(itemId, manager.saveFragmentInstanceState(this))
            }
    
            currentTransaction!!.remove(this)
        }
    
        private companion object {
            const val KEY_FRAGMENT_IDS = "fragment_keys_"
            const val KEY_FRAGMENT_STATES = "fragment_states_"
            const val KEY_FRAGMENT_STATE = "fragment_state_"
        }
    }
    

    And the Java piece:

    import android.support.annotation.NonNull;
    import android.support.annotation.Nullable;
    import android.support.v4.view.PagerAdapter;
    import android.view.ViewGroup;
    
    /**
     * A PagerAdapter whose {@link #setPrimaryItem} is overridden with proper nullability annotations.
     */
    public abstract class NullablePagerAdapter extends PagerAdapter {
        @Override
        public void setPrimaryItem(@NonNull ViewGroup container,
                                   int position,
                                   @Nullable Object object) {
            // `object` is actually nullable. It's even in the dang source code which is hilariously
            // ridiculous:
            // `mAdapter.setPrimaryItem(this, mCurItem, curItem != null ? curItem.object : null);`
        }
    }
    

提交回复
热议问题