Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

ActionBar tab contents overlapping

I found a ton of these messages in StackOverflow. Like those many other people, I have the same problem with tab contents overlapping when switching tabs. None of the advises I found didn't work with my problem.

When my app launches, it correctly shows the contents of the first tab. When I click the other tab, the old contents stay on the screen and the other tab's content is added on the screen, too. When switching tabs second time, all the contents disappear. Switching tabs won't do anyhting anymore.

I followed Google's Developer document here.

My application has this onCreate method.. The class extends ActionBarActivity from the support libary android.support.v7.app.

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

    ActionBar actionBar = getSupportActionBar();
    actionBar.setNavigationMode(ActionBar.NAVIGATION_MODE_TABS);
    actionBar.setDisplayShowTitleEnabled(false);

    Tab tab = actionBar.newTab().setText("TAB1").setTabListener(new TabListener<Tab1Class>(this, "tab1", Tab1Class.class));
    actionBar.addTab(tab);

    tab = actionBar.newTab().setText("TAB2").setTabListener(new TabListener<Tab2Class>(this, "tab2", Tab2Class.class));
    actionBar.addTab(tab);
}

My TabListener class is copied from the page I linked:

public class TabListener<T extends Fragment> implements ActionBar.TabListener {
    private Fragment mFragment;
    private final Activity mActivity;
    private final String mTag;
    private final Class<T> mClass;

    public TabListener(Activity activity, String tag, Class<T> clz) {
        mActivity = activity;
        mTag = tag;
        mClass = clz;
    }

    @Override
    public void onTabSelected(Tab tab, FragmentTransaction ft) {
        if(mFragment == null) {
            mFragment = Fragment.instantiate(mActivity, mClass.getName());
            ft.add(android.R.id.content, mFragment, mTag);
        } else {
            ft.attach(mFragment);
        }
    }

    @Override
    public void onTabUnselected(Tab tab, FragmentTransaction ft) {
        if(mFragment != null) {
            ft.detach(mFragment);
        }
    }

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

Both classes I use as the contents of the tabs extend Fragment from android.support.v4.app.Fragment. They inflate their layouts in onCreateView methods.

What's wrong?

like image 663
MikkoP Avatar asked Oct 28 '13 18:10

MikkoP


Video Answer


1 Answers

What's wrong?

After a quick look through the code for the ActionBarActivity, there seems to be a bug for the ICS and above part of the implementation of the ActionBar(the code should work for pre ICS devices) which also takes care of the tabs.

In the ActionBarImplICS class which represents the implementation for ICS devices it seems the FragmentTransaction passed to the onTabUnselected() callback is completely useless as it isn't committed anywhere after the listener's callback returns(the transaction is committed for the other two callbacks of the TabListener). So a committed fragment will never be detached from the layout on a tab selection and it will stay getting the overlapping content(due to the FrameLayout which holds both fragments).

I've written another implementation of the TabListener interface which does all of its job from only one of the callbacks which isn't affected by the above bug(onTabSelected()):

public class TabListenerImpl implements ActionBar.TabListener {

    private List<TabInfo> mTabs = new ArrayList<TabInfo>();
    private Context mContext;

    public TabListenerImpl(Context context) {
        mContext = context;
    }

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

    }

    @Override
    public void onTabSelected(Tab tab, FragmentTransaction ft) {
        // iterate over all of the tabs, match the tag we have and see if
        // we also have a fragment instance for it. If we don't, create one
        // and add it to the container, if we have an instance simply attach
        // it. Detach every other tag which doesn't match the tag.
        for (TabInfo t : mTabs) {
            if (tab.getTag() == t.tag) {
                if (t.pageFragment == null) {
                    t.pageFragment = Fragment.instantiate(mContext,
                            t.clazz.getName());
                    ft.add(android.R.id.content, t.pageFragment, t.tag);
                } else {
                    ft.attach(t.pageFragment);
                }
            } else {
                if (t.pageFragment != null && !t.pageFragment.isDetached()) {
                    ft.detach(t.pageFragment);
                }
            }
        }
    }

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

    /**
     * Call this method BEFORE you call the actionBar.addTab() method!
     * 
     * @param tag
     *            a String representing the tag that was set on the tab to
     *            identify itself
     * @param clazz
     *            the class of the Fragment
     */
    public void addTab(String tag, Class<? extends Fragment> clazz) {
        TabInfo ti = new TabInfo();
        ti.clazz = clazz;
        ti.tag = tag;
        mTabs.add(ti);
    }

    // wrapper class
    private class TabInfo {
        Class<? extends Fragment> clazz;
        Fragment pageFragment;
        String tag;
    }

}

Which you could then use as:

TabListenerImpl listener = new TabListenerImpl(this);
Tab tab = actionBar.newTab().setText("TAB1").setTag("TAB1").setTabListener(listener);
listener.addTab("TAB1", Tab1Class.class);
actionBar.addTab(tab);

tab = actionBar.newTab().setText("TAB2").setTag("TAB2").setTabListener(listener);
listener.addTab("TAB2", Tab2Class.class);
actionBar.addTab(tab);

I would advise you to set a container as the content view(and also for the tab content) and not use the android.R.id.content container. Keep in mind that my implementation doesn't take care of configuration changes.

like image 190
user Avatar answered Sep 20 '22 16:09

user