Android Fragments - Multiple tabs/pageflows, custo

2019-08-04 06:46发布

问题:

I'm running into memory issues with fragments and I could use some help as to the appropriate path to take. I cannot use a simple backstack because the application needs to retain several paths that the user takes within the application (and the user can jump back and forth). The navigation handles fragments in this way:

transaction.hide(currentFragment).show(newFragment).commit();

What I think would help my situation is having the view of the fragment temporarily destroyed and then recreated when the fragment gets placed back in view (instead of simply hiding the UI from the user's view). From reading the API, it doesn't sound like the hide method does this. Does anyone know if there are some built-in methods to the FragmentTransaction/FragmentManager/Fragment class that will allow me to do this?

Another option I'm considering is creating my own lifecycle for each fragment. I've explored using tabhost as well, but it doesn't appear it's going to solve the memory issues. If you have another idea, I'm open to it.

Thanks guys, I appreciate your help with this.

回答1:

I came up with a solution to achieve a better memory managed solution (adapter-based) for having multiple user journeys saved at the same time (independent tabs with something that represents backstacks). I scrapped using the FragmentManager as it was not conducive to this behavior. I will leave my answer here in case anyone else runs into a similar problem of needing this idea of multiple backstacks using ViewPagers with FragmentStatePagerAdapter - one viewpager for each tab in their app (this can still be cleaned up more but you can get the jist):

Primary Activity:

public class MultiTabbedActivity extends FragmentActivity implements NavigationFragment.OnNavigationSelectedListener
{

    private Page1Fragment mPage1;
    private Page2Fragment mPage2;


    private CustomFragment mCurrent;
    private String mNav;

    private List<CustomFragment> mPage1BackStack;
    private List<CustomFragment> mPage2BackStack;

    private CustomViewPager mPagerPage1;
    private CustomViewPager mPagerPage2;

    private boolean mUpdating = false;

    @Override
    protected void onCreate(Bundle savedInstanceState)
    {
        super.onCreate(savedInstanceState);

        // initialize the backstacks
        mPage1BackStack = new ArrayList<CustomFragment>();
        mPage2BackStack = new ArrayList<CustomFragment>();

        mPage1 = new Page1Fragment();
        mPage2 = new Page2Fragment();

        mPage1BackStack.add(mPage1);
        mPage2BackStack.add(mPage2);

        mPagerPage1 = (CustomViewPager) findViewById(R.id.page1_fragment);
        mPagerPage2 = (CustomViewPager) findViewById(R.id.page2_fragment);

        mPagerPage1.setOffscreenPageLimit(3); //Customizable to determine how many 
        mPagerPage2.setOffscreenPageLimit(3); //fragments you want to hold in memory

        mPagerPage1.setAdapter(new CustomFragmentAdapter(getSupportFragmentManager(), mPage1BackStack));
        mPagerPage2.setAdapter(new CustomFragmentAdapter(getSupportFragmentManager(), mPage2BackStack));

        displayPager(mPagerPage1);
        mCurrent = mPage1;
        mNav = GlobalConstants.PAGE_PAGE1;

    }

    private void displayPager(ViewPager pager)
    {
        mPagerPage1.setVisibility(ViewPager.GONE);
        mPagerPage2.setVisibility(ViewPager.GONE);

        pager.setCurrentItem(pager.getAdapter().getCount() - 1);
        pager.getAdapter().notifyDataSetChanged();
        pager.setVisibility(ViewPager.VISIBLE);
    }

    @Override
    public void onNavSelected(String page)
    {
        if (page != null && page != "" && !mUpdating)
        {
            // Determine the Fragment selected
            if (page.equalsIgnoreCase(GlobalConstants.PAGE_PAGE1))
            {
                mNav = GlobalConstants.PAGE_PAGE1;
                displayPager(mPagerPage1);
                mCurrent = mPage1BackStack.get(mPage1BackStack.size() - 1);
            }
            else if (page.equalsIgnoreCase(GlobalConstants.PAGE_PAGE2))
            {
                mNav = GlobalConstants.PAGE_PAGE2;
                displayPager(mPagerPage2);
                mCurrent = mPage2BackStack.get(mPage2BackStack.size() - 1);
            }
        }
    }


    @Override
    public void onBackPressed()
    {
        PageFragment navFrag = null;
        // Update the Navigation Menu to indicate the currently visible
        // Fragment
        try
        {
            mUpdating = true;

            if (mPage1BackStack.size() > 1 && mNav.equalsIgnoreCase(GlobalConstants.PAGE_PAGE1))
            {
                mPagerPage1.setCurrentItem(mPage1BackStack.size() - 2);
                mPage1BackStack.remove(mPage1BackStack.size() - 1);
                navFrag = mPage1BackStack.get(mPage1BackStack.size() - 1);
                mPagerPage1.setAdapter(new CustomFragmentAdapter(getSupportFragmentManager(), mPage1BackStack));
            }
            else if (mPage2BackStack.size() > 1 && mNav.equalsIgnoreCase(GlobalConstants.PAGE_PAGE2))
            {
                mPagerPage2.setCurrentItem(mPage2BackStack.size() - 2);
                mPage2BackStack.remove(mPage2BackStack.size() - 1);
                navFrag = mPage2BackStack.get(mPage2BackStack.size() - 1);
                mPagerPage2.setAdapter(new CustomFragmentAdapter(getSupportFragmentManager(), mPage2BackStack));
            }
            else
            {
                super.onBackPressed();
            }

            if (navFrag != null)
                mCurrent = navFrag;
        }
        catch (Exception e)
        {
            e.printStackTrace();
        }
        finally
        {
            mUpdating = false;
        }
    }

    public void updateFragment(PageFragment newFragment)
    {
        if (mNav.equalsIgnoreCase(GlobalConstants.PAGE_PAGE1))
        {
            mPage1BackStack.add(newFragment);
            displayPager(mPagerPage1);
        }
        else if (mNav.equalsIgnoreCase(GlobalConstants.PAGE_PAGE2))
        {
            mPage2BackStack.add(newFragment);
            displayPager(mPagerPage2);
        }
        mCurrent = newFragment;
    }
}

CustomViewPager:

public class CustomViewPager extends ViewPager {

    private boolean enabled;

    public CustomViewPager(Context context, AttributeSet attrs) {
        super(context, attrs);
        this.enabled = false;
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        if (this.enabled) {
            return super.onTouchEvent(event);
        }

        return false;
    }

    @Override
    public boolean onInterceptTouchEvent(MotionEvent event) {
        if (this.enabled) {
            return super.onInterceptTouchEvent(event);
        }

        return false;
    }

    public void setPagingEnabled(boolean enabled) {
        this.enabled = enabled;
    }
}

CustomAdapter:

public class CustomFragmentAdapter extends FragmentStatePagerAdapter
{
    List<CustomFragment> mFragments;

    public CustomFragmentAdapter(FragmentManager fm, List<CustomFragment> fragments)
    {
        super(fm);
        mFragments = fragments;
    }

    public List<PageFragment> getmFragments()
    {
        return mFragments;
    }

    public void setmFragments(List<CustomFragment> mFragments)
    {
        this.mFragments = mFragments;
    }

    @Override
    public int getCount()
    {
        return mFragments.size();
    }

    @Override
    public Fragment getItem(int position)
    {
        return (Fragment) (mFragments.get(position));
    }
}

UI (wrapped inside a linear layout):

    <fragment
        android:id="@+id/navigation_fragment"
        android:name="your.package.here.NavigationFragment"
        android:layout_width="70dp"
        android:layout_height="match_parent" />

    <FrameLayout
        android:id="@+id/page_fragment"
        android:layout_width="wrap_content"
        android:layout_height="match_parent"
        android:layout_weight="2" >

        <your.package.here.CustomViewPager
            android:id="@+id/page1_fragment"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:visibility="gone" />
        <your.package.here.CustomViewPager
            android:id="@+id/page2_fragment"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:visibility="gone" />
    </FrameLayout>