Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to delete a tab with actionbar, viewpager, and multiple fragments?

I'm using code I found here.

public class ActionBarTabs extends SherlockFragmentActivity {
CustomViewPager mViewPager;
TabsAdapter mTabsAdapter;
TextView tabCenter;
TextView tabText;
@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN,
            WindowManager.LayoutParams.FLAG_FULLSCREEN);

    mViewPager = new CustomViewPager(this);
    mViewPager.setId(R.id.pager);

    setContentView(mViewPager);
    ActionBar bar = getSupportActionBar();
    bar.setNavigationMode(ActionBar.NAVIGATION_MODE_TABS);
    bar.setDisplayOptions(0, ActionBar.DISPLAY_SHOW_TITLE);

    mTabsAdapter = new TabsAdapter(this, mViewPager);

    mTabsAdapter.addTab(bar.newTab().setText("Home"),
            ToolKitFragment.class, null);
    mTabsAdapter.addTab(bar.newTab().setText("FujiSan"),
            FujiFragment.class, null);
}

public static class TabsAdapter extends FragmentPagerAdapter implements
        ActionBar.TabListener, ViewPager.OnPageChangeListener {
    private final Context mContext;
    private final ActionBar mActionBar;
    private final ViewPager mViewPager;
    private final ArrayList<TabInfo> mTabs = new ArrayList<TabInfo>();

    static final class TabInfo {
        private final Class<?> clss;
        private final Bundle args;

        TabInfo(Class<?> _class, Bundle _args) {
            clss = _class;
            args = _args;
        }
    }

    public TabsAdapter(SherlockFragmentActivity activity, ViewPager pager) {
        super(activity.getSupportFragmentManager());
        mContext = activity;
        mActionBar = activity.getSupportActionBar();
        mViewPager = pager;
        mViewPager.setAdapter(this);
        mViewPager.setOnPageChangeListener(this);
    }

    public void addTab(ActionBar.Tab tab, Class<?> clss, Bundle args) {
        TabInfo info = new TabInfo(clss, args);
        tab.setTag(info);
        tab.setTabListener(this);
        mTabs.add(info);
        mActionBar.addTab(tab);
        notifyDataSetChanged();
    }

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

    @Override
    public Fragment getItem(int position) {
        TabInfo info = mTabs.get(position);
        return Fragment.instantiate(mContext, info.clss.getName(),
                info.args);
    }

    @Override
    public void onPageScrolled(int position, float positionOffset,
            int positionOffsetPixels) {
    }

    @Override
    public void onPageSelected(int position) {
        mActionBar.setSelectedNavigationItem(position);
    }

    @Override
    public void onPageScrollStateChanged(int state) {
    }

    @Override
    public void onTabSelected(Tab tab, FragmentTransaction ft) {
        Object tag = tab.getTag();
        for (int i = 0; i < mTabs.size(); i++) {
            if (mTabs.get(i) == tag) {
                mViewPager.setCurrentItem(i);
            }
        }
    }

    @Override
    public void onTabUnselected(Tab tab, FragmentTransaction ft) {
    }

    @Override
    public void onTabReselected(Tab tab, FragmentTransaction ft) {
    }
}
}

I'm trying to modify it so that I can delete tabs dynamically. I tried by simply creating this function:

    public void removeTab(ActionBar.Tab tab) {
        mTabs.remove(tab);
        mActionBar.removeTab(tab);
        notifyDataSetChanged();
    }

But I always get an indexoutofboundsexception. Does anyone know a good way to do this?

EDIT

By changing my method to:

    public void removeTab(ActionBar.Tab tab) {
        mTabs.remove(tab.getTag());
        mActionBar.removeTab(tab);
        notifyDataSetChanged();
    }

I was able to successfully remove tabs. However, when I delete a tab that IS NOT the one on the far right, the view (or fragment?) that was associated with the tab doesn't go away. Instead, it seems to become associated with the next highest tab. For instance, if I deleted tab 0, tab 0's view just moves to tab 1. If I delete the tab with the highest ID then the tab and view go away correctly but then COME BACK when I add a new tab.

It's as if there's a list of fragments somewhere that is being associated with the tabs and deleting the tab doesn't delete the fragment. Does anyone have any idea what's causing this?

EDIT2

I've tried to delete the fragments with:

            Fragment fragmentToRemove = getItem(tab.getPosition());
            destroyItem(mViewPager, tab.getPosition(), fragmentToRemove);

and also

        Fragment fragmentToRemove = getItem(tab.getPosition());
        FragmentTransaction fragmentTransaction = mActivity
                .getSupportFragmentManager().beginTransaction();
        fragmentTransaction.remove(fragmentToRemove);
        fragmentTransaction.commit();

But neither has worked so far.

like image 850
Adam Avatar asked Jul 25 '12 18:07

Adam


2 Answers

Got it!

public void removeTab(ActionBar.Tab tab) {
    mTabs.remove(tab.getTag());
    mActionBar.removeTab(tab);
    notifyDataSetChanged();
}

and override destroyItem

    @Override
    public void destroyItem(ViewGroup container, int position, Object object) {
        // TODO Auto-generated method stub
        super.destroyItem(container, position, object);
        FragmentManager manager = ((Fragment) object).getFragmentManager();
        FragmentTransaction trans = manager.beginTransaction();
        trans.remove((Fragment) object);
        trans.commit();
    }

Works perfectly!!!

like image 74
Adam Avatar answered Sep 27 '22 21:09

Adam


I just discovered today that the real trick to supporting inserting or removing tabs is to override FragmentPagerAdapter.getItemPosition():

@Override
public int getItemPosition(Object object) {
    Fragment fragment = (Fragment) object;
    for (int i = 0; i < tabInfos.size(); i++) {
        if (tabInfos.get(i).clss == fragment.getClass()) {
            return i;
        }
    }
    // The tab must have been removed
    return POSITION_NONE;
}

getItemPosition() will be called by the ViewPager after you have called notifyDataSetChanged() (after adding/removing tabs). The ViewPager is trying to resync its cache with this adapter. It calls getItemPosition() for each 'object' (Fragment) it holds, and we tell it what position that Fragment is from. If there is no match, it is because we deleted that tab, so return POSITION_NONE.

I have also found it's important to override FragmentPagerAdapter.getItemId() if you insert or remove tabs, because the Fragments will be shifting positions left and right. This method is called by FragmentPagerAdapter.instantiateItem() and is appended to a name (String) for the item. It uses this name to lookup a pre-existing Fragment with a matching name (if there is one) otherwise instantiate a new one. Here's some code:

@Override
public long getItemId(int position) {
    TabInfo info = tabInfos.get(position);
    // The default hashCode() implementation is based on memory address, which will be unique
    int hash = info.clss.hashCode();
    return hash;
}
like image 37
jfritz42 Avatar answered Sep 27 '22 21:09

jfritz42