自定义FragmentTabHost实现可控制是否保存fragment状态
2019獨角獸企業重金招聘Python工程師標準>>>
目前針對頁面總布局的切換,最主流的做法就是用狗哥官方出品的FragmentTabHost,這玩意想必大家都應該用過,也都應該了解它的一個特點:不保存Fragment的當前狀態。如果你在Fragment里面放了個listView,那么當用戶下拉了listView后,又切換了Fragment,再切回來的時候,會發現listView已不在之前的位置了。或者當切換Fragment后再切回來,會發現之前的數據沒了,還要從新曲加載。。
造成這個問題的主要原因是,谷狗在FragmentTabHost中,使用了下面這兩個方法:
FragmentTransaction ft = mFragmentManager.beginTransaction(); ft.attach(newTab.fragment) //從mFragmentManager返回棧中附加參數fragment ft.detach(mLastTab.fragment);//從mFragmentManager返回棧中分離參數fragment這兩個方法是把Fragment中的view從ViewTree上分離/附加,但是Fragment本身還是在的,下面是這兩個方法的執行流程:
detach()->onPause()->onStop()->onDestroyView()
attach()->onCreateView()->onActivityCreated()->onStart()->onResume()
可以看到每執行一次這兩個方法,相當于讓fragment重新走了一遍生命周期,這就是為什么fragment沒有保存狀態的原因了。
要想讓fragment保存狀態其實也很簡單,就是把attach()替換成show(),把detach()替換成hide(),就行了。那么問題來了,有些項目也需要讓fragment不保存狀態啊,我們是不是可以自定義一個FragmentTabHost,來實現可控的狀態保存呢?下面是我的代碼,可以拿來直接用了!
package com.william.fragmentTabHost;/*** Created by zhangwei on 2017/7/10.*/import android.content.Context; import android.content.res.TypedArray; import android.os.Bundle; import android.os.Parcel; import android.os.Parcelable; import android.support.annotation.NonNull; import android.support.annotation.Nullable; import android.support.v4.app.Fragment; import android.support.v4.app.FragmentManager; import android.support.v4.app.FragmentTransaction; import android.util.AttributeSet; import android.view.View; import android.view.ViewGroup; import android.widget.FrameLayout; import android.widget.LinearLayout; import android.widget.TabHost; import android.widget.TabWidget;import java.util.ArrayList; import java.util.List;public class FragmentTabHost extends TabHostimplements TabHost.OnTabChangeListener {private final ArrayList<TabInfo> mTabs = new ArrayList<>();private List<Boolean> isNeedToHides = new ArrayList<>();private FrameLayout mRealTabContent;private Context mContext;private FragmentManager mFragmentManager;private int mContainerId;private TabHost.OnTabChangeListener mOnTabChangeListener;private TabInfo mLastTab;private boolean mAttached;static final class TabInfo {final @NonNullString tag;final @NonNull Class<?> clss;final @NullableBundle args;Fragment fragment;TabInfo(@NonNull String _tag, @NonNull Class<?> _class, @Nullable Bundle _args) {tag = _tag;clss = _class;args = _args;}}static class DummyTabFactory implements TabHost.TabContentFactory {private final Context mContext;public DummyTabFactory(Context context) {mContext = context;}@Overridepublic View createTabContent(String tag) {View v = new View(mContext);v.setMinimumWidth(0);v.setMinimumHeight(0);return v;}}static class SavedState extends BaseSavedState {String curTab;SavedState(Parcelable superState) {super(superState);}SavedState(Parcel in) {super(in);curTab = in.readString();}@Overridepublic void writeToParcel(Parcel out, int flags) {super.writeToParcel(out, flags);out.writeString(curTab);}@Overridepublic String toString() {return "FragmentTabHost.SavedState{"+ Integer.toHexString(System.identityHashCode(this))+ " curTab=" + curTab + "}";}public static final Parcelable.Creator<SavedState> CREATOR= new Parcelable.Creator<SavedState>() {@Overridepublic SavedState createFromParcel(Parcel in) {return new SavedState(in);}@Overridepublic SavedState[] newArray(int size) {return new SavedState[size];}};}public FragmentTabHost(Context context) {// Note that we call through to the version that takes an AttributeSet,// because the simple Context construct can result in a broken object!super(context, null);initFragmentTabHost(context, null);}public FragmentTabHost(Context context, AttributeSet attrs) {super(context, attrs);initFragmentTabHost(context, attrs);}private void initFragmentTabHost(Context context, AttributeSet attrs) {final TypedArray a = context.obtainStyledAttributes(attrs,new int[] { android.R.attr.inflatedId }, 0, 0);mContainerId = a.getResourceId(0, 0);a.recycle();super.setOnTabChangedListener(this);}private void ensureHierarchy(Context context) {// 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) {LinearLayout ll = new LinearLayout(context);ll.setOrientation(LinearLayout.VERTICAL);addView(ll, new FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,ViewGroup.LayoutParams.MATCH_PARENT));TabWidget tw = new TabWidget(context);tw.setId(android.R.id.tabs);tw.setOrientation(TabWidget.HORIZONTAL);ll.addView(tw, new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,ViewGroup.LayoutParams.WRAP_CONTENT, 0));FrameLayout fl = new FrameLayout(context);fl.setId(android.R.id.tabcontent);ll.addView(fl, new LinearLayout.LayoutParams(0, 0, 0));mRealTabContent = fl = new FrameLayout(context);mRealTabContent.setId(mContainerId);ll.addView(fl, new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, 0, 1));}}/*** @deprecated Don't call the original TabHost setup, you must instead* call {@link #setup(Context, FragmentManager)} or* {@link #setup(Context, FragmentManager, int)}.*/@Override @Deprecatedpublic void setup() {throw new IllegalStateException("Must call setup() that takes a Context and FragmentManager");}public void setup(Context context, FragmentManager manager) {ensureHierarchy(context); // Ensure views required by super.setup()super.setup();mContext = context;mFragmentManager = manager;ensureContent();}public void setup(Context context, FragmentManager manager, int containerId) {ensureHierarchy(context); // Ensure views required by super.setup()super.setup();mContext = context;mFragmentManager = manager;mContainerId = containerId;ensureContent();mRealTabContent.setId(containerId);// We must have an ID to be able to save/restore our state. If// the owner hasn't set one at this point, we will set it ourselves.if (getId() == View.NO_ID) {setId(android.R.id.tabhost);}}private void ensureContent() {if (mRealTabContent == null) {mRealTabContent = (FrameLayout)findViewById(mContainerId);if (mRealTabContent == null) {throw new IllegalStateException("No tab content FrameLayout found for id " + mContainerId);}}}@Overridepublic void setOnTabChangedListener(OnTabChangeListener l) {mOnTabChangeListener = l;}/*** 添加TabSpec* @param tabSpec* @param clss* @param args* @param isNeedToHide 是隱藏還是出站,隱藏可保持fragment的狀態,出站可回收資源*/public void addTab(@NonNull TabHost.TabSpec tabSpec, @NonNull Class<?> clss,@Nullable Bundle args, @NonNull boolean isNeedToHide) {tabSpec.setContent(new DummyTabFactory(mContext));final String tag = tabSpec.getTag();final TabInfo info = new TabInfo(tag, clss, args);if (mAttached) {// 如果我們已經附加到頁面了, 那么要檢查一下這個tab的fragment,如果它已存在就要讓他變成閑置狀態.// 這通常是不應該發生的info.fragment = mFragmentManager.findFragmentByTag(tag);if (info.fragment != null ) {FragmentTransaction ft = mFragmentManager.beginTransaction();if (isNeedToHide && !info.fragment.isHidden()) {ft.hide(info.fragment);}if (!isNeedToHide && !info.fragment.isDetached()) {ft.detach(info.fragment);}ft.commit();}}this.isNeedToHides.add(isNeedToHide);mTabs.add(info);addTab(tabSpec);}@Overrideprotected void onAttachedToWindow() {super.onAttachedToWindow();final String currentTag = getCurrentTabTag();// Go through all tabs and make sure their fragments match// the correct state.FragmentTransaction ft = null;for (int i = 0, count = mTabs.size(); i < count; i++) {final TabInfo tab = mTabs.get(i);tab.fragment = mFragmentManager.findFragmentByTag(tab.tag);if (tab.fragment != null && !tab.fragment.isDetached() && !tab.fragment.isHidden()) {if (tab.tag.equals(currentTag)) {// 這個tab的fragment已經存在并且處于激活狀態, 并且我們實際上也想用它來做當前的tab. Nothing to do.mLastTab = tab;} else {//這個fragment是從激活狀態恢復的,但它并不是當前的tab,所以要禁用它if (ft == null) {ft = mFragmentManager.beginTransaction();}if (isNeedToHides.get(i)) {ft.hide(tab.fragment);} else {ft.detach(tab.fragment);}}}}// We are now ready to go. Make sure we are switched to the// correct tab.mAttached = true;ft = doTabChanged(currentTag, ft);if (ft != null) {ft.commit();mFragmentManager.executePendingTransactions();}}@Overrideprotected void onDetachedFromWindow() {super.onDetachedFromWindow();mAttached = false;}@Overrideprotected Parcelable onSaveInstanceState() {Parcelable superState = super.onSaveInstanceState();SavedState ss = new SavedState(superState);ss.curTab = getCurrentTabTag();return ss;}@Overrideprotected void onRestoreInstanceState(Parcelable state) {if (!(state instanceof SavedState)) {super.onRestoreInstanceState(state);return;}SavedState ss = (SavedState) state;super.onRestoreInstanceState(ss.getSuperState());setCurrentTabByTag(ss.curTab);}@Overridepublic void onTabChanged(String tabId) {if (mAttached) {final FragmentTransaction ft = doTabChanged(tabId, null);if (ft != null) {ft.commit();}}if (mOnTabChangeListener != null) {mOnTabChangeListener.onTabChanged(tabId);}}@Nullableprivate FragmentTransaction doTabChanged(@Nullable String tag,@Nullable FragmentTransaction ft) {final TabInfo newTab = getTabInfoForTag(tag);if (mLastTab != newTab) {if (ft == null) {ft = mFragmentManager.beginTransaction();}if (mLastTab != null) {if (mLastTab.fragment != null) {for (int i=0;i<this.mTabs.size();i++) {if (mLastTab.tag.equals(mTabs.get(i).tag)) {if (isNeedToHides.get(i)) {ft.hide(mLastTab.fragment);} else {ft.detach(mLastTab.fragment);}break;}}}}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 {for (int i=0;i<this.mTabs.size();i++) {if (tag.equals(mTabs.get(i).tag)) {if (isNeedToHides.get(i)) {ft.show(newTab.fragment);} else {ft.attach(newTab.fragment);}break;}}}}mLastTab = newTab;}return ft;}@Nullableprivate TabInfo getTabInfoForTag(String tabId) {for (int i = 0, count = mTabs.size(); i < count; i++) {final TabInfo tab = mTabs.get(i);if (tab.tag.equals(tabId)) {return tab;}}return null;} }使用方法也和谷狗的FragmentTabHost一樣,但是在創建tabSpec時,多了一個boolean類型的參數,表示這個fragment是顯示隱藏,還是分離附加。傳true表示用顯示隱藏,傳false表示用分離附加
this.tabhost.addTab(tabSpec, fragments[i], null,true);轉載于:https://my.oschina.net/JiangTun/blog/917733
總結
以上是生活随笔為你收集整理的自定义FragmentTabHost实现可控制是否保存fragment状态的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 堆和优先级队列
- 下一篇: 2D 游戏引擎 AlloyGameEng