Android FragmentTabHost分析
Android
WHATFragmentTabHost extends TabHost
基本功能继承了TabHost,Content使用Fragment展示。实现底部、顶部Tab,其他区域展示内容的效果。
// ContentView
privateFrameLayoutmRealTabContent;
privateContextmContext;
privateFragmentManagermFragmentManager;
// ContentView的ID
privateintmContainerId;
privateTabHost.OnTabChangeListenermOnTabChangeListener;
// 上次切换的Tab信息
privateTabInfomLastTab;
privatebooleanmAttached;
WHY
实现了TabHost.TabContentFactoryFactory
staticclassDummyTabFactoryimplementsTabHost.TabContentFactory{
privatefinalContextmContext;
publicDummyTabFactory(Contextcontext){
mContext=context;
}
@Override
publicViewcreateTabContent(Stringtag){
// 这个貌似没有用到,真实的ContentView是mRealTabContent,其ID就是mContainerId
Viewv=newView(mContext);
v.setMinimumWidth(0);
v.setMinimumHeight(0);
returnv;
}
}
初始化
privatevoidinitFragmentTabHost(Contextcontext,AttributeSetattrs){
finalTypedArraya=context.obtainStyledAttributes(attrs,
newint[]{android.R.attr.inflatedId},0,0);
// 指定inflatedId
mContainerId=a.getResourceId(0,0);
a.recycle();
super.setOnTabChangedListener(this);
}
确保有TabWidget和ConentView
// 自动生成ViewTree,上面是TabWidget,下面是Content
privatevoidensureHierarchy(Contextcontext){
// If owner hasn't made its own view hierarchy, then as a convenience
// we will construct a standard one here.
if(findViewById(android.R.id.tabs)==null){
LinearLayoutll=newLinearLayout(context);
ll.setOrientation(LinearLayout.VERTICAL);
addView(ll,newFrameLayout.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.MATCH_PARENT));
TabWidgettw=newTabWidget(context);
tw.setId(android.R.id.tabs);
tw.setOrientation(TabWidget.HORIZONTAL);
ll.addView(tw,newLinearLayout.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.WRAP_CONTENT,0));
FrameLayoutfl=newFrameLayout(context);
fl.setId(android.R.id.tabcontent);
ll.addView(fl,newLinearLayout.LayoutParams(0,0,0));
mRealTabContent=fl=newFrameLayout(context);
mRealTabContent.setId(mContainerId);
ll.addView(fl,newLinearLayout.LayoutParams(
LinearLayout.LayoutParams.MATCH_PARENT,0,1));
}
}
// 确保必须有指定的mContainerId
privatevoidensureContent(){
if(mRealTabContent==null){
mRealTabContent=(FrameLayout)findViewById(mContainerId);
if(mRealTabContent==null){
thrownewIllegalStateException(
"No tab content FrameLayout found for id "+mContainerId);
}
}
}
如果布局中的FragmentTabHost有指定android:inflatedId则使用这个方法初始化,一般是使用布局设置的情况。setup(Context context, FragmentManager manager)
或者指定一个containerId,一般用在java代码中写布局的情况。setup(Context context, FragmentManager manager, int containerId)
切换Tab,通过FragmentManager的事物来管理的
publicvoidonTabChanged(StringtabId){
if(mAttached){
finalFragmentTransactionft=doTabChanged(tabId,null);
if(ft!=null){
ft.commit();
}
}
if(mOnTabChangeListener!=null){
mOnTabChangeListener.onTabChanged(tabId);
}
}
@Nullable
privateFragmentTransactiondoTabChanged(@NullableStringtag,
@NullableFragmentTransactionft){
finalTabInfonewTab=getTabInfoForTag(tag);
if(mLastTab!=newTab){
if(ft==null){
ft=mFragmentManager.beginTransaction();
}
if(mLastTab!=null){
if(mLastTab.fragment!=null){
ft.detach(mLastTab.fragment);
}
}
if(newTab!=null){
if(newTab.fragment==null){
newTab.fragment=Fragment.instantiate(mContext,
newTab.clss.getName(),newTab.args);
ft.add(mContainerId,newTab.fragment,newTab.tag);
}else{
ft.attach(newTab.fragment);
}
}
mLastTab=newTab;
}
returnft;
}
每次新旧的切换是使用detach和attach,所以会造成Fragment的生命周期(onCreatedView)重复执行,导致每次都会重新加载布局等问题。
解决方案有2个:
1. 重写doTabChanged方法,使用FragmentTransaction的show/hide来切换显示、隐藏。
2. 在Fragment的onCreateView中判断这个rootView是否已经存在,不存在才加载View。
HOW TO USE
布局中使用,基本上和TabHost一样,只是把TabHost控件换成了FragmentTabHost
android:layout_width="match_parent"
android:layout_height="match_parent">
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
android:id="@android:id/tabcontent"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"/>
android:id="@android:id/tabs"
android:layout_width="match_parent"
android:layout_height="48dp"/>
在Java代码中设置
FragmentTabHosttabHost=(FragmentTabHost)findViewById(android.R.id.tabhost);
// 指定content id
// tabHost.setup(this, getSupportFragmentManager(), android.R.id.tabcontent);
// content id = xml 中设置的inflatedId
tabHost.setup(this,getSupportFragmentManager());
TabHost.TabSpectabSpec1=tabHost.newTabSpec("TabA").setIndicator("TabA");
TabHost.TabSpectabSpec2=tabHost.newTabSpec("TabB").setIndicator("TabB");
tabHost.addTab(tabSpec1,Tab1Fragment.class,null);
tabHost.addTab(tabSpec2,Tab2Fragment.class,null);