FragmentTabHost.java revision 0adacc1aa313d757ae1c517152cef838e0f35c13
1/*
2 * Copyright (C) 2012 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package android.support.v4.app;
18
19import java.util.HashMap;
20
21import android.content.Context;
22import android.content.res.TypedArray;
23import android.os.Bundle;
24import android.util.AttributeSet;
25import android.view.View;
26import android.view.ViewGroup;
27import android.widget.FrameLayout;
28import android.widget.LinearLayout;
29import android.widget.TabHost;
30import android.widget.TabWidget;
31
32/**
33 * Special TabHost that allows the use of {@link Fragment} objects for
34 * its tab content.  When placing this in a view hierarchy, after inflating
35 * the hierarchy you must call {@link #setup(Context, FragmentManager, int)}
36 * to complete the initialization of the tab host.
37 */
38public class FragmentTabHost extends TabHost
39        implements TabHost.OnTabChangeListener {
40    private final HashMap<String, TabInfo> mTabs = new HashMap<String, TabInfo>();
41    private FrameLayout mRealTabContent;
42    private Context mContext;
43    private FragmentManager mFragmentManager;
44    private int mContainerId;
45    private TabHost.OnTabChangeListener mOnTabChangeListener;
46    private TabInfo mLastTab;
47
48    static final class TabInfo {
49        private final String tag;
50        private final Class<?> clss;
51        private final Bundle args;
52        private Fragment fragment;
53
54        TabInfo(String _tag, Class<?> _class, Bundle _args) {
55            tag = _tag;
56            clss = _class;
57            args = _args;
58        }
59    }
60
61    static class DummyTabFactory implements TabHost.TabContentFactory {
62        private final Context mContext;
63
64        public DummyTabFactory(Context context) {
65            mContext = context;
66        }
67
68        @Override
69        public View createTabContent(String tag) {
70            View v = new View(mContext);
71            v.setMinimumWidth(0);
72            v.setMinimumHeight(0);
73            return v;
74        }
75    }
76
77    public FragmentTabHost(Context context) {
78        // Note that we call through to the version that takes an AttributeSet,
79        // because the simple Context construct can result in a broken object!
80        super(context, null);
81        initFragmentTabHost(context, null);
82    }
83
84    public FragmentTabHost(Context context, AttributeSet attrs) {
85        super(context, attrs);
86        initFragmentTabHost(context, attrs);
87    }
88
89    private void initFragmentTabHost(Context context, AttributeSet attrs) {
90        TypedArray a = context.obtainStyledAttributes(attrs,
91                new int[] { android.R.attr.inflatedId }, 0, 0);
92        mContainerId = a.getResourceId(0, 0);
93        a.recycle();
94
95        super.setOnTabChangedListener(this);
96
97        // If owner hasn't made its own view hierarchy, then as a convenience
98        // we will construct a standard one here.
99        if (findViewById(android.R.id.tabs) == null) {
100            LinearLayout ll = new LinearLayout(context);
101            ll.setOrientation(LinearLayout.VERTICAL);
102            addView(ll, new FrameLayout.LayoutParams(
103                    ViewGroup.LayoutParams.FILL_PARENT,
104                    ViewGroup.LayoutParams.FILL_PARENT));
105
106            TabWidget tw = new TabWidget(context);
107            tw.setId(android.R.id.tabs);
108            tw.setOrientation(TabWidget.HORIZONTAL);
109            ll.addView(tw, new LinearLayout.LayoutParams(
110                    ViewGroup.LayoutParams.FILL_PARENT,
111                    ViewGroup.LayoutParams.WRAP_CONTENT, 0));
112
113            FrameLayout fl = new FrameLayout(context);
114            fl.setId(android.R.id.tabcontent);
115            ll.addView(fl, new LinearLayout.LayoutParams(0, 0, 0));
116
117            mRealTabContent = fl = new FrameLayout(context);
118            mRealTabContent.setId(mContainerId);
119            ll.addView(fl, new LinearLayout.LayoutParams(
120                    LinearLayout.LayoutParams.FILL_PARENT, 0, 1));
121        }
122    }
123
124    /**
125     * @deprecated Don't call the original TabHost setup, you must instead
126     * call {@link #setup(Context, FragmentManager)} or
127     * {@link #setup(Context, FragmentManager, int)}.
128     */
129    @Override @Deprecated
130    public void setup() {
131        throw new IllegalStateException(
132                "Must call setup() that takes a Context and FragmentManager");
133    }
134
135    public void setup(Context context, FragmentManager manager) {
136        super.setup();
137        mContext = context;
138        mFragmentManager = manager;
139        ensureContent();
140    }
141
142    public void setup(Context context, FragmentManager manager, int containerId) {
143        super.setup();
144        mContext = context;
145        mFragmentManager = manager;
146        mContainerId = containerId;
147        ensureContent();
148        mRealTabContent.setId(containerId);
149    }
150
151    private void ensureContent() {
152        if (mRealTabContent == null) {
153            mRealTabContent = (FrameLayout)findViewById(mContainerId);
154            if (mRealTabContent == null) {
155                throw new IllegalStateException(
156                        "No tab content FrameLayout found for id " + mContainerId);
157            }
158        }
159    }
160
161    @Override
162    public void setOnTabChangedListener(OnTabChangeListener l) {
163        mOnTabChangeListener = l;
164    }
165
166    public void addTab(TabHost.TabSpec tabSpec, Class<?> clss, Bundle args) {
167        tabSpec.setContent(new DummyTabFactory(mContext));
168        String tag = tabSpec.getTag();
169
170        TabInfo info = new TabInfo(tag, clss, args);
171
172        // Check to see if we already have a fragment for this tab, probably
173        // from a previously saved state.  If so, deactivate it, because our
174        // initial state is that a tab isn't shown.
175        info.fragment = mFragmentManager.findFragmentByTag(tag);
176        if (info.fragment != null && !info.fragment.isDetached()) {
177            FragmentTransaction ft = mFragmentManager.beginTransaction();
178            ft.detach(info.fragment);
179            ft.commit();
180        }
181
182        mTabs.put(tag, info);
183        addTab(tabSpec);
184    }
185
186    @Override
187    public void onTabChanged(String tabId) {
188        TabInfo newTab = mTabs.get(tabId);
189        if (mLastTab != newTab) {
190            FragmentTransaction ft = mFragmentManager.beginTransaction();
191            if (mLastTab != null) {
192                if (mLastTab.fragment != null) {
193                    ft.detach(mLastTab.fragment);
194                }
195            }
196            if (newTab != null) {
197                if (newTab.fragment == null) {
198                    newTab.fragment = Fragment.instantiate(mContext,
199                            newTab.clss.getName(), newTab.args);
200                    ft.add(mContainerId, newTab.fragment, newTab.tag);
201                } else {
202                    ft.attach(newTab.fragment);
203                }
204            }
205
206            mLastTab = newTab;
207            ft.commit();
208            mFragmentManager.executePendingTransactions();
209        }
210        if (mOnTabChangeListener != null) {
211            mOnTabChangeListener.onTabChanged(tabId);
212        }
213    }
214}
215