70

ViewPager と独自の FragmentStatePagerAdapter で奇妙な動作が見られます。

私のビュー階層は次のようになります。

-> (1) Fragment root view (RelativeLayout)
 -> (2) ViewPager
  -> (3) ViewPager's current fragment view

Fragment ルート ビュー (1) を担当する Fragment が (フラグメント トランザクションで .hide() を使用して) 非表示になり、(.show() で) 表示されると、現在 ViewPager に表示されていたフラグメント ビュー (3 ) フラグメントはまだ存在しますが、null になります。基本的に、私の ViewPager は完全に空白/透明になります。

これを修正するために私が見つけた唯一の方法は、電話することです

int current = myViewPager.getCurrentItem();
myViewPager.setAdapter(myAdapter);
myViewPager.setCurrentItem(current);

親フラグメントが表示された後。これにより、何らかの方法でビューが再作成され、画面に表示されます。unregisterDataSetObserver()残念ながら、これにより、古いオブザーバーで 2 回呼び出されたページャー アダプターを処理する例外が発生することがあります。

これを行うより良い方法はありますか?私が求めているのは次のとおりだと思います:

ViewPager の親フラグメントが非表示になっていると、ViewPager 内のフラグメント ビューが破棄されるのはなぜですか?

更新:これは、アプリケーションが「最小化」されてから「復元」された場合にも発生します(ホームアクションキーを押してから戻ることにより)。

要求ごとに、これが私のページャー アダプター クラスです。

public class MyInfoSlidePagerAdapter extends FragmentStatePagerAdapter {

    private ArrayList<MyInfo> infos = new ArrayList<MyInfo>();

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

    public MyInfoSlidePagerAdapter (FragmentManager fm, MyInfo[] newInfos) {
        super(fm);
        setInfos(newInfos);
    }

    @Override
    public int getItemPosition(Object object) {
        int position = infos.indexOf(((MyInfoDetailsFragment)object).getMyInfo());
        return position > 0 ? position : POSITION_NONE;
    }

    @Override
    public CharSequence getPageTitle(int position) {
        return infos.get(position).getName();
    }

    @Override
    public Fragment getItem(int i) {
        return infos.size() > 0 ? MyInfoDetailsFragment.getNewInstance(infos.get(i)) : null;
    }

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

    public Location getMyInfoAtPosition(int i) {
        return infos.get(i);
    }

    public void setInfos(MyInfo[] newInfos) {
        infos = new ArrayList<MyInfo>(Arrays.asList(newInfos));
    }

    public int getPositionOfMyInfo(MyInfo info) {
        return infos.indexOf(info);
    }
}

いくつかの変数の名前を変更しましたが、それ以外はまさに私が持っているものです。

4

5 に答える 5

95

特定の問題について十分な情報を提供していないため、問題を再現しようとするサンプル プロジェクトを作成しました。アプリにはPagerFragment、相対レイアウト内にフラグメント () を保持するアクティビティがあり、このレイアウトの下に非表示のボタンがあります。 & は上記を示していますPagerFragmentPagerFragmentには があり、ViewPagerページャー アダプタ内の各フラグメントには単にラベルが表示されます。このフラグメントには という名前が付けられDataFragmentます。ラベル リストは親アクティビティで作成され、PagerFragment に渡され、そのアダプターを介して each に渡されますDataFragment。可視性の変更はPagerFragment問題なく行われ、再び可視になるたびに、以前に表示されたラベルが表示されます。

問題の鍵: viewpager アダプターを作成するときは、getFragmentManager ではなくFragment#getChildFragmentManager() を使用してください。

この単純なプロジェクトをあなたが持っているものと比較して、違いがどこにあるかを確認できるかもしれません。だからここに行きます(トップダウン):

PagerActivity (プロジェクト内の唯一のアクティビティ):

public class PagerActivity extends FragmentActivity {

    private static final String PAGER_TAG = "PagerActivity.PAGER_TAG";

    @Override
    protected void onCreate(Bundle savedInstance) {
        super.onCreate(savedInstance);
        setContentView(R.layout.pager_activity);
        if (savedInstance == null) {
            PagerFragment frag = PagerFragment.newInstance(buildPagerData());
            FragmentManager fm = getSupportFragmentManager();
            fm.beginTransaction().add(R.id.layout_fragments, frag, PAGER_TAG).commit();
        }
        findViewById(R.id.btnFragments).setOnClickListener(new OnClickListener() {

            @Override
            public void onClick(View v) {
                changeFragmentVisibility();
            }
        });
    }

    private List<String> buildPagerData() {
        ArrayList<String> pagerData = new ArrayList<String>();
        pagerData.add("Robert de Niro");
        pagerData.add("John Smith");
        pagerData.add("Valerie Irons");
        pagerData.add("Metallica");
        pagerData.add("Rammstein");
        pagerData.add("Zinedine Zidane");
        pagerData.add("Ronaldo da Lima");
        return pagerData;
    }

    protected void changeFragmentVisibility() {
        Fragment frag = getSupportFragmentManager().findFragmentByTag(PAGER_TAG);
        if (frag == null) {
            Toast.makeText(this, "No PAGER fragment found", Toast.LENGTH_SHORT).show();
            return;
        }
        boolean visible = frag.isVisible();
        Log.d("APSampler", "Pager fragment visibility: " + visible);
        FragmentTransaction ft = getSupportFragmentManager().beginTransaction();
        if (visible) {
            ft.hide(frag);
        } else {
            ft.show(frag);
        }
        ft.commit();
        getSupportFragmentManager().executePendingTransactions();
    }
}

そのレイアウト ファイルpager_activity.xml

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:padding="4dp" >

    <Button
        android:id="@+id/btnFragments"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentBottom="true"
        android:layout_centerHorizontal="true"
        android:text="Hide/Show fragments" />

    <RelativeLayout
        android:id="@+id/layout_fragments"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_above="@+id/btnFragments"
        android:layout_marginBottom="4dp" >
    </RelativeLayout>

</RelativeLayout>

PagerFragmentアクティビティが最初に表示されるときとPagerFragmentクラスを追加していることに注意してください。

public class PagerFragment extends Fragment {

    private static final String DATA_ARGS_KEY = "PagerFragment.DATA_ARGS_KEY";

    private List<String> data;

    private ViewPager pagerData;

    public static PagerFragment newInstance(List<String> data) {
        PagerFragment pagerFragment = new PagerFragment();
        Bundle args = new Bundle();
        ArrayList<String> argsValue = new ArrayList<String>(data);
        args.putStringArrayList(DATA_ARGS_KEY, argsValue);
        pagerFragment.setArguments(args);
        return pagerFragment;
    }

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        data = getArguments().getStringArrayList(DATA_ARGS_KEY);
    }

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

    @Override
    public void onViewCreated(View view, Bundle savedInstanceState) {
        pagerData = (ViewPager) view.findViewById(R.id.pager_data);
        setupPagerData();
    }

    private void setupPagerData() {
        PagerAdapter adapter = new LocalPagerAdapter(getChildFragmentManager(), data);
        pagerData.setAdapter(adapter);
    }

}

そのレイアウト (フルサイズの ViewPager のみ):

<android.support.v4.view.ViewPager xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/pager_data"
    android:layout_width="match_parent"
    android:layout_height="match_parent" />

およびそのアダプター:

public class LocalPagerAdapter extends FragmentStatePagerAdapter {
    private List<String> pagerData;

    public LocalPagerAdapter(FragmentManager fm, List<String> pagerData) {
        super(fm);
        this.pagerData = pagerData;
    }

    @Override
    public Fragment getItem(int position) {
        return DataFragment.newInstance(pagerData.get(position));
    }

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

このアダプターは、DataFragment各ページに対して以下を作成します。

public class DataFragment extends Fragment {
    private static final String DATA_ARG_KEY = "DataFragment.DATA_ARG_KEY";

    private String localData;

    public static DataFragment newInstance(String data) {
        DataFragment df = new DataFragment();
        Bundle args = new Bundle();
        args.putString(DATA_ARG_KEY, data);
        df.setArguments(args);
        return df;
    }

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        localData = getArguments().getString(DATA_ARG_KEY);
    }

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

    @Override
    public void onViewCreated(View view, Bundle savedInstanceState) {
        view.findViewById(R.id.btn_page_action).setOnClickListener(new OnClickListener() {

            @Override
            public void onClick(View v) {
                Toast.makeText(getActivity(), localData, Toast.LENGTH_SHORT).show();
            }
        });
        ((TextView) view.findViewById(R.id.txt_label)).setText(localData);
    }
}

DataFragmentのレイアウト:

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    android:padding="4dp" >

    <Button
        android:id="@+id/btn_page_action"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentBottom="true"
        android:layout_centerHorizontal="true"
        android:text="Interogate" />

    <TextView
        android:id="@+id/txt_label"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_centerInParent="true"
        android:textAppearance="?android:attr/textAppearanceLarge" />

</RelativeLayout>

コーディングを楽しんでください!

于 2013-09-17T09:05:26.773 に答える
7

View Pager は、フラグメントを常に最新の状態に保ち、フラグメントが使用されていないときにメモリを解放してパフォーマンスを最適化することに非常に固執しています。明らかに、これはモバイル システムで有効な有用​​な特性です。しかし、リソースのこの永続的な割り当て解除により、フラグメントはフォーカスを得るたびに作成されます。

mViewPager.setOffscreenPageLimit(NUMBEROFFRAGMENTSCREENS);

これがドキュメントです。

この古い投稿には、あなたの問題に対する興味深い解決策があります..参照してください

于 2013-09-18T21:33:20.557 に答える
1

私も同じ問題を抱えていました。私のアプリ (FragmentActivity) には、3 つのフラグメントを持つページャー (ViewPager) があります。フラグメント間をスワイプしている間、それらは常に破壊され、再作成されます。実際には機能上問題ありませんが(閉じていないカーソルを期待してください)、この質問についても疑問に思っていました。

ViewPager の動作を変更するための回避策があるかどうかはわかりませんが、構成オブジェクト (おそらく静的) を用意し、破棄する前にオブジェクトを構成オブジェクトに保存することをお勧めしmyViewPagerます。

public class App extends FragmentActivity {

    static MyData data;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
         data = (MyData) getLastCustomNonConfigurationInstance();
         if (data == null) {
             data = new MyData();
             data.savedViewPager = myViewPager;
         } else {
             myViewPager = data.savedViewPager;
        }
    }

    @Override
    public Object onRetainCustomNonConfigurationInstance() {
        Log.d("onRetainCustomNonConfigurationInstance", "Configuration call");
        return data;
    }
}

public class MyData {
    public ViewPager savedViewPager;
}

このようにして、破棄されないオブジェクトへの参照を保存できるため、それへの参照があり、すべての重要なオブジェクトをリロードできます。

私の提案がお役に立てば幸いです。

于 2013-09-12T08:08:51.417 に答える