1. ホーム
  2. android

[解決済み] ViewPagerとフラグメント - フラグメントの状態を保存する正しい方法は何ですか?

2022-03-20 08:13:03

質問

フラグメントは、UIロジックをいくつかのモジュールに分離するのに非常に適しているようです。しかし ViewPager そのライフサイクルは、私にはまだ霧の中です。だから、Guruの考えがとても必要なのです。

編集

以下の解決策を参照してください;-)

スコープ

メインアクティビティには ViewPager というフラグメントがあります。これらのフラグメントは、他の(サブメイン)アクティビティのために少し異なるロジックを実装することができるので、フラグメントのデータはアクティビティ内のコールバックインターフェースを介して入力されます。そして、最初の起動ではすべてうまくいきますが、しかし!...

問題点

アクティビティが再作成されるとき(例えば、方向転換のとき)、そのアクティビティに含まれる ViewPager の断片があります。このコード(下にあります)では、アクティビティが作成されるたびに、新しい ViewPager フラグメントアダプタはフラグメントと同じですが(多分これが問題)、FragmentManagerはすでにこれらのフラグメントをすべてどこかに保存しており(どこに?)、それらのための再作成機構を開始します。そこで、再作成機構は、quot;old"フラグメントのonAttachやonCreateViewなどを、Activityの実装メソッドを介してデータを開始するための私のコールバックインターフェースコールを呼び出します。しかし、このメソッドは、ActivityのonCreateメソッドによって作成された、新しく作成されたフラグメントを指しています。

課題

私の使い方が悪いのかもしれませんが、Android 3 Proの本にもあまり載っていません。そこで お願い ありがとうございました。

コード

主な活動内容

public class DashboardActivity extends BasePagerActivity implements OnMessageListActionListener {

private MessagesFragment mMessagesFragment;

@Override
protected void onCreate(Bundle savedInstanceState) {
    Logger.d("Dash onCreate");
    super.onCreate(savedInstanceState);

    setContentView(R.layout.viewpager_container);
    new DefaultToolbar(this);

    // create fragments to use
    mMessagesFragment = new MessagesFragment();
    mStreamsFragment = new StreamsFragment();

    // set titles and fragments for view pager
    Map<String, Fragment> screens = new LinkedHashMap<String, Fragment>();
    screens.put(getApplicationContext().getString(R.string.dashboard_title_dumb), new DumbFragment());
    screens.put(getApplicationContext().getString(R.string.dashboard_title_messages), mMessagesFragment);

    // instantiate view pager via adapter
    mPager = (ViewPager) findViewById(R.id.viewpager_pager);
    mPagerAdapter = new BasePagerAdapter(screens, getSupportFragmentManager());
    mPager.setAdapter(mPagerAdapter);

    // set title indicator
    TitlePageIndicator indicator = (TitlePageIndicator) findViewById(R.id.viewpager_titles);
    indicator.setViewPager(mPager, 1);

}

/* set of fragments callback interface implementations */

@Override
public void onMessageInitialisation() {

    Logger.d("Dash onMessageInitialisation");
    if (mMessagesFragment != null)
        mMessagesFragment.loadLastMessages();
}

@Override
public void onMessageSelected(Message selectedMessage) {

    Intent intent = new Intent(this, StreamActivity.class);
    intent.putExtra(Message.class.getName(), selectedMessage);
    startActivity(intent);
}

BasePagerActivityの別名ヘルパー

public class BasePagerActivity extends FragmentActivity {

BasePagerAdapter mPagerAdapter;
ViewPager mPager;
}

アダプタ

public class BasePagerAdapter extends FragmentPagerAdapter implements TitleProvider {

private Map<String, Fragment> mScreens;

public BasePagerAdapter(Map<String, Fragment> screenMap, FragmentManager fm) {

    super(fm);
    this.mScreens = screenMap;
}

@Override
public Fragment getItem(int position) {

    return mScreens.values().toArray(new Fragment[mScreens.size()])[position];
}

@Override
public int getCount() {

    return mScreens.size();
}

@Override
public String getTitle(int position) {

    return mScreens.keySet().toArray(new String[mScreens.size()])[position];
}

// hack. we don't want to destroy our fragments and re-initiate them after
@Override
public void destroyItem(View container, int position, Object object) {

    // TODO Auto-generated method stub
}

}

フラグメント

public class MessagesFragment extends ListFragment {

private boolean mIsLastMessages;

private List<Message> mMessagesList;
private MessageArrayAdapter mAdapter;

private LoadMessagesTask mLoadMessagesTask;
private OnMessageListActionListener mListener;

// define callback interface
public interface OnMessageListActionListener {
    public void onMessageInitialisation();
    public void onMessageSelected(Message selectedMessage);
}

@Override
public void onAttach(Activity activity) {
    super.onAttach(activity);
    // setting callback
    mListener = (OnMessageListActionListener) activity;
    mIsLastMessages = activity instanceof DashboardActivity;

}

@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
    inflater.inflate(R.layout.fragment_listview, container);
    mProgressView = inflater.inflate(R.layout.listrow_progress, null);
    mEmptyView = inflater.inflate(R.layout.fragment_nodata, null);
    return super.onCreateView(inflater, container, savedInstanceState);
}

@Override
public void onActivityCreated(Bundle savedInstanceState) {
    super.onActivityCreated(savedInstanceState);

    // instantiate loading task
    mLoadMessagesTask = new LoadMessagesTask();

    // instantiate list of messages
    mMessagesList = new ArrayList<Message>();
    mAdapter = new MessageArrayAdapter(getActivity(), mMessagesList);
    setListAdapter(mAdapter);
}

@Override
public void onResume() {
    mListener.onMessageInitialisation();
    super.onResume();
}

public void onListItemClick(ListView l, View v, int position, long id) {
    Message selectedMessage = (Message) getListAdapter().getItem(position);
    mListener.onMessageSelected(selectedMessage);
    super.onListItemClick(l, v, position, id);
}

/* public methods to load messages from host acitivity, etc... */
}

解決方法

馬鹿な解決策は、(ホストアクティビティーの)onSaveInstanceState内でputFragmentを使用してフラグメントを保存し、onCreate内でgetFragmentを使用してフラグメントを取得することです。でも、そんなことをしてもダメなような気がするのですが...。以下のコードを見てください。

    @Override
protected void onSaveInstanceState(Bundle outState) {

    super.onSaveInstanceState(outState);
    getSupportFragmentManager()
            .putFragment(outState, MessagesFragment.class.getName(), mMessagesFragment);
}

protected void onCreate(Bundle savedInstanceState) {
    Logger.d("Dash onCreate");
    super.onCreate(savedInstanceState);

    ...
    // create fragments to use
    if (savedInstanceState != null) {
        mMessagesFragment = (MessagesFragment) getSupportFragmentManager().getFragment(
                savedInstanceState, MessagesFragment.class.getName());
                StreamsFragment.class.getName());
    }
    if (mMessagesFragment == null)
        mMessagesFragment = new MessagesFragment();
    ...
}

解決方法は?

このとき FragmentPagerAdapter はフラグメントを FragmentManager に追加する際、フラグメントが配置される特定の位置に基づいた特別なタグを使用します。 FragmentPagerAdapter.getItem(int position) は、その位置のフラグメントが存在しない場合にのみ呼び出されます。回転した後、Androidはこの特定の位置のフラグメントをすでに作成/保存していることに気づくので、単純に FragmentManager.findFragmentByTag() 新しいものを作るのではありません。このようなことは FragmentPagerAdapter そのため、フラグメントの初期化コードを getItem(int) メソッドを使用します。

を使用していなかったとしても FragmentPagerAdapter でいちいち新しいフラグメントを作成するのは良くない。 Activity.onCreate(Bundle) . お気づきのように、フラグメントをFragmentManagerに追加すると、ローテート後に再作成されるので、再度追加する必要はありません。このような操作は、フラグメントを扱う際によくあるエラーの原因となります。

フラグメントを扱うときの通常のアプローチはこうです。

protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);

    ...

    CustomFragment fragment;
    if (savedInstanceState != null) {
        fragment = (CustomFragment) getSupportFragmentManager().findFragmentByTag("customtag");
    } else {
        fragment = new CustomFragment();
        getSupportFragmentManager().beginTransaction().add(R.id.container, fragment, "customtag").commit(); 
    }

    ...

}

を使用する場合 FragmentPagerAdapter の場合、フラグメントの管理をアダプタに委ねることになるので、上記の手順を実行する必要はありません。デフォルトでは、現在の位置の前後にあるフラグメントをひとつずつプリロードするだけです (しかし、これは FragmentStatePagerAdapter ). これは ViewPager.setOffscreenPageLimit(int) . このため、アダプタ外のフラグメントのメソッドを直接呼び出すと、フラグメントが生きていない可能性があり、有効であることが保証されません。

長い話を短くすると、あなたの解決策である putFragment というのは、それほどおかしなことではなく、また、通常のフラグメントの使い方(上記)とさほど変わりはありません。なぜなら、フラグメントを追加するのはアダプターであって、あなた個人ではないからです。ただ、そのためには offscreenPageLimit は、必要なフラグメントを常に読み込むのに十分な高さであり、フラグメントの存在に依存しているためです。これは、ViewPagerの遅延ロード機能を回避しますが、あなたのアプリケーションに必要なものであると思われます。

もう一つの方法は FragmentPageAdapter.instantiateItem(View, int) を返す前に、スーパーコールから返されたフラグメントへの参照を保存します (フラグメントが既に存在する場合は、それを見つけるロジックがあります)。

のソースの一部をご覧ください。 FragmentPagerAdapter (短い)と ビューページャー (長い)。