com.github.michalbednarski.intentslab.uihelpers.FragmentTabMergingPagerAdapter.java Source code

Java tutorial

Introduction

Here is the source code for com.github.michalbednarski.intentslab.uihelpers.FragmentTabMergingPagerAdapter.java

Source

/*
 * IntentsLab - Android app for playing with Intents and Binder IPC
 * Copyright (C) 2014 Micha Bednarski
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 *     You should have received a copy of the GNU General Public License
 *     along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

package com.github.michalbednarski.intentslab.uihelpers;

import android.os.Parcelable;
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentManager;
import android.support.v4.app.FragmentTransaction;
import android.support.v4.util.ArrayMap;
import android.support.v4.view.PagerAdapter;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;

import com.github.michalbednarski.intentslab.BuildConfig;
import com.github.michalbednarski.intentslab.Utils;

import java.util.ArrayList;

/**
 * Implementation of {@link android.support.v4.view.PagerAdapter} that
 * represents each page as a {@link android.support.v4.app.Fragment} that is persistently
 * kept in the fragment manager as long as the user can return to the page.
 *
 * <p>This version of the pager is best for use when there are a handful of
 * typically more static fragments to be paged through, such as a set of tabs.
 * The fragment of each page the user visits will be kept in memory, though its
 * view hierarchy may be destroyed when not visible.  This can result in using
 * a significant amount of memory since fragment instances can hold on to an
 * arbitrary amount of state.  For larger sets of pages, consider
 * {@link android.support.v4.app.FragmentStatePagerAdapter}.
 *
 * <p>When using FragmentTabMergingPagerAdapter the host ViewPager must have a
 * valid ID set.</p>
 */
class FragmentTabMergingPagerAdapter extends PagerAdapter {
    private static final String TAG = "FragmentTabMergingPagerAdapter";
    private static final boolean DEBUG = false;

    private final FragmentManager mFragmentManager;
    private FragmentTransaction mCurTransaction = null;
    private ArrayList<PendingAddFragmentAfterMove> mPendingAddFragmentOps = null;

    private Object mCurrentPrimaryItem;
    private int mCurrentPrimaryPage = -1;

    private final Fragment[] mFragments;
    private final int[] mPageToFirstFragmentMap;
    private final int[] mFragmentToPageMap;
    private final MultiFragmentPageInfo[] mPagesInfoMap;

    public FragmentTabMergingPagerAdapter(FragmentManager fm, Fragment[] fragments,
            ArrayMap<Integer, MultiFragmentPageInfo> tabMappings) {
        mFragmentManager = fm;
        mFragments = fragments;

        int count = fragments.length;

        int[] fragmentToPageMap = new int[count];
        int[] pageToFirstFragmentMap = new int[count];
        int pageCount = 0;

        for (int fragmentIndex = 0; fragmentIndex < count;) {
            final MultiFragmentPageInfo info = tabMappings.get(fragmentIndex);
            final int page = pageCount++;

            // Get count of fragments on page
            final int fragmentsOnPage;
            if (info != null) {
                fragmentsOnPage = info.fillInIds.length;
            } else {
                fragmentsOnPage = 1;
            }

            // Ensure valid fragmentsOnPage value
            if (BuildConfig.DEBUG && fragmentsOnPage <= 0) {
                throw new AssertionError("Page must have at least one fragment");
            }

            // Create mappings for page
            for (int i = 0; i < fragmentsOnPage; i++) {
                fragmentToPageMap[fragmentIndex + i] = page;
            }
            pageToFirstFragmentMap[page] = fragmentIndex;
            fragmentIndex += fragmentsOnPage;
        }

        // Ensure that pageCount >= count
        if (BuildConfig.DEBUG && (count < pageCount || pageCount <= 0)) {
            throw new AssertionError("Invalid count of pages");
        }

        // Shrink and export to fields
        mFragmentToPageMap = fragmentToPageMap;
        mPageToFirstFragmentMap = Utils.shrinkIntArray(pageToFirstFragmentMap, pageCount);
        mPagesInfoMap = new MultiFragmentPageInfo[pageCount];
        for (int i = 0; i < tabMappings.size(); i++) {
            mPagesInfoMap[tabMappings.keyAt(i)] = tabMappings.valueAt(i);
        }
    }

    @Override
    public int getCount() {
        return mPageToFirstFragmentMap.length;
    }

    @Override
    public void startUpdate(ViewGroup container) {
    }

    @Override
    public Object instantiateItem(ViewGroup container, int page) {
        if (mCurTransaction == null) {
            mCurTransaction = mFragmentManager.beginTransaction();
        }

        // Get info about page
        final MultiFragmentPageInfo pageInfo = mPagesInfoMap[page];
        final int fragmentsOnPage = pageInfo != null ? pageInfo.fillInIds.length : 1;
        final int firstFragmentOnPage = mPageToFirstFragmentMap[page];

        // Prepare wrapper layout
        final View view;
        final Fragment[] addedFragments;
        if (pageInfo != null) {
            view = LayoutInflater.from(container.getContext()).inflate(pageInfo.containerLayout, container, false);
            container.addView(view);
            addedFragments = new Fragment[fragmentsOnPage];
        } else {
            view = null;
            addedFragments = null;
        }

        // Prepare all fragments on page
        final int pagerId = container.getId();
        for (int i = 0; i < fragmentsOnPage; i++) {
            final int fragmentIndex = firstFragmentOnPage + i;
            final int containerId = pageInfo == null ? pagerId : pageInfo.fillInIds[i];

            // Do we already have this fragment?
            String name = makeFragmentName(pagerId, fragmentIndex);
            Fragment fragment = mFragmentManager.findFragmentByTag(name);
            if (fragment != null) {
                if (DEBUG)
                    Log.v(TAG, "Attaching item #" + fragmentIndex + ": f=" + fragment);
                if (fragment.getId() != containerId) {
                    // Added to another container
                    moveFragment(fragment, containerId);
                } else {
                    // Already added, possibly detached
                    if (pageInfo != null) {
                        // When activity was created this tab wasn't ready
                        // Detach and re-attach this fragment
                        mCurTransaction.detach(fragment);
                    }

                    mCurTransaction.attach(fragment);
                }
            } else {
                // Not added
                fragment = mFragments[fragmentIndex];

                if (DEBUG)
                    Log.v(TAG, "Adding item #" + fragmentIndex + ": f=" + fragment);

                mCurTransaction.add(containerId, fragment, name);
            }

            // Deactivate menu if not on current page
            if (page != mCurrentPrimaryPage) {
                fragment.setMenuVisibility(false);
                fragment.setUserVisibleHint(false);
            }

            // For single fragment page return fragment
            if (pageInfo == null) {
                return fragment;
            }

            // On multi fragment page add to page
            addedFragments[i] = fragment;
        }

        // Return descriptor of multi fragment page
        return new AddedPageInfo(view, addedFragments);
    }

    private void moveFragment(Fragment fragment, int newContainerId) {
        // We must remove, commit, executePending and add again to move
        // http://stackoverflow.com/a/17775067

        // Schedule fragment to be re-added
        if (mPendingAddFragmentOps == null) {
            mPendingAddFragmentOps = new ArrayList<PendingAddFragmentAfterMove>();
        }
        mPendingAddFragmentOps.add(new PendingAddFragmentAfterMove(newContainerId, fragment, fragment.getTag()));

        // Remove it now
        mCurTransaction.remove(fragment);
    }

    @Override
    public void destroyItem(ViewGroup container, int page, Object object) {
        if (mCurTransaction == null) {
            mCurTransaction = mFragmentManager.beginTransaction();
        }

        if (DEBUG)
            Log.v(TAG, "Detaching items from page #" + page);

        if (object instanceof AddedPageInfo) {
            for (Fragment fragment : ((AddedPageInfo) object).fragments) {
                mCurTransaction.detach(fragment);
            }
            container.removeView(((AddedPageInfo) object).view);
        } else {
            mCurTransaction.detach((Fragment) object);
        }
    }

    @Override
    public void setPrimaryItem(ViewGroup container, int page, Object object) {
        if (object != mCurrentPrimaryItem) {
            if (mCurrentPrimaryItem != null) {
                activateOrDeactivatePage(mCurrentPrimaryItem, false);
            }
            if (object != null) {
                activateOrDeactivatePage(object, true);
            }
            mCurrentPrimaryItem = object;
            mCurrentPrimaryPage = page;
        }
    }

    private void activateOrDeactivatePage(Object object, boolean activate) {
        if (object instanceof AddedPageInfo) {
            for (Fragment fragment : ((AddedPageInfo) object).fragments) {
                fragment.setMenuVisibility(activate);
                fragment.setUserVisibleHint(activate);
            }
        } else {
            Fragment fragment = (Fragment) object;
            fragment.setMenuVisibility(activate);
            fragment.setUserVisibleHint(activate);
        }
    }

    @Override
    public void finishUpdate(ViewGroup container) {
        if (mCurTransaction != null) {
            mCurTransaction.commitAllowingStateLoss();
            mCurTransaction = null;
            mFragmentManager.executePendingTransactions();

            // Execute scheduled re-additions after remove
            if (mPendingAddFragmentOps != null) {
                final FragmentTransaction transaction = mFragmentManager.beginTransaction();
                for (PendingAddFragmentAfterMove op : mPendingAddFragmentOps) {
                    op.addToTransaction(transaction);
                }
                mPendingAddFragmentOps = null;
                transaction.commitAllowingStateLoss();
                mFragmentManager.executePendingTransactions();
            }
        }
    }

    @Override
    public boolean isViewFromObject(View view, Object object) {
        if (object instanceof AddedPageInfo) {
            return ((AddedPageInfo) object).view == view;
        }
        return ((Fragment) object).getView() == view;
    }

    @Override
    public Parcelable saveState() {
        return null;
    }

    @Override
    public void restoreState(Parcelable state, ClassLoader loader) {
    }

    private static String makeFragmentName(int viewId, long id) {
        return "android:switcher:" + viewId + ":" + id;
    }

    // Save/restore state
    private int mSavedPageNumber = -1;
    private int mSavedFragmentNumber;

    int fragmentNumberToPageNumber(int fragmentNumber) {
        if (fragmentNumber > 0) {
            mSavedFragmentNumber = fragmentNumber;
            mSavedPageNumber = mFragmentToPageMap[fragmentNumber];
            return mSavedPageNumber;
        }
        return 0;
    }

    int getCurrentFragmentNumber() {
        if (mCurrentPrimaryPage == mSavedPageNumber) {
            return mSavedFragmentNumber;
        }
        return mPageToFirstFragmentMap[mCurrentPrimaryPage];
    }

    // Info about tabs to merge
    static final class MultiFragmentPageInfo {
        MultiFragmentPageInfo(int containerLayout, int[] fillInIds) {
            this.containerLayout = containerLayout;
            this.fillInIds = fillInIds;
        }

        final int containerLayout;
        final int[] fillInIds;
    }

    // Created page view and fragment holder
    private static final class AddedPageInfo {
        AddedPageInfo(View view, Fragment[] fragments) {
            this.view = view;
            this.fragments = fragments;
        }

        final View view;
        final Fragment[] fragments;
    }

    private static final class PendingAddFragmentAfterMove {
        private int mContainerViewId;
        private Fragment mFragment;
        private String mTag;

        PendingAddFragmentAfterMove(int containerViewId, Fragment fragment, String tag) {
            mContainerViewId = containerViewId;
            mFragment = fragment;
            mTag = tag;
        }

        void addToTransaction(FragmentTransaction transaction) {
            transaction.add(mContainerViewId, mFragment, mTag);
        }
    }
}