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 // If owner hasn't made its own view hierarchy, then as a convenience 140 // we will construct a standard one here. 141 if (findViewById(android.R.id.tabs) == null) { 142 LinearLayout ll = new LinearLayout(context); 143 ll.setOrientation(LinearLayout.VERTICAL); 144 addView(ll, new FrameLayout.LayoutParams( 145 ViewGroup.LayoutParams.FILL_PARENT, 146 ViewGroup.LayoutParams.FILL_PARENT)); 147 148 TabWidget tw = new TabWidget(context); 149 tw.setId(android.R.id.tabs); 150 tw.setOrientation(TabWidget.HORIZONTAL); 151 ll.addView(tw, new LinearLayout.LayoutParams( 152 ViewGroup.LayoutParams.FILL_PARENT, 153 ViewGroup.LayoutParams.WRAP_CONTENT, 0)); 154 155 FrameLayout fl = new FrameLayout(context); 156 fl.setId(android.R.id.tabcontent); 157 ll.addView(fl, new LinearLayout.LayoutParams(0, 0, 0)); 158 159 mRealTabContent = fl = new FrameLayout(context); 160 mRealTabContent.setId(mContainerId); 161 ll.addView(fl, new LinearLayout.LayoutParams( 162 LinearLayout.LayoutParams.FILL_PARENT, 0, 1)); 163 } 164 } 165 166 /** 167 * @deprecated Don't call the original TabHost setup, you must instead 168 * call {@link #setup(Context, FragmentManager)} or 169 * {@link #setup(Context, FragmentManager, int)}. 170 */ 171 @Override @Deprecated 172 public void setup() { 173 throw new IllegalStateException( 174 "Must call setup() that takes a Context and FragmentManager"); 175 } 176 177 public void setup(Context context, FragmentManager manager) { 178 super.setup(); 179 mContext = context; 180 mFragmentManager = manager; 181 ensureContent(); 182 } 183 184 public void setup(Context context, FragmentManager manager, int containerId) { 185 super.setup(); 186 mContext = context; 187 mFragmentManager = manager; 188 mContainerId = containerId; 189 ensureContent(); 190 mRealTabContent.setId(containerId); 191 192 // We must have an ID to be able to save/restore our state. If 193 // the owner hasn't set one at this point, we will set it ourself. 194 if (getId() == View.NO_ID) { 195 setId(android.R.id.tabhost); 196 } 197 } 198 199 private void ensureContent() { 200 if (mRealTabContent == null) { 201 mRealTabContent = (FrameLayout)findViewById(mContainerId); 202 if (mRealTabContent == null) { 203 throw new IllegalStateException( 204 "No tab content FrameLayout found for id " + mContainerId); 205 } 206 } 207 } 208 209 @Override 210 public void setOnTabChangedListener(OnTabChangeListener l) { 211 mOnTabChangeListener = l; 212 } 213 214 public void addTab(TabHost.TabSpec tabSpec, Class<?> clss, Bundle args) { 215 tabSpec.setContent(new DummyTabFactory(mContext)); 216 String tag = tabSpec.getTag(); 217 218 TabInfo info = new TabInfo(tag, clss, args); 219 220 if (mAttached) { 221 // If we are already attached to the window, then check to make 222 // sure this tab's fragment is inactive if it exists. This shouldn't 223 // normally happen. 224 info.fragment = mFragmentManager.findFragmentByTag(tag); 225 if (info.fragment != null && !info.fragment.isDetached()) { 226 FragmentTransaction ft = mFragmentManager.beginTransaction(); 227 ft.detach(info.fragment); 228 ft.commit(); 229 } 230 } 231 232 mTabs.add(info); 233 addTab(tabSpec); 234 } 235 236 @Override 237 protected void onAttachedToWindow() { 238 super.onAttachedToWindow(); 239 240 String currentTab = getCurrentTabTag(); 241 242 // Go through all tabs and make sure their fragments match 243 // the correct state. 244 FragmentTransaction ft = null; 245 for (int i=0; i<mTabs.size(); i++) { 246 TabInfo tab = mTabs.get(i); 247 tab.fragment = mFragmentManager.findFragmentByTag(tab.tag); 248 if (tab.fragment != null && !tab.fragment.isDetached()) { 249 if (tab.tag.equals(currentTab)) { 250 // The fragment for this tab is already there and 251 // active, and it is what we really want to have 252 // as the current tab. Nothing to do. 253 mLastTab = tab; 254 } else { 255 // This fragment was restored in the active state, 256 // but is not the current tab. Deactivate it. 257 if (ft == null) { 258 ft = mFragmentManager.beginTransaction(); 259 } 260 ft.detach(tab.fragment); 261 } 262 } 263 } 264 265 // We are now ready to go. Make sure we are switched to the 266 // correct tab. 267 mAttached = true; 268 ft = doTabChanged(currentTab, ft); 269 if (ft != null) { 270 ft.commit(); 271 mFragmentManager.executePendingTransactions(); 272 } 273 } 274 275 @Override 276 protected void onDetachedFromWindow() { 277 super.onDetachedFromWindow(); 278 mAttached = false; 279 } 280 281 @Override 282 protected Parcelable onSaveInstanceState() { 283 Parcelable superState = super.onSaveInstanceState(); 284 SavedState ss = new SavedState(superState); 285 ss.curTab = getCurrentTabTag(); 286 return ss; 287 } 288 289 @Override 290 protected void onRestoreInstanceState(Parcelable state) { 291 SavedState ss = (SavedState)state; 292 super.onRestoreInstanceState(ss.getSuperState()); 293 setCurrentTabByTag(ss.curTab); 294 } 295 296 @Override 297 public void onTabChanged(String tabId) { 298 if (mAttached) { 299 FragmentTransaction ft = doTabChanged(tabId, null); 300 if (ft != null) { 301 ft.commit(); 302 } 303 } 304 if (mOnTabChangeListener != null) { 305 mOnTabChangeListener.onTabChanged(tabId); 306 } 307 } 308 309 private FragmentTransaction doTabChanged(String tabId, FragmentTransaction ft) { 310 TabInfo newTab = null; 311 for (int i=0; i<mTabs.size(); i++) { 312 TabInfo tab = mTabs.get(i); 313 if (tab.tag.equals(tabId)) { 314 newTab = tab; 315 } 316 } 317 if (newTab == null) { 318 throw new IllegalStateException("No tab known for tag " + tabId); 319 } 320 if (mLastTab != newTab) { 321 if (ft == null) { 322 ft = mFragmentManager.beginTransaction(); 323 } 324 if (mLastTab != null) { 325 if (mLastTab.fragment != null) { 326 ft.detach(mLastTab.fragment); 327 } 328 } 329 if (newTab != null) { 330 if (newTab.fragment == null) { 331 newTab.fragment = Fragment.instantiate(mContext, 332 newTab.clss.getName(), newTab.args); 333 ft.add(mContainerId, newTab.fragment, newTab.tag); 334 } else { 335 ft.attach(newTab.fragment); 336 } 337 } 338 339 mLastTab = newTab; 340 } 341 return ft; 342 } 343} 344