505

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

編集

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

範囲

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

問題

アクティビティが再作成されると (方向の変更など)、ViewPagerのフラグメントも再作成されます。コード(以下に記載)は、アクティビティが作成されるたびに、ViewPagerフラグメントと同じ新しいフラグメントアダプターを作成しようとすることを示しています(おそらくこれが問題です)が、 FragmentManager にはこれらすべてのフラグメントがすでにどこかに保存されています(どこに?)それらのレクリエーションメカニズムを開始します。したがって、再作成メカニズムは、アクティビティの実装メソッドを介してデータを開始するためのコールバック インターフェイス呼び出しを使用して、「古い」フラグメントの onAttach、onCreateView などを呼び出します。ただし、このメソッドは、アクティビティの 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 で保存し、getFragment を介して onCreate 内に取得することです。しかし、私はまだ物事がそのように機能するべきではないという奇妙な感覚を持っています...以下のコードを参照してください:

    @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();
    ...
}
4

11 に答える 11

457

FragmentManagerにFragmentPagerAdapterフラグメントを追加すると、フラグメントが配置される特定の位置に基づいて特別なタグが使用されます。FragmentPagerAdapter.getItem(int position)その位置のフラグメントが存在しない場合にのみ呼び出されます。FragmentManager.findFragmentByTag()回転後、Android は、この特定の位置のフラグメントが既に作成/保存されていることに気付くため、新しいフラグメントを作成する代わりに、単純に で再接続を試みます。を使用すると、これらすべてが無料になります。そのため、メソッド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、フラグメント管理をアダプターに委譲するため、上記の手順を実行する必要はありません。デフォルトでは、現在の位置の前後に 1 つの Fragment のみをプリロードします (ただし、 を使用していない限り、フラグメントは破棄されませんFragmentStatePagerAdapter)。これはViewPager.setOffscreenPageLimit(int)によって制御されます。このため、アダプターの外部でフラグメントのメソッドを直接呼び出しても、有効であるとは限りません。

簡単に言えputFragmentば、後で参照を取得できるようにするために使用するソリューションはそれほどクレイジーではなく、とにかくフラグメントを使用する通常の方法 (上記) とそれほど違いはありません。フラグメントはアダプターによって追加され、個人的には追加されないため、それ以外の方法で参照を取得することは困難です。offscreenPageLimit存在することに依存しているため、必要なフラグメントを常にロードするのに十分な高さであることを確認してください。これは、ViewPager の遅延読み込み機能をバイパスしますが、アプリケーションに必要なもののようです。

もう 1 つの方法はFragmentPageAdapter.instantiateItem(View, int)、super 呼び出しから返されたフラグメントへの参照をオーバーライドしてから、それを返す前に保存することです (既に存在する場合は、フラグメントを見つけるロジックがあります)。

全体像については、FragmentPagerAdapter (短い) とViewPager (長い) のソースの一部を見てください。

于 2012-03-10T13:00:21.537 に答える
38

antonyt素晴らしい回答FragmentPageAdapter.instantiateItem(View, int)と、作成済みへの参照を保存するためのオーバーライドについての言及を拡張するソリューションを提供したいと思いFragmentsます。これにより、後で作業を行うことができます。これはFragmentStatePagerAdapter;でも動作するはずです。詳細については、注を参照してください。


の内部セットに依存しないFragmentsによって返されるへの参照を取得する方法の簡単な例を次に示します。重要なのは、 inではなくそこに参照をオーバーライドして保存することです。FragmentPagerAdaptertagsFragmentsinstantiateItem()getItem()

public class SomeActivity extends Activity {
    private FragmentA m1stFragment;
    private FragmentB m2ndFragment;

    // other code in your Activity...

    private class CustomPagerAdapter extends FragmentPagerAdapter {
        // other code in your custom FragmentPagerAdapter...

        public CustomPagerAdapter(FragmentManager fm) {
            super(fm);
        }

        @Override
        public Fragment getItem(int position) {
            // Do NOT try to save references to the Fragments in getItem(),
            // because getItem() is not always called. If the Fragment
            // was already created then it will be retrieved from the FragmentManger
            // and not here (i.e. getItem() won't be called again).
            switch (position) {
                case 0:
                    return new FragmentA();
                case 1:
                    return new FragmentB();
                default:
                    // This should never happen. Always account for each position above
                    return null;
            }
        }

        // Here we can finally safely save a reference to the created
        // Fragment, no matter where it came from (either getItem() or
        // FragmentManger). Simply save the returned Fragment from
        // super.instantiateItem() into an appropriate reference depending
        // on the ViewPager position.
        @Override
        public Object instantiateItem(ViewGroup container, int position) {
            Fragment createdFragment = (Fragment) super.instantiateItem(container, position);
            // save the appropriate reference depending on position
            switch (position) {
                case 0:
                    m1stFragment = (FragmentA) createdFragment;
                    break;
                case 1:
                    m2ndFragment = (FragmentB) createdFragment;
                    break;
            }
            return createdFragment;
        }
    }

    public void someMethod() {
        // do work on the referenced Fragments, but first check if they
        // even exist yet, otherwise you'll get an NPE.

        if (m1stFragment != null) {
            // m1stFragment.doWork();
        }

        if (m2ndFragment != null) {
            // m2ndFragment.doSomeWorkToo();
        }
    }
}

またはtags、クラス メンバー変数/への参照の代わりに作業したい場合は、同じ方法でセットをFragments取得することもできます。tagsFragmentPagerAdapterFragmentStatePagerAdaptertagsFragments

@Override
public Object instantiateItem(ViewGroup container, int position) {
    Fragment createdFragment = (Fragment) super.instantiateItem(container, position);
    // get the tags set by FragmentPagerAdapter
    switch (position) {
        case 0:
            String firstTag = createdFragment.getTag();
            break;
        case 1:
            String secondTag = createdFragment.getTag();
            break;
    }
    // ... save the tags somewhere so you can reference them later
    return createdFragment;
}

このメソッドは、内部tagセットの模倣に依存せずFragmentPagerAdapter、代わりにそれらを取得するために適切な API を使用することに注意してください。これtagにより、 の将来のバージョンで変更が加えられた場合SupportLibraryでも、安全を保つことができます。


の設計に応じて、Activity作業Fragmentsしようとしている がまだ存在する場合と存在しない場合があることを忘れないnullでください。そのため、参照を使用する前にチェックを行ってそのことを考慮する必要があります。

また、代わりにを使用している場合はFragmentStatePagerAdapter、 へのハード参照を保持したくありませんFragments。それらのハード参照が多数ある可能性があり、ハード参照はそれらを不必要にメモリに保持するからです。Fragment代わりに、参照を標準の変数ではなく変数に保存しWeakReferenceます。このような:

WeakReference<Fragment> m1stFragment = new WeakReference<Fragment>(createdFragment);
// ...and access them like so
Fragment firstFragment = m1stFragment.get();
if (firstFragment != null) {
    // reference hasn't been cleared yet; do work...
}
于 2015-03-26T20:22:44.660 に答える
19

あなたの質問に対する別の比較的簡単な解決策を見つけました。

FragmentPagerAdapter ソース コードからわかるように、によって管理されるフラグメントは、以下を使用して生成されたタグFragmentPagerAdapterの下に格納されます。FragmentManager

String tag="android:switcher:" + viewId + ":" + index;

viewIdです。はあなたのcontainer.getId()インスタンスです。はフラグメントの位置です。したがって、オブジェクト ID を次の場所に保存できます。containerViewPagerindexoutState

@Override
protected void onSaveInstanceState(Bundle outState) {
    super.onSaveInstanceState(outState);
    outState.putInt("viewpagerid" , mViewPager.getId() );
}

@Override
    protected void onCreate(Bundle savedInstanceState) {
    setContentView(R.layout.activity_main);
    if (savedInstanceState != null)
        viewpagerid=savedInstanceState.getInt("viewpagerid", -1 );  

    MyFragmentPagerAdapter titleAdapter = new MyFragmentPagerAdapter (getSupportFragmentManager() , this);        
    mViewPager = (ViewPager) findViewById(R.id.pager);
    if (viewpagerid != -1 ){
        mViewPager.setId(viewpagerid);
    }else{
        viewpagerid=mViewPager.getId();
    }
    mViewPager.setAdapter(titleAdapter);

このフラグメントと通信したい場合は、次FragmentManagerのように if from を取得できます。

getSupportFragmentManager().findFragmentByTag("android:switcher:" + viewpagerid + ":0")
于 2013-03-03T04:17:44.267 に答える
17

回答を検索するたびにこのスレッドにたどり着いたため、おそらくわずかに異なるケースの代替ソリューションを提供したいと思います。

私の場合 - ページを動的に作成/追加して ViewPager にスライドさせていますが、回転すると (onConfigurationChange)、もちろん OnCreate が再度呼び出されるため、新しいページになります。しかし、ローテーション前に作成されたすべてのページへの参照を保持したいと考えています。

問題 - 作成する各フラグメントに一意の識別子がないため、参照する唯一の方法は、ローテーション/構成の変更後に復元される参照を配列に保存することでした。

回避策 - 重要な概念は、アクティビティ (フラグメントを表示する) で既存のフラグメントへの参照の配列も管理することでした。これは、このアクティビティが onSaveInstanceState でバンドルを利用できるためです。

public class MainActivity extends FragmentActivity

このアクティビティ内で、開いているページを追跡するプライベート メンバーを宣言します。

private List<Fragment> retainedPages = new ArrayList<Fragment>();

これは onSaveInstanceState が呼び出されるたびに更新され、onCreate で復元されます

@Override
protected void onSaveInstanceState(Bundle outState) {
    retainedPages = _adapter.exportList();
    outState.putSerializable("retainedPages", (Serializable) retainedPages);
    super.onSaveInstanceState(outState);
}

...一度保存すると、取得できます...

@Override
public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);

    if (savedInstanceState != null) {
        retainedPages = (List<Fragment>) savedInstanceState.getSerializable("retainedPages");
    }
    _mViewPager = (CustomViewPager) findViewById(R.id.viewPager);
    _adapter = new ViewPagerAdapter(getApplicationContext(), getSupportFragmentManager());
    if (retainedPages.size() > 0) {
        _adapter.importList(retainedPages);
    }
    _mViewPager.setAdapter(_adapter);
    _mViewPager.setCurrentItem(_adapter.getCount()-1);
}

これらはメイン アクティビティに必要な変更だったので、これを機能させるには FragmentPagerAdapter 内のメンバーとメソッドが必要でした。

public class ViewPagerAdapter extends FragmentPagerAdapter

同一のコンストラクト (上記の MainActivity に示されている)

private List<Fragment> _pages = new ArrayList<Fragment>();

この同期 (上記の onSaveInstanceState で使用されている) は、特にメソッドによってサポートされています。

public List<Fragment> exportList() {
    return _pages;
}

public void importList(List<Fragment> savedPages) {
    _pages = savedPages;
}

そして最後に、フラグメントクラスで

public class CustomFragment extends Fragment

これらすべてを機能させるために、2 つの変更がありました。

public class CustomFragment extends Fragment implements Serializable

そして、これを onCreate に追加して、フラグメントが破棄されないようにします

setRetainInstance(true);

私はまだ Fragments と Android のライフ サイクルに頭を悩ませている最中なので、この方法には冗長性や非効率性がある可能性があることに注意してください。しかし、それは私にとってはうまくいき、私のようなケースを持つ他の人に役立つことを願っています.

于 2012-12-04T04:12:19.457 に答える
8

私のソリューションは非常に失礼ですが、うまくいきます:保持されたデータから動的に作成されたフラグメントであるため、PageAdapter呼び出す前にすべてのフラグメントを削除super.onSaveInstanceState()し、アクティビティの作成時にそれらを再作成するだけです:

@Override
protected void onSaveInstanceState(Bundle outState) {
    outState.putInt("viewpagerpos", mViewPager.getCurrentItem() );
    mSectionsPagerAdapter.removeAllfragments();
    super.onSaveInstanceState(outState);
}

でそれらを削除することはできませんonDestroy()。そうしないと、次の例外が発生します。

java.lang.IllegalStateException:後にこのアクションを実行できませんonSaveInstanceState

ページ アダプタのコードは次のとおりです。

public void removeAllfragments()
{
    if ( mFragmentList != null ) {
        for ( Fragment fragment : mFragmentList ) {
            mFm.beginTransaction().remove(fragment).commit();
        }
        mFragmentList.clear();
        notifyDataSetChanged();
    }
}

onCreate()現在のページのみを保存し、フラグメントが作成された後に復元します。

if (savedInstanceState != null)
    mViewPager.setCurrentItem( savedInstanceState.getInt("viewpagerpos", 0 ) );  
于 2013-04-17T09:51:12.943 に答える
4

それは何BasePagerAdapterですか?標準のページャー アダプターの 1 つを使用する必要があります。 または のいずれFragmentPagerAdapterかを使用FragmentStatePagerAdapterする必要があります。これは、必要なくなったフラグメントをViewPager保持するか (前者)、状態を保存し (後者)、必要に応じて再作成するかによって決まります。再び必要です。

ViewPager使用するためのサンプルコードはここにあります

FragmentManagerフレームワーク内の が状態を保存し、ページャーが作成したアクティブなフラグメントを復元するため、アクティビティ インスタンス全体のビュー ページャーでのフラグメントの管理が少し複雑であることは事実です。これが実際に意味することは、初期化時にアダプタが復元されたフラグメントに再接続することを確認する必要があるということです。FragmentPagerAdapterまたはのコードを見て、FragmentStatePagerAdapterこれがどのように行われるかを確認できます。

于 2011-11-04T07:07:29.323 に答える
0

このシンプルでエレガントなソリューションを思いつきました。アクティビティがフラグメントの作成を担当し、アダプターがそれらを提供するだけであると想定しています。

mFragmentsこれはアダプターのコードです ( Activity によって維持されるフラグメントのリストであることを除いて、ここで奇妙なことは何もありません)。

class MyFragmentPagerAdapter extends FragmentStatePagerAdapter {

    public MyFragmentPagerAdapter(FragmentManager fm) {
        super(fm);
    }

    @Override
    public Fragment getItem(int position) {
        return mFragments.get(position);
    }

    @Override
    public int getCount() {
        return mFragments.size();
    }

    @Override
    public int getItemPosition(Object object) {
        return POSITION_NONE;
    }

    @Override
    public CharSequence getPageTitle(int position) {
        TabFragment fragment = (TabFragment)mFragments.get(position);
        return fragment.getTitle();
    }
} 

このスレッドの全体的な問題は、「古い」フラグメントの参照を取得することなので、アクティビティの onCreate でこのコードを使用します。

    if (savedInstanceState!=null) {
        if (getSupportFragmentManager().getFragments()!=null) {
            for (Fragment fragment : getSupportFragmentManager().getFragments()) {
                mFragments.add(fragment);
            }
        }
    }

もちろん、必要に応じてこのコードをさらに微調整できます。たとえば、フラグメントが特定のクラスのインスタンスであることを確認できます。

于 2016-07-19T15:53:28.150 に答える
0

A bit different opinion instead of storing the Fragments yourself just leave it to the FragmentManager and when you need to do something with the fragments look for them in the FragmentManager:

//make sure you have the right FragmentManager 
//getSupportFragmentManager or getChildFragmentManager depending on what you are using to manage this stack of fragments
List<Fragment> fragments = fragmentManager.getFragments();
if(fragments != null) {
   int count = fragments.size();
   for (int x = 0; x < count; x++) {
       Fragment fragment = fragments.get(x);
       //check if this is the fragment we want, 
       //it may be some other inspection, tag etc.
       if (fragment instanceof MyFragment) {
           //do whatever we need to do with it
       }
   }
}

If you have a lot of Fragments and the cost of instanceof check may be not what you want, but it is good thing to have in mind that the FragmentManager already keeps account of Fragments.

于 2020-11-05T12:12:02.633 に答える
-32

追加:

   @SuppressLint("ValidFragment")

あなたのクラスの前に。

うまくいかない場合は、次のようにします。

@SuppressLint({ "ValidFragment", "HandlerLeak" })
于 2014-03-24T06:18:37.243 に答える