一、FragmentTabHost+Fragment
主界面布局
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<!--Fragment内容-->
<FrameLayout
android:id="@+id/fragment_container"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1">
</FrameLayout>
<!--分割线-->
<TextView
android:layout_width="match_parent"
android:layout_height="1dp"
android:background="#e3e3e3"/>
<!--Tab标签栏-->
<android.support.v4.app.FragmentTabHost
android:id="@+id/tabhost"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="bottom">
</android.support.v4.app.FragmentTabHost>
</LinearLayout>
主界面Activity
private static Class[] mHomeFragments = {ItemFragment2.class, ItemFragment.class, ItemFragment2.class, ItemFragment.class};
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_home_pager);
ButterKnife.bind(this);
for (; i < 4; i++) {
mFragmentTabHost.setup(this, getSupportFragmentManager(), R.id.tab_content);
<!--setIndicator定制TabView,可以自己添加点击事件,覆盖默认的点击事件,注意newTabSpec("" + i)标签要设置的不一样,否则不转换-->
TabHost.TabSpec tabSpec = mFragmentTabHost.newTabSpec("" + i).setIndicator(getCustomedTabView(i));
mFragmentTabHost.addTab(tabSpec, mHomeFragments[i], null);
}
}
CustomedTabView 布局
Fragment的实现
需要缓存View,否则每次都会调用onCreateView
public class ItemFragment2 extends Fragment {
@Nullable
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
return inflater.inflate(R.layout.fragment_2, container, false);
// 每次都新建View但是不新建ItemFragment2比较烦人
}
可能问题
使用FragmetnTabHost虽然不是每次都是新建Fragment,但是每次都会执行 onCreateView,如果想要不重复执行,需要自己添加逻辑,参考方法如下:
public class ItemFragment2 extends Fragment {
private SoftReference<View> mRootView;
@Nullable
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
if (mRootView == null || mRootView.get() == null) {
mRootView = new SoftReference<>(inflater.inflate(R.layout.fragment_2, container, false));
} else {
if (mRootView.get().getParent() != null) {
((ViewGroup) mRootView.get().getParent()).removeView(mRootView.get());
}
}
return mRootView.get();
}
不过这里要注意,一定要采取这种写法inflater.inflate(R.layout.fragment_2, container, false) ,不然有问题
注意事项 FragmentMangerImpl里mAdded跟mActive的区别
mAdded 是被添加到Container中去的,而mActive是全部参与的Fragment集合,mAdded是活着的,是被Add到container里面的,mActive却可能死掉,对于FragmentTabHost,只有一个Added,原因如下,change的时候,先detach再add或者attach,
void makeInactive(Fragment f) {
if (f.mIndex < 0) {
return;
}
if (DEBUG) Log.v(TAG, "Freeing fragment index " + f);
<!--这里会置空-->
mActive.set(f.mIndex, null);
if (mAvailIndices == null) {
mAvailIndices = new ArrayList<Integer>();
}
void makeActive(Fragment f) {
if (f.mIndex >= 0) {
return;
}
<!--如果有空位置,就用空位置,如果没有,就新建,一般都是用的,死一个,留一个坑-->
if (mAvailIndices == null || mAvailIndices.size() <= 0) {
if (mActive == null) {
mActive = new ArrayList<Fragment>();
}
f.setIndex(mActive.size(), mParent);
mActive.add(f);
} else {
f.setIndex(mAvailIndices.remove(mAvailIndices.size()-1), mParent);
mActive.set(f.mIndex, f);
}
private FragmentTransaction doTabChanged(String tabId, FragmentTransaction ft) {
TabInfo newTab = null;
for (int i=0; i<mTabs.size(); i++) {
TabInfo tab = mTabs.get(i);
if (tab.tag.equals(tabId)) {
newTab = tab;
}
}
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);
}
}
public void detachFragment(Fragment fragment, int transition, int transitionStyle) {
if (DEBUG) Log.v(TAG, "detach: " + fragment);
if (!fragment.mDetached) {
fragment.mDetached = true;
if (fragment.mAdded) {
// We are not already in back stack, so need to remove the fragment.
if (mAdded != null) {
if (DEBUG) Log.v(TAG, "remove from detach: " + fragment);
mAdded.remove(fragment);
}
if (fragment.mHasMenu && fragment.mMenuVisible) {
mNeedMenuInvalidate = true;
}
fragment.mAdded = false;
moveToState(fragment, Fragment.CREATED, transition, transitionStyle, false);
}
}
}
滑动的时候,先remove再add,因为可以知道删除那个,但是,通过setCurrent那种,如果不是临近的,就先add,再remove,因为可能都要remove ,如果size足够大,就不要再新建可用槽了。remove才会
缺点
- 不能滑动切换Page
- 么有预加载,切换后才会加载新的页面
- 自己处理onCreateView的重复创建逻辑
重建的问题 onRestoreInstanceState在onAttachedToWindow函数前执行,先restore设置好,再处理正确。
@Override
protected Parcelable onSaveInstanceState() {
Parcelable superState = super.onSaveInstanceState();
SavedState ss = new SavedState(superState);
ss.curTab = getCurrentTabTag();
return ss;
}
@Override
protected void onRestoreInstanceState(Parcelable state) {
SavedState ss = (SavedState)state;
super.onRestoreInstanceState(ss.getSuperState());
setCurrentTabByTag(ss.curTab);
}
二、FragmentTabHost+ViewPager+Fragment
主界面布局
其实布局很简单,就是ViewPager+FragmentTabHost
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<android.support.v4.view.ViewPager
android:id="@+id/viewpager"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1">
</android.support.v4.view.ViewPager>
<TextView
android:layout_width="match_parent"
android:layout_height="1dp"
android:background="#e3e3e3"/>
<android.support.v4.app.FragmentTabHost
android:id="@+id/tabhost"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="bottom">
</android.support.v4.app.FragmentTabHost>
</LinearLayout>
Activity实现
private static Class[] mHomeFragments = {ItemFragment2.class, ItemFragment.class, ItemFragment2.class, ItemFragment.class};
private static ArrayList<String> mTablist = new ArrayList<>();
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_home_tabhosst_viewpager);
ButterKnife.bind(this);
for (; i < 4; i++) {
mFragmentTabHost.setup(this, getSupportFragmentManager());
TabHost.TabSpec tabSpec = mFragmentTabHost.newTabSpec("" + i).setIndicator(getCustomedTabView(i));
mTablist.add("" + i);
// 这里需要,因为会有重建问题,后台杀死后,FragmentTabHost在OnAttachToWindow的时候回调用,但是仍然会调用Adapter的getItem
mFragmentTabHost.addTab(tabSpec, mHomeFragments[i], null);
mFragmentTabHost.setOnTabChangedListener(new TabHost.OnTabChangeListener() {
@Override
public void onTabChanged(String s) {
mViewPager.setCurrentItem(mTablist.indexOf(s));
}
});
}
mViewPager.setAdapter(new HomeAdapter(getSupportFragmentManager()));
mViewPager.addOnPageChangeListener(new ViewPager.OnPageChangeListener() {
@Override
public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
}
@Override
public void onPageSelected(int position) {
mFragmentTabHost.setCurrentTab(position);
}
@Override
public void onPageScrollStateChanged(int state) {
}
});
// 预加载开启
mViewPager.setOffscreenPageLimit(4);
Fragment的实现
这里不会重新调用onCreateView,所以可以不缓存View
public class ItemFragment2 extends Fragment {
@Nullable
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
return inflater.inflate(R.layout.fragment_2, container, false);
}
缺点
- 对于多层内嵌支持不好,需要自定义ViewPager处理嵌套滑动
- 可能会多生成额外外的Fragment ,因为默认attachtoWindow的时候,会生成相应的class 解决方式是自己定义,不做dotabchange类无意义的处理。
自定义FragmentTabHost解决生成额外的Fragment问题
删除doTabChange 删除onAttachToWindow
public class CustomedFragmentTabHost extends TabHost
implements TabHost.OnTabChangeListener {
。。。。
@Override
public void onTabChanged(String tabId) {
if (mOnTabChangeListener != null) {
mOnTabChangeListener.onTabChanged(tabId);
}
}
其他ViewPager问题
void dataSetChanged() {
// This method only gets called if our observer is attached, so mAdapter is non-null.
final int adapterCount = mAdapter.getCount();
mExpectedAdapterCount = adapterCount;
boolean needPopulate = mItems.size() < mOffscreenPageLimit * 2 + 1 &&
mItems.size() < adapterCount;
int newCurrItem = mCurItem;
boolean isUpdating = false;
for (int i = 0; i < mItems.size(); i++) {
final ItemInfo ii = mItems.get(i);
final int newPos = mAdapter.getItemPosition(ii.object);
if (newPos == PagerAdapter.POSITION_UNCHANGED) {
continue;
}
if (newPos == PagerAdapter.POSITION_NONE) {
mItems.remove(i);
i--;
if (!isUpdating) {
mAdapter.startUpdate(this);
isUpdating = true;
}
mAdapter.destroyItem(this, ii.position, ii.object);
needPopulate = true;
if (mCurItem == ii.position) {
// Keep the current item in the valid range
newCurrItem = Math.max(0, Math.min(mCurItem, adapterCount - 1));
needPopulate = true;
}
continue;
}
if (ii.position != newPos) {
if (ii.position == mCurItem) {
// Our current item changed position. Follow it.
newCurrItem = newPos;
}
ii.position = newPos;
needPopulate = true;
}
}
if (isUpdating) {
mAdapter.finishUpdate(this);
}
Collections.sort(mItems, COMPARATOR);
if (needPopulate) {
// Reset our known page widths; populate will recompute them.
final int childCount = getChildCount();
for (int i = 0; i < childCount; i++) {
final View child = getChildAt(i);
final LayoutParams lp = (LayoutParams) child.getLayoutParams();
if (!lp.isDecor) {
lp.widthFactor = 0.f;
}
}
setCurrentItemInternal(newCurrItem, false, true);
requestLayout();
}
}
三、ViewPager+SlidingTabLayout
FragmentTabHost必须在底部一排显示完全,否则会挤在一起,不支持滑动,此时可以用SlidingTabLayout代替
四、FragmentTabHost+Fragment+ViewPager+TabLayout双重嵌套
FragmentPagerAdapter与FragmentStatePagerAdapter区别
FragmentPagerAdapter的destroyItem采用detach而后者采用remove
@Override
public void destroyItem(ViewGroup container, int position, Object object) {
if (mCurTransaction == null) {
mCurTransaction = mFragmentManager.beginTransaction();
}
if (DEBUG) Log.v(TAG, "Detaching item #" + getItemId(position) + ": f=" + object
+ " v=" + ((Fragment)object).getView());
mCurTransaction.detach((Fragment)object);
}
@Override
public void destroyItem(ViewGroup container, int position, Object object) {
Fragment fragment = (Fragment) object;
if (mCurTransaction == null) {
mCurTransaction = mFragmentManager.beginTransaction();
}
if (DEBUG) Log.v(TAG, "Removing item #" + position + ": f=" + object
+ " v=" + ((Fragment)object).getView());
while (mSavedState.size() <= position) {
mSavedState.add(null);
}
mSavedState.set(position, fragment.isAdded()
? mFragmentManager.saveFragmentInstanceState(fragment) : null);
mFragments.set(position, null);
mCurTransaction.remove(fragment);
}
所以Fragmentpageradapter中viewpager id有用的
mCurTransaction.add(container.getId(), fragment,
makeFragmentName(container.getId(), itemId));
@Override
public Object instantiateItem(ViewGroup container, int position) {
if (mCurTransaction == null) {
mCurTransaction = mFragmentManager.beginTransaction();
}
final long itemId = getItemId(position);
// Do we already have this fragment?
String name = makeFragmentName(container.getId(), itemId);
Fragment fragment = mFragmentManager.findFragmentByTag(name);
if (fragment != null) {
if (DEBUG) Log.v(TAG, "Attaching item #" + itemId + ": f=" + fragment);
mCurTransaction.attach(fragment);
} else {
fragment = getItem(position);
if (DEBUG) Log.v(TAG, "Adding item #" + itemId + ": f=" + fragment);
mCurTransaction.add(container.getId(), fragment,
makeFragmentName(container.getId(), itemId));