【问题标题】:How to maintain fragment's state in the application如何在应用程序中维护片段的状态
【发布时间】:2013-10-12 07:00:39
【问题描述】:

片段在FragmentTabHost中显示时如何保持其状态?

感谢tutorial,我能够在我的应用程序中实现FragmentTabHost

我的目的是创建一个应用程序,其主要活动包含一些选项卡(在整个应用程序的顶部粘贴)。单击每个选项卡会在选项卡下方打开一个新片段。

问题是当我点击一个选项卡做某事,然后转到另一个选项卡打开一个新片段,然后返回到第一个选项卡 - 我在这里的更改没有得到维护。

流程:

我真的需要实现这个逻辑。如果我的方法是错误的,请提出替代方案。

谢谢

代码:

主要活动

public class FagTabHostMain extends FragmentActivity {
    FragmentTabHost mTabHost;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_fag_tab_host_main);

        mTabHost = (FragmentTabHost) findViewById(android.R.id.tabhost);
        mTabHost.setup(this, getSupportFragmentManager(), R.id.realtabcontent);

        mTabHost.addTab(mTabHost.newTabSpec("audio").setIndicator("Audio"),
                AudioContainerFragmentClass.class, null);
        mTabHost.addTab(mTabHost.newTabSpec("video").setIndicator("Video"),
                VideoContainerFragmentClass.class, null);

    }

    @Override
    public void onBackPressed() {
        boolean isPopFragment = false;
        String currentTabTag = mTabHost.getCurrentTabTag();
        if (currentTabTag.equals("audio")) {
            isPopFragment = ((AudioContainerFragmentClass) getSupportFragmentManager()
                    .findFragmentByTag("audio")).popFragment();
        } else if (currentTabTag.equals("video")) {
            isPopFragment = ((VideoContainerFragmentClass) getSupportFragmentManager()
                    .findFragmentByTag("video")).popFragment();
        }
        // Finish when no more fragments to show in back stack
        finish();
    }
}

主要活动布局

<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.app.FragmentTabHost
        android:id="@android:id/tabhost"
        android:layout_width="match_parent"
        android:layout_height="match_parent" >

        <!-- Not Using this one right now -->
        <FrameLayout
            android:id="@android:id/tabcontent"
            android:layout_width="0dip"
            android:layout_height="0dip"
            android:layout_weight="0" />

    </android.support.v4.app.FragmentTabHost>

    <FrameLayout
        android:id="@+id/realtabcontent"
        android:layout_width="match_parent"
        android:layout_height="0dip"
        android:layout_weight="1" />

</LinearLayout>

AudioContainerFragmentClass

public class AudioContainerFragmentClass extends Fragment implements
        OnClickListener {

    final String TAG = "AudioContainerFragmentClass";
    private Boolean mIsViewInitiated = false;
    private boolean addToBackStack = true;
    private Button bNextFragment;
    private LinearLayout linearLayout;

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
            Bundle savedInstanceState) {
        try {
            Log.e("AudioContainerFragmentClass", "onCreateView called");
            linearLayout = (LinearLayout) inflater.inflate(
                    R.layout.audio_fragment_container, container, false);
        } catch (Exception e) {
            printException(e.toString());
        }
        return linearLayout;
    }

    @Override
    public void onActivityCreated(Bundle savedInstanceState) {
        try {
            super.onActivityCreated(savedInstanceState);
            Log.e("AudioContainerFragmentClass", "onActivityCreated called");
            if (!mIsViewInitiated) {
                mIsViewInitiated = true;
                initView();
            }
        } catch (Exception e) {
            printException(e.toString());
        }
    }

    private void initView() {
        try {
            Log.e("AudioContainerFragmentClass", "initView called");
            bNextFragment = (Button) linearLayout
                    .findViewById(R.id.bNextFragment);
            bNextFragment.setOnClickListener(this);
            replaceFragment(new AudioFragment(), false);
        } catch (Exception e) {
            printException(e.toString());
        }
    }

    private void replaceFragment(AudioFragment audioFragment, boolean b) {
        try {
            FragmentTransaction ft = getChildFragmentManager()
                    .beginTransaction();
            if (addToBackStack) {
                ft.addToBackStack(null);
            }
            ft.replace(R.id.audio_sub_fragment, audioFragment);
            ft.commit();
            getChildFragmentManager().executePendingTransactions();
        } catch (Exception e) {
            printException(e.toString());
        }
    }

    // Called from FagTabHostMain Activity
    public boolean popFragment() {
        boolean isPop = false;
        try {
            Log.e("AudioContainerFragmentClass", "popFragment called");
            if (getChildFragmentManager().getBackStackEntryCount() > 0) {
                isPop = true;
                getChildFragmentManager().popBackStack();
            }
        } catch (Exception e) {
            printException(e.toString());
        }
        return isPop;
    }

    @Override
    public void onClick(View arg0) {
        TextView tv  = (TextView)getActivity().findViewById(R.id.tvaudioTitle);
        tv.setText("Text changed");
    }

    private void printException(String string) {
        Log.e("__ERRORR__", string);
    }
}

音频片段

public class AudioFragment extends Fragment {

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
            Bundle savedInstanceState) {
        View view = inflater.inflate(R.layout.audio_sub_fragment, container,
                false);
        return view;
    }
}

【问题讨论】:

标签: android android-layout android-fragments fragment-tab-host


【解决方案1】:

在我的应用中有同样的东西。 您需要将FragmentTabHost 复制到您的项目中,将您的代码指向使用新的自定义FragmentTabHost,然后将doTabChanged 的代码更改为以下实现:

    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 (newTab == null) {
        throw new IllegalStateException("No tab known for tag " + tabId);
    }
    if (mLastTab != newTab) {
        if (ft == null) {
            ft = mFragmentManager.beginTransaction();
        }
        if (mLastTab != null) {
            if (mLastTab.fragment != null) {
                ft.hide(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);
                findViewById(mContainerId).setContentDescription("DEBUG. add fragment to this container");
            } else {
                if (newTab.fragment.isHidden()){
                    ft.show(newTab.fragment);
                }
                else{
                    ft.attach(newTab.fragment); 
                }
            }
        }

        mPreviousTab = mLastTab;
        mLastTab = newTab;
    }
    return ft;
}

所做的更改是我们使用hide/show 代替片段而不是deattach/attach

【讨论】:

  • 不错的逻辑,让我试试。 mTabs 存储在第三行的值是多少。另外,您是否在某处发布了完整的 sn-p 以供参考。还有如何拨打doTabChanged
  • @raul8,完整的代码 sn-p 实际上是原始的FragmentTabHost,只有我在上面发布的更改。为了开始使用此代码,只需 1. 将整个代码复制到项目中的一个新类中(代码可以在这里找到:grepcode.com/file/repository.grepcode.com/java/ext/…) 2. 用我发布的方法代码替换方法代码。 3.在你的FagTabHostMain类中,为你的成员使用新的类`FragmentTabHost mTab​​Host;`
  • 感谢@Sean 的回答。但我猜你还没有把所有的代码都放在这里。因为在屏幕旋转时我有些头疼,因为您使用带有代码if (mLastTab != null) { if (mLastTab.fragment != null) { ft.hide(mLastTab.fragment); } } 的 mLastTab 隐藏了上一个选项卡。您应该在 onAttachedToWindow 中显示先前隐藏的选项卡,并在分离片段之前使用if(tab.fragment != null &amp;&amp; tab.fragment.isHidden()){ ft.show(tab.fragment); } 之类的代码。
  • FragmentTabHost.java 与我的库 android-support-v4-appcompat 发生冲突。我收到这样的错误:- 无法执行 dex:多个 dex 文件定义 Landroid/support/v4/app/FragmentTabHost$DummyTabFactory。我正在为这个项目使用 Eclipse。
  • 我认为您的代码没有正确更改,从最后开始的第四行代码计数是错误的。我认为应该将“附加”方法更改为“显示”方法
【解决方案2】:

我相信每次切换选项卡时都会重新实例化您的片段,这意味着您的字段变量会被重置。

您可能可以使用 saveInstance 包来管理片段的状态,但我发现使用 SharedPreferences 更有用且更简单。即使您的应用程序重新启动,这还具有保持保存状态的好处。

要读取和写入变量到 SharedPreferences,我使用了这个小助手类:

public class PreferencesData {

public static void saveString(Context context, String key, String value) {
    SharedPreferences sharedPrefs = PreferenceManager
            .getDefaultSharedPreferences(context);
    sharedPrefs.edit().putString(key, value).commit();
}

public static void saveInt(Context context, String key, int value) {
    SharedPreferences sharedPrefs = PreferenceManager
            .getDefaultSharedPreferences(context);
    sharedPrefs.edit().putInt(key, value).commit();
}


public static void saveBoolean(Context context, String key, boolean value) {
    SharedPreferences sharedPrefs = PreferenceManager
            .getDefaultSharedPreferences(context);
    sharedPrefs.edit().putBoolean(key, value).commit();
}

public static int getInt(Context context, String key, int defaultValue) {
    SharedPreferences sharedPrefs = PreferenceManager
            .getDefaultSharedPreferences(context);
    return sharedPrefs.getInt(key, defaultValue);
}

public static String getString(Context context, String key, String defaultValue) {
    SharedPreferences sharedPrefs = PreferenceManager
            .getDefaultSharedPreferences(context);
    return sharedPrefs.getString(key, defaultValue);
}

public static boolean getBoolean(Context context, String key, boolean defaultValue) {
    SharedPreferences sharedPrefs = PreferenceManager
            .getDefaultSharedPreferences(context);
    return sharedPrefs.getBoolean(key, defaultValue);
}
}

现在,作为示例,保存您的 mIsViewInitiated 变量,然后在 onPause 中:

@Override
protected void onPause() {
    PreferencesData.saveBoolean(this, "isViewInitiated", mIsViewInitiated);
    super.onPause();
}

然后再次检索它:

@Override
public void onActivityCreated(Bundle savedInstanceState) {
    try {
        super.onActivityCreated(savedInstanceState);
        Log.e("AudioContainerFragmentClass", "onActivityCreated called");
        // will now be true if onPause have been called
        mIsViewInitiated = PreferencesData.getBoolean(this, "isViewInitiated", false);
        if (!mIsViewInitiated) {
            mIsViewInitiated = true;
            initView();
        }
    } catch (Exception e) {
        printException(e.toString());
    }
}

由于此示例变量告诉是否已加载某些 UI,因此您可能希望在销毁 Activity 时将其设置为 false。

@Override
protected void onDestroy() {
    PreferencesData.saveBoolean(this, "isViewInitiated", false);
    super.onDestroy();
}

此答案只是一个选项,显示了我的个人喜好,而其他选项可能更适合您的情况。我建议看看http://developer.android.com/guide/topics/data/data-storage.html

【讨论】:

  • 您的回答非常好,但我试图避免共享首选项,因为要保留的数据质量很大。即使 Sean 提供的答案也需要进一步处理,但它确实为我提供了一个开始。谢谢
  • 没问题。由于我在这个方向上看不到任何答案,我建议也许查看保留 GoogleMap 片段的 API 演示示例。我认为您应该能够组合 setRetainInstance(true) 并保留对片段的引用,以便在选项卡更改时重新实例化正确的(保留的)片段。只是给你另一个选择。
【解决方案3】:

修改您的 Activity 以覆盖 onSaveInstanceState 和您的 onCreate 方法以从“savedInstanceState”恢复。

public static final String TAB_STATE = "TAB_STATE";

@Override
protected void onSaveInstanceState(Bundle outState) {
    outstate.putParcelable(TAB_STATE, mTabHost.onSaveInstanceState());
    super.onSaveInstanceState(outState);
}

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_fag_tab_host_main);

    mTabHost = (FragmentTabHost) findViewById(android.R.id.tabhost);
    mTabHost.setup(this, getSupportFragmentManager(), R.id.realtabcontent);
    if(savedInstanceState==null || savedInstanceState.getParcelable(TAB_STATE)==null){
    mTabHost.addTab(mTabHost.newTabSpec("audio").setIndicator("Audio"),
            AudioContainerFragmentClass.class, null);
    mTabHost.addTab(mTabHost.newTabSpec("video").setIndicator("Video"),
            VideoContainerFragmentClass.class, null);
    } else{
        mTabHost.onRestoreInstanceState(savedInstanceState.getParcelable(TAB_STATE));
    }

}

【讨论】:

  • 感谢您的回复,但无法在 onSaveInstanceState() 方法中调用 mTabHost.onSaveInstanceState()。这对你有用吗?
【解决方案4】:

如上所述,您可以通过 Bundle、Shared Preferences 或 SQLite db 保存然后恢复您的数据。您可能还想在您的Fragment 上致电setRetainInstance(true)。这将阻止您的片段被重复重新创建。

【讨论】:

  • 我尝试了这种方法但无法保留状态。可能是我做错了。能否举例说明一下?
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2010-12-13
  • 1970-01-01
  • 1970-01-01
  • 2018-12-03
  • 2014-09-01
相关资源
最近更新 更多