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