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.ArrayList; 20 21import android.content.Context; 22import android.content.res.TypedArray; 23import android.os.Bundle; 24import android.os.Parcel; 25import android.os.Parcelable; 26import android.util.AttributeSet; 27import android.view.View; 28import android.view.ViewGroup; 29import android.widget.FrameLayout; 30import android.widget.LinearLayout; 31import android.widget.TabHost; 32import android.widget.TabWidget; 33 34/** 35 * Special TabHost that allows the use of {@link Fragment} objects for 36 * its tab content. When placing this in a view hierarchy, after inflating 37 * the hierarchy you must call {@link #setup(Context, FragmentManager, int)} 38 * to complete the initialization of the tab host. 39 * 40 * <p>Here is a simple example of using a FragmentTabHost in an Activity: 41 * 42 * {@sample development/samples/Support4Demos/src/com/example/android/supportv4/app/FragmentTabs.java 43 * complete} 44 * 45 * <p>This can also be used inside of a fragment through fragment nesting: 46 * 47 * {@sample development/samples/Support4Demos/src/com/example/android/supportv4/app/FragmentTabsFragmentSupport.java 48 * complete} 49 */ 50public class FragmentTabHost extends TabHost 51 implements TabHost.OnTabChangeListener { 52 private final ArrayList<TabInfo> mTabs = new ArrayList<TabInfo>(); 53 private FrameLayout mRealTabContent; 54 private Context mContext; 55 private FragmentManager mFragmentManager; 56 private int mContainerId; 57 private TabHost.OnTabChangeListener mOnTabChangeListener; 58 private TabInfo mLastTab; 59 private boolean mAttached; 60 61 static final class TabInfo { 62 private final String tag; 63 private final Class<?> clss; 64 private final Bundle args; 65 private Fragment fragment; 66 67 TabInfo(String _tag, Class<?> _class, Bundle _args) { 68 tag = _tag; 69 clss = _class; 70 args = _args; 71 } 72 } 73 74 static class DummyTabFactory implements TabHost.TabContentFactory { 75 private final Context mContext; 76 77 public DummyTabFactory(Context context) { 78 mContext = context; 79 } 80 81 @Override 82 public View createTabContent(String tag) { 83 View v = new View(mContext); 84 v.setMinimumWidth(0); 85 v.setMinimumHeight(0); 86 return v; 87 } 88 } 89 90 static class SavedState extends BaseSavedState { 91 String curTab; 92 93 SavedState(Parcelable superState) { 94 super(superState); 95 } 96 97 private SavedState(Parcel in) { 98 super(in); 99 curTab = in.readString(); 100 } 101 102 @Override 103 public void writeToParcel(Parcel out, int flags) { 104 super.writeToParcel(out, flags); 105 out.writeString(curTab); 106 } 107 108 @Override 109 public String toString() { 110 return "FragmentTabHost.SavedState{" 111 + Integer.toHexString(System.identityHashCode(this)) 112 + " curTab=" + curTab + "}"; 113 } 114 115 public static final Parcelable.Creator<SavedState> CREATOR 116 = new Parcelable.Creator<SavedState>() { 117 public SavedState createFromParcel(Parcel in) { 118 return new SavedState(in); 119 } 120 121 public SavedState[] newArray(int size) { 122 return new SavedState[size]; 123 } 124 }; 125 } 126 127 public FragmentTabHost(Context context) { 128 // Note that we call through to the version that takes an AttributeSet, 129 // because the simple Context construct can result in a broken object! 130 super(context, null); 131 initFragmentTabHost(context, null); 132 } 133 134 public FragmentTabHost(Context context, AttributeSet attrs) { 135 super(context, attrs); 136 initFragmentTabHost(context, attrs); 137 } 138 139 private void initFragmentTabHost(Context context, AttributeSet attrs) { 140 TypedArray a = context.obtainStyledAttributes(attrs, 141 new int[] { android.R.attr.inflatedId }, 0, 0); 142 mContainerId = a.getResourceId(0, 0); 143 a.recycle(); 144 145 super.setOnTabChangedListener(this); 146 147 // If owner hasn't made its own view hierarchy, then as a convenience 148 // we will construct a standard one here. 149 if (findViewById(android.R.id.tabs) == null) { 150 LinearLayout ll = new LinearLayout(context); 151 ll.setOrientation(LinearLayout.VERTICAL); 152 addView(ll, new FrameLayout.LayoutParams( 153 ViewGroup.LayoutParams.FILL_PARENT, 154 ViewGroup.LayoutParams.FILL_PARENT)); 155 156 TabWidget tw = new TabWidget(context); 157 tw.setId(android.R.id.tabs); 158 tw.setOrientation(TabWidget.HORIZONTAL); 159 ll.addView(tw, new LinearLayout.LayoutParams( 160 ViewGroup.LayoutParams.FILL_PARENT, 161 ViewGroup.LayoutParams.WRAP_CONTENT, 0)); 162 163 FrameLayout fl = new FrameLayout(context); 164 fl.setId(android.R.id.tabcontent); 165 ll.addView(fl, new LinearLayout.LayoutParams(0, 0, 0)); 166 167 mRealTabContent = fl = new FrameLayout(context); 168 mRealTabContent.setId(mContainerId); 169 ll.addView(fl, new LinearLayout.LayoutParams( 170 LinearLayout.LayoutParams.FILL_PARENT, 0, 1)); 171 } 172 } 173 174 /** 175 * @deprecated Don't call the original TabHost setup, you must instead 176 * call {@link #setup(Context, FragmentManager)} or 177 * {@link #setup(Context, FragmentManager, int)}. 178 */ 179 @Override @Deprecated 180 public void setup() { 181 throw new IllegalStateException( 182 "Must call setup() that takes a Context and FragmentManager"); 183 } 184 185 public void setup(Context context, FragmentManager manager) { 186 super.setup(); 187 mContext = context; 188 mFragmentManager = manager; 189 ensureContent(); 190 } 191 192 public void setup(Context context, FragmentManager manager, int containerId) { 193 super.setup(); 194 mContext = context; 195 mFragmentManager = manager; 196 mContainerId = containerId; 197 ensureContent(); 198 mRealTabContent.setId(containerId); 199 200 // We must have an ID to be able to save/restore our state. If 201 // the owner hasn't set one at this point, we will set it ourself. 202 if (getId() == View.NO_ID) { 203 setId(android.R.id.tabhost); 204 } 205 } 206 207 private void ensureContent() { 208 if (mRealTabContent == null) { 209 mRealTabContent = (FrameLayout)findViewById(mContainerId); 210 if (mRealTabContent == null) { 211 throw new IllegalStateException( 212 "No tab content FrameLayout found for id " + mContainerId); 213 } 214 } 215 } 216 217 @Override 218 public void setOnTabChangedListener(OnTabChangeListener l) { 219 mOnTabChangeListener = l; 220 } 221 222 public void addTab(TabHost.TabSpec tabSpec, Class<?> clss, Bundle args) { 223 tabSpec.setContent(new DummyTabFactory(mContext)); 224 String tag = tabSpec.getTag(); 225 226 TabInfo info = new TabInfo(tag, clss, args); 227 228 if (mAttached) { 229 // If we are already attached to the window, then check to make 230 // sure this tab's fragment is inactive if it exists. This shouldn't 231 // normally happen. 232 info.fragment = mFragmentManager.findFragmentByTag(tag); 233 if (info.fragment != null && !info.fragment.isDetached()) { 234 FragmentTransaction ft = mFragmentManager.beginTransaction(); 235 ft.detach(info.fragment); 236 ft.commit(); 237 } 238 } 239 240 mTabs.add(info); 241 addTab(tabSpec); 242 } 243 244 @Override 245 protected void onAttachedToWindow() { 246 super.onAttachedToWindow(); 247 248 String currentTab = getCurrentTabTag(); 249 250 // Go through all tabs and make sure their fragments match 251 // the correct state. 252 FragmentTransaction ft = null; 253 for (int i=0; i<mTabs.size(); i++) { 254 TabInfo tab = mTabs.get(i); 255 tab.fragment = mFragmentManager.findFragmentByTag(tab.tag); 256 if (tab.fragment != null && !tab.fragment.isDetached()) { 257 if (tab.tag.equals(currentTab)) { 258 // The fragment for this tab is already there and 259 // active, and it is what we really want to have 260 // as the current tab. Nothing to do. 261 mLastTab = tab; 262 } else { 263 // This fragment was restored in the active state, 264 // but is not the current tab. Deactivate it. 265 if (ft == null) { 266 ft = mFragmentManager.beginTransaction(); 267 } 268 ft.detach(tab.fragment); 269 } 270 } 271 } 272 273 // We are now ready to go. Make sure we are switched to the 274 // correct tab. 275 mAttached = true; 276 ft = doTabChanged(currentTab, ft); 277 if (ft != null) { 278 ft.commit(); 279 mFragmentManager.executePendingTransactions(); 280 } 281 } 282 283 @Override 284 protected void onDetachedFromWindow() { 285 super.onDetachedFromWindow(); 286 mAttached = false; 287 } 288 289 @Override 290 protected Parcelable onSaveInstanceState() { 291 Parcelable superState = super.onSaveInstanceState(); 292 SavedState ss = new SavedState(superState); 293 ss.curTab = getCurrentTabTag(); 294 return ss; 295 } 296 297 @Override 298 protected void onRestoreInstanceState(Parcelable state) { 299 SavedState ss = (SavedState)state; 300 super.onRestoreInstanceState(ss.getSuperState()); 301 setCurrentTabByTag(ss.curTab); 302 } 303 304 @Override 305 public void onTabChanged(String tabId) { 306 if (mAttached) { 307 FragmentTransaction ft = doTabChanged(tabId, null); 308 if (ft != null) { 309 ft.commit(); 310 } 311 } 312 if (mOnTabChangeListener != null) { 313 mOnTabChangeListener.onTabChanged(tabId); 314 } 315 } 316 317 private FragmentTransaction doTabChanged(String tabId, FragmentTransaction ft) { 318 TabInfo newTab = null; 319 for (int i=0; i<mTabs.size(); i++) { 320 TabInfo tab = mTabs.get(i); 321 if (tab.tag.equals(tabId)) { 322 newTab = tab; 323 } 324 } 325 if (newTab == null) { 326 throw new IllegalStateException("No tab known for tag " + tabId); 327 } 328 if (mLastTab != newTab) { 329 if (ft == null) { 330 ft = mFragmentManager.beginTransaction(); 331 } 332 if (mLastTab != null) { 333 if (mLastTab.fragment != null) { 334 ft.detach(mLastTab.fragment); 335 } 336 } 337 if (newTab != null) { 338 if (newTab.fragment == null) { 339 newTab.fragment = Fragment.instantiate(mContext, 340 newTab.clss.getName(), newTab.args); 341 ft.add(mContainerId, newTab.fragment, newTab.tag); 342 } else { 343 ft.attach(newTab.fragment); 344 } 345 } 346 347 mLastTab = newTab; 348 } 349 return ft; 350 } 351} 352