support FragmentPagerAdapter menyimpan referensi ke fragmen lama

109

INFO TERBARU:

saya telah mempersempit masalah saya menjadi masalah dengan fragmentManager yang mempertahankan contoh fragmen lama dan viewpager saya tidak sinkron dengan FragmentManager saya. Lihat masalah ini ... http://code.google.com/p/android/issues/detail?id=19211#makechanges . Saya masih tidak tahu bagaimana menyelesaikan ini. Ada saran ...

Saya telah mencoba men-debug ini untuk waktu yang lama dan bantuan apa pun akan sangat kami hargai. Saya menggunakan FragmentPagerAdapter yang menerima daftar fragmen seperti ini:

List<Fragment> fragments = new Vector<Fragment>();
fragments.add(Fragment.instantiate(this, Fragment1.class.getName())); 
...
new PagerAdapter(getSupportFragmentManager(), fragments);

Implementasinya standar. Saya menggunakan pustaka komputasi ActionBarSherlock dan v4 untuk Fragmen.

Masalah saya adalah bahwa setelah meninggalkan aplikasi dan membuka beberapa aplikasi lain dan kembali, fragmen kehilangan referensi kembali ke FragmentActivity (mis. getActivity() == null). Saya tidak tahu mengapa ini terjadi. Saya mencoba mengatur secara manual setRetainInstance(true);tetapi ini tidak membantu. Saya membayangkan bahwa ini terjadi ketika FragmentActivity saya dihancurkan, namun ini masih terjadi jika saya membuka aplikasi sebelum saya mendapatkan pesan log. Apakah ada ide?

@Override
protected void onDestroy(){
    Log.w(TAG, "DESTROYDESTROYDESTROYDESTROYDESTROYDESTROYDESTROY");
    super.onDestroy();
}

Adaptor:

public class PagerAdapter extends FragmentPagerAdapter {
    private List<Fragment> fragments;

    public PagerAdapter(FragmentManager fm, List<Fragment> fragments) {
        super(fm);

        this.fragments = fragments;

    }

    @Override
    public Fragment getItem(int position) {

        return this.fragments.get(position);

    }

    @Override
    public int getCount() {

        return this.fragments.size();

    }

}

Salah satu Fragmen saya dilucuti tetapi saya membayar semuanya itu dilucuti dan masih tidak berfungsi ...

public class MyFragment extends Fragment implements MyFragmentInterface, OnScrollListener {
...

@Override
public void onCreate(Bundle savedInstanceState){
    super.onCreate(savedInstanceState);
    handler = new Handler();    
    setHasOptionsMenu(true);
}

@Override
public void onAttach(Activity activity) {
    super.onAttach(activity);
    Log.w(TAG,"ATTACHATTACHATTACHATTACHATTACH");
    context = activity;
    if(context== null){
        Log.e("IS NULL", "NULLNULLNULLNULLNULLNULLNULLNULLNULLNULLNULL");
    }else{
        Log.d("IS NOT NULL", "NOTNOTNOTNOTNOTNOTNOTNOT");
    }

}

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

@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
    View v = inflater.inflate(R.layout.my_fragment,container, false);

    return v;
}


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

private void callService(){
    // do not call another service is already running
    if(startLoad || !canSet) return;
    // set flag
    startLoad = true;
    canSet = false;
    // show the bottom spinner
    addFooter();
    Intent intent = new Intent(context, MyService.class);
    intent.putExtra(MyService.STATUS_RECEIVER, resultReceiver);
    context.startService(intent);
}

private ResultReceiver resultReceiver = new ResultReceiver(null) {
    @Override
    protected void onReceiveResult(int resultCode, final Bundle resultData) {
        boolean isSet = false;
        if(resultData!=null)
        if(resultData.containsKey(MyService.STATUS_FINISHED_GET)){
            if(resultData.getBoolean(MyService.STATUS_FINISHED_GET)){
                removeFooter();
                startLoad = false;
                isSet = true;
            }
        }

        switch(resultCode){
        case MyService.STATUS_FINISHED: 
            stopSpinning();
            break;
        case SyncService.STATUS_RUNNING:
            break;
        case SyncService.STATUS_ERROR:
            break;
        }
    }
};

public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
    menu.clear();
    inflater.inflate(R.menu.activity, menu);
}

@Override
public void onPause(){
    super.onPause();
}

public void onScroll(AbsListView arg0, int firstVisible, int visibleCount, int totalCount) {
    boolean loadMore = /* maybe add a padding */
        firstVisible + visibleCount >= totalCount;

    boolean away = firstVisible+ visibleCount <= totalCount - visibleCount;

    if(away){
        // startLoad can now be set again
        canSet = true;
    }

    if(loadMore) 

}

public void onScrollStateChanged(AbsListView arg0, int state) {
    switch(state){
    case OnScrollListener.SCROLL_STATE_FLING: 
        adapter.setLoad(false); 
        lastState = OnScrollListener.SCROLL_STATE_FLING;
        break;
    case OnScrollListener.SCROLL_STATE_IDLE: 
        adapter.setLoad(true);
        if(lastState == SCROLL_STATE_FLING){
            // load the images on screen
        }

        lastState = OnScrollListener.SCROLL_STATE_IDLE;
        break;
    case OnScrollListener.SCROLL_STATE_TOUCH_SCROLL:
        adapter.setLoad(true);
        if(lastState == SCROLL_STATE_FLING){
            // load the images on screen
        }

        lastState = OnScrollListener.SCROLL_STATE_TOUCH_SCROLL;
        break;
    }
}

@Override
public void onDetach(){
    super.onDetach();
    if(this.adapter!=null)
        this.adapter.clearContext();

    Log.w(TAG, "DETACHEDDETACHEDDETACHEDDETACHEDDETACHEDDETACHED");
}

public void update(final int id, String name) {
    if(name!=null){
        getActivity().getSupportActionBar().setTitle(name);
    }

}

}

Metode update dipanggil saat pengguna berinteraksi dengan fragmen berbeda dan getActivity menampilkan null. Inilah metode yang dipanggil oleh fragmen lain ...

((MyFragment) pagerAdapter.getItem(1)).update(id, name);

Saya percaya bahwa ketika aplikasi dihancurkan kemudian dibuat lagi alih-alih hanya memulai aplikasi ke fragmen default, aplikasi dimulai dan kemudian viewpager menavigasi ke halaman terakhir yang diketahui. Ini tampak aneh, bukankah seharusnya aplikasi memuat ke fragmen default?

Maurycy
sumber
27
Fragmen Android payah!
Hamidreza Sadegh
13
Tuhan, aku suka log pesanmu: D
oli.G

Jawaban:

120

Anda mengalami masalah karena Anda membuat instance dan menyimpan referensi ke fragmen Anda di luar PagerAdapter.getItem, dan mencoba menggunakan referensi tersebut secara terpisah dari ViewPager. Seperti yang dikatakan Seraph, Anda memiliki jaminan bahwa sebuah fragmen telah dibuat / ditambahkan di ViewPager pada waktu tertentu - ini harus dianggap sebagai detail implementasi. ViewPager lambat memuat halamannya; secara default hanya memuat halaman saat ini, dan satu ke kiri dan kanan.

Jika Anda meletakkan aplikasi Anda di latar belakang, fragmen yang telah ditambahkan ke pengelola fragmen akan disimpan secara otomatis. Meskipun aplikasi Anda mati, informasi ini dipulihkan saat Anda meluncurkan kembali aplikasi Anda.

Sekarang pertimbangkan bahwa Anda telah melihat beberapa halaman, Fragmen A, B dan C. Anda tahu bahwa ini telah ditambahkan ke pengelola fragmen. Karena Anda sedang menggunakan FragmentPagerAdapterdan tidak FragmentStatePagerAdapter, fragmen ini masih akan ditambahkan (tetapi berpotensi terlepas) saat Anda menggulir ke halaman lain.

Pertimbangkan bahwa Anda kemudian membuat latar belakang aplikasi Anda, dan kemudian terhenti. Saat Anda kembali, Android akan mengingat bahwa Anda dulu memiliki Fragmen A, B, dan C di pengelola fragmen sehingga ia membuatnya kembali untuk Anda dan kemudian menambahkannya. Namun, yang ditambahkan ke pengelola fragmen sekarang BUKAN yang Anda miliki di daftar fragmen di Aktivitas Anda.

FragmentPagerAdapter tidak akan mencoba memanggil getPositionjika sudah ada fragmen yang ditambahkan untuk posisi halaman tersebut. Faktanya, karena fragmen yang dibuat ulang oleh Android tidak akan pernah dihapus, Anda tidak memiliki harapan untuk menggantinya dengan panggilan ke getPosition. Mendapatkan pegangan di atasnya juga cukup sulit untuk mendapatkan referensi ke sana karena ditambahkan dengan tag yang tidak Anda kenal. Ini adalah desain; Anda tidak disarankan untuk mengotak-atik fragmen yang dikelola view pager. Anda harus melakukan semua tindakan Anda dalam sebuah fragmen, berkomunikasi dengan aktivitas tersebut, dan meminta untuk beralih ke halaman tertentu, jika perlu.

Sekarang, kembali ke masalah Anda dengan aktivitas yang hilang. Memanggil pagerAdapter.getItem(1)).update(id, name)setelah semua ini terjadi akan mengembalikan Anda fragmen dalam daftar Anda, yang belum ditambahkan ke pengelola fragmen , sehingga tidak akan memiliki referensi Aktivitas. Saya menyarankan metode pembaruan Anda harus memodifikasi beberapa struktur data bersama (mungkin dikelola oleh aktivitas), dan kemudian ketika Anda pindah ke halaman tertentu, metode ini dapat menggambar sendiri berdasarkan data yang diperbarui ini.

antonyt
sumber
1
Saya suka solusi Anda karena sangat elegan dan kemungkinan akan mengubah kode saya, namun seperti yang Anda katakan, saya ingin menyimpan 100% logika dan data di dalam fragmen. Solusi Anda akan membutuhkan cukup banyak Menyimpan semua data di FragmentActivity dan kemudian menggunakan setiap Fragmen hanya untuk menangani Logika tampilan. Seperti yang saya katakan, saya lebih suka ini tetapi interaksi antar fragmen sangat berat dan mungkin menjengkelkan untuk dikelola. Anyways Terima kasih atas penjelasan rinci Anda. Anda menjelaskan ini jauh lebih baik daripada saya.
Maurycy
28
Singkat: tidak pernah memiliki referensi ke Fragmen di luar Adaptor
lulus
2
Jadi, bagaimana instance Activity mengakses instance FragmentPagerAdapter jika tidak membuat instance FragmentPagerAdapter? Tidakkah instance di masa mendatang hanya akan memulihkan FragmentPagerAdapter dan semua instance fragmennya? Haruskah FragmentPagerAdapter menerapkan semua antarmuka fragmen untuk mengelola komunikasi antar fragmen?
Eric H.
pernyataan "Mendapatkan pegangan di atasnya juga cukup sulit untuk mendapatkan referensi ke sana karena ia ditambahkan dengan tag yang tidak Anda kenal. Ini memang sengaja; Anda tidak disarankan untuk mengotak-atik fragmen yang dikelola view pager . " salah. sangat mudah untuk mendapatkan referensi dengan menelepon instantiateItemdan Anda seharusnya melakukannya dalam onCreateaktivitas Anda. lihat detailnya di sini: stackoverflow.com/questions/14035090/…
morgwai
secara umum, cerdik untuk membuat sendiri fragmen tanpa memuat karena dalam skenario "hancurkan dan buat ulang", Anda mungkin akan menangani fragmen terpisah dengan yang sebenarnya dibuat ulang dan ditampilkan
hmac
108

Saya menemukan solusi sederhana yang berhasil untuk saya.

Jadikan adaptor fragmen Anda memperluas FragmentStatePagerAdapter sebagai ganti FragmentPagerAdapter dan ganti metode onSave untuk mengembalikan null

@Override
public Parcelable saveState()
{
    return null;
}

Ini mencegah android membuat ulang fragmen


Suatu hari kemudian saya menemukan solusi lain yang lebih baik.

Panggil setRetainInstance(true)semua fragmen Anda dan simpan referensi ke sana di suatu tempat. Saya melakukannya di variabel statis dalam aktivitas saya, karena dideklarasikan sebagai singleTask dan fragmen bisa tetap sama sepanjang waktu.

Dengan cara ini android tidak membuat ulang fragmen tetapi menggunakan instance yang sama.

mc.dev
sumber
4
Terima kasih, Terima kasih, Terima kasih banyak, saya benar-benar tidak punya kata-kata untuk berterima kasih Mik, saya mengejar masalah ini dari 10 hari terakhir dan mencoba begitu banyak metode. Tapi empat garis ajaib ini menyelamatkan hidup saya :)
7
Memiliki referensi statis ke fragmen dan / atau aktivitas adalah hal yang sangat berisiko untuk dilakukan, karena dapat menyebabkan kebocoran memori dengan sangat mudah. Tentu saja, jika Anda berhati-hati, Anda dapat menanganinya dengan mudah dengan menyetelnya ke null saat tidak lagi diperlukan.
Pengembang android
Ini berhasil untuk saya. Fragmen dan tampilan masing-masing menyimpan tautannya setelah aplikasi mogok dan boot ulang. Terima kasih!
swebal
Saya menggunakan setRetainInstance(true)dengan FragmentPagerAdapter. Semuanya bekerja dengan baik. Namun saat saya memutar perangkat, adaptor masih memiliki fragmen tetapi fragmen tidak ditampilkan. Metode siklus proses fragmen juga tidak dipanggil. Adakah yang bisa membantu?
Jonas
1
kamu telah menyelamatkan hariku !!! Telah mencari bug ini selama 3 hari sejauh ini. Saya memiliki Viewpager dengan 2 fragmen di dalam SingleTask Activity dan dengan tanda "Dont keep Activities" diaktifkan. Terima kasih banyak!!!!
matriks
29

Saya memecahkan masalah ini dengan mengakses fragmen saya secara langsung melalui FragmentManager, bukan melalui FragmentPagerAdapter seperti itu. Pertama, saya perlu mencari tahu tag dari fragmen yang dibuat secara otomatis oleh FragmentPagerAdapter ...

private String getFragmentTag(int pos){
    return "android:switcher:"+R.id.viewpager+":"+pos;
}

Kemudian saya hanya mendapatkan referensi ke fragmen itu dan melakukan apa yang saya butuhkan begitu ...

Fragment f = this.getSupportFragmentManager().findFragmentByTag(getFragmentTag(1));
((MyFragmentInterface) f).update(id, name);
viewPager.setCurrentItem(1, true);

Di dalam fragmen saya, saya mengatur setRetainInstance(false);agar saya dapat menambahkan nilai secara manual ke bundel storedInstanceState.

@Override
public void onSaveInstanceState(Bundle outState) {
    if(this.my !=null)
        outState.putInt("myId", this.my.getId());

    super.onSaveInstanceState(outState);
}

dan kemudian di OnCreate saya mengambil kunci itu dan memulihkan status fragmen seperlunya. Solusi mudah yang sulit (setidaknya bagi saya) untuk dipikirkan.

Maurycy
sumber
Saya memiliki masalah yang serupa dengan Anda, tetapi saya kurang memahami penjelasan Anda, dapatkah Anda memberikan detail lebih lanjut? Saya juga memiliki dan adaptor yang menyimpan fragmen dalam daftar, tetapi ketika saya melanjutkan aplikasi saya dari aplikasi terbaru, aplikasi mogok karena ada beberapa fragmen yang terlepas. Masalahnya ada di sini stackoverflow.com/questions/11631408/…
Georgy Gobozov
3
Apa 'milik saya' Anda dalam referensi fragmen?
Josh
Kita semua tahu bahwa solusi ini bukanlah pendekatan yang baik tetapi ini adalah cara termudah untuk digunakan FragmentByTagdi ViewPager.
Youngjae
berikut adalah cara mengakses fragmen tanpa bergantung pada kompatibilitas dengan cara internal untuk menetapkan tag: stackoverflow.com/questions/14035090/…
morgwai
26

Solusi teruji kerja global.

getSupportFragmentManager()menyimpan referensi null beberapa kali dan halaman Tampilan tidak membuat yang baru karena menemukan referensi ke fragmen yang sama. Jadi untuk getChildFragmentManager()mengatasi penggunaan ini memecahkan masalah dengan cara yang sederhana.

Jangan lakukan ini:

new PagerAdapter(getSupportFragmentManager(), fragments);

Melakukan hal ini:

new PagerAdapter(getChildFragmentManager() , fragments);

Vinayak
sumber
6
ini hanya mungkin jika Anda menghosting (dan membuat instance) pagerAdapter di dalam Fragment dan bukan di dalam aktivitas Anda. Dalam hal ini Anda benar, Anda harus menggunakan childFragmentManager secara default. Menggunakan SupportFragmentManager akan salah secara default
Klitos G.
Sangat tepat. Memecahkan masalah saya tanpa menggunakan peretasan. Terima kasih! Versi Kotlin saya berubah dari FragmentStatePagerAdapter(activity!!.supportFragmentManager)menjadi lebih mudah untuk dilihat FragmentStatePagerAdapter(childFragmentManager):)
dll
7

Jangan mencoba berinteraksi di antara fragmen di ViewPager. Anda tidak dapat menjamin bahwa fragmen lain terpasang atau bahkan ada. Alih-alih mengubah judul bilah tindakan dari fragmen, Anda dapat melakukannya dari aktivitas Anda. Gunakan pola antarmuka standar untuk ini:

public interface UpdateCallback
{
    void update(String name);
}

public class MyActivity extends FragmentActivity implements UpdateCallback
{
    @Override
    public void update(String name)
    {
        getSupportActionBar().setTitle(name);
    }

}

public class MyFragment extends Fragment
{
    private UpdateCallback callback;

    @Override
    public void onAttach(SupportActivity activity)
    {
        super.onAttach(activity);
        callback = (UpdateCallback) activity;
    }

    @Override
    public void onDetach()
    {
        super.onDetach();
        callback = null;
    }

    public void updateActionbar(String name)
    {
        if(callback != null)
            callback.update(name);
    }
}
Malaikat
sumber
Tentu akan menyertakan kode sekarang ... Saya sudah mencatat metode tersebut. OnDetach dipanggil saat onStop dipanggil di fragmentActivity. onAttach dipanggil tepat sebelum onCreate dari FragmentActivity.
Maurycy
Hmm terima kasih tapi masalah masih berlanjut. Saya membuat pengaturan nama panggilan balik sebagai gantinya. Bisakah saya tidak memasukkan logika ke dalam Fragmen? Saya meminta fragmen saya mengaktifkan layanan dan elemen lain yang memerlukan referensi ke aktivitas tersebut. Saya harus memindahkan semua logika aplikasi saya ke aktivitas utama untuk menghindari masalah yang saya alami. Ini sepertinya sedikit tidak perlu.
Maurycy
Ok jadi saya baru saja menguji ini sepenuhnya dan mengomentari semua kode saya ... kecuali untuk panggilan balik untuk mengubah judul. Ketika aplikasi pertama kali diluncurkan dan saya pindah ke layar itu nama judul diubah np. Setelah memuat beberapa aplikasi kemudian kembali judul tidak berubah lagi. Sepertinya callback diterima di instance aktivitas lama. Saya tidak bisa memikirkan penjelasan lain
Maurycy
Saya menyimpangkan ini menjadi masalah dengan fragmentManager dan masalah ini ... code.google.com/p/android/issues/detail?id=19211 . Saya masih tidak tahu bagaimana menyelesaikan ini
Maurycy
5

Anda dapat menghapus fragmen saat menghancurkan viewpager, dalam kasus saya, saya menghapusnya onDestroyView()dari fragmen saya:

@Override
public void onDestroyView() {

    if (getChildFragmentManager().getFragments() != null) {
        for (Fragment fragment : getChildFragmentManager().getFragments()) {
            getChildFragmentManager().beginTransaction().remove(fragment).commitAllowingStateLoss();
        }
    }

    super.onDestroyView();
}
Raoni Novellino
sumber
Terima kasih, solusi Anda berhasil. Diperlukan yang ViewPagerjuga didasarkan pada childFragmentManageradaptor (bukan fragmentManager). Varian lain juga berfungsi: jangan gunakan onDestroyView, tetapi hapus fragmen turunan sebelum ViewPagerpembuatan adaptor.
CoolMind
4

Setelah beberapa jam mencari masalah serupa, saya pikir ada solusi lain. Yang ini setidaknya berhasil untuk saya dan saya hanya perlu mengubah beberapa baris.

Ini adalah masalah yang saya hadapi, saya memiliki aktivitas dengan halaman tampilan yang menggunakan FragmentStatePagerAdapter dengan dua Fragmen. Semuanya berfungsi dengan baik sampai saya memaksa aktivitas untuk dihancurkan (opsi pengembang) atau saya memutar layar. Saya menyimpan referensi ke dua fragmen setelah dibuat di dalam metode getItem.

Pada titik itu, aktivitas akan dibuat lagi dan semuanya berfungsi dengan baik pada titik ini, tetapi saya telah kehilangan referensi ke fragmetns saya karena getItem tidak dipanggil lagi.

Beginilah cara saya memperbaiki masalah itu, di dalam FragmentStatePagerAdapter:

    @Override
    public Object instantiateItem(ViewGroup container, int position) {
        Object aux = super.instantiateItem(container, position);

        //Update the references to the Fragments we have on the view pager
        if(position==0){
            fragTabOne = (FragOffersList)aux;
        }
        else{
            fragTabTwo = (FragOffersList) aux;
        }

        return aux;
    }

Anda tidak akan mendapatkan panggilan di getItem lagi jika adaptor sudah memiliki referensi ke dalamnya secara internal, dan Anda tidak boleh mengubahnya. Sebagai gantinya Anda bisa mendapatkan fragmen yang sedang digunakan dengan melihat metode lain ini instantiateItem () yang akan dipanggil untuk setiap fragmen Anda.

Semoga bisa membantu siapa saja.

Lancelot
sumber
Bagaimana jika Anda memiliki banyak fragmen? apakah Anda benar-benar lebih suka menyimpan referensi untuk masing-masingnya? Bukankah itu buruk untuk ingatan?
Pengembang android
Itu semua tergantung pada apa yang sebenarnya ingin Anda capai. Biasanya dengan tampilan pager ini, Anda tidak menyimpan lebih dari 3 fragmen sekaligus. Yang di tengah dan yang satu di setiap sisi. Untuk apa yang saya inginkan, saya benar-benar perlu memiliki referensi ke fragmen, tetapi saya tahu saya hanya perlu menangani dua.
Lancelot
0

Karena FragmentManager akan menangani pemulihan Fragmen untuk Anda segera setelah metode onResume () dipanggil, saya memiliki panggilan fragmen ke aktivitas dan menambahkan dirinya sendiri ke daftar. Dalam contoh saya, saya menyimpan semua ini dalam implementasi PagerAdapter saya. Setiap fragmen mengetahui posisinya karena ditambahkan ke argumen fragmen saat pembuatan. Sekarang setiap kali saya perlu memanipulasi fragmen pada indeks tertentu, yang harus saya lakukan adalah menggunakan daftar dari adaptor saya.

Berikut ini adalah contoh Adaptor untuk ViewPager kustom yang akan mengembangkan fragmen saat bergerak menjadi fokus, dan menurunkan skalanya saat bergerak keluar dari fokus. Selain kelas Adapter dan Fragment yang saya miliki di sini, yang Anda butuhkan hanyalah agar aktivitas induk dapat mereferensikan variabel adaptor dan Anda sudah disetel.

Adaptor

public class GrowPagerAdapter extends FragmentPagerAdapter implements OnPageChangeListener, OnScrollChangedListener {

public final String TAG = this.getClass().getSimpleName();

private final int COUNT = 4;

public static final float BASE_SIZE = 0.8f;
public static final float BASE_ALPHA = 0.8f;

private int mCurrentPage = 0;
private boolean mScrollingLeft;

private List<SummaryTabletFragment> mFragments;

public int getCurrentPage() {
    return mCurrentPage;
}

public void addFragment(SummaryTabletFragment fragment) {
    mFragments.add(fragment.getPosition(), fragment);
}

public GrowPagerAdapter(FragmentManager fm) {
    super(fm);

    mFragments = new ArrayList<SummaryTabletFragment>();
}

@Override
public int getCount() {
    return COUNT;
}

@Override
public Fragment getItem(int position) {
    return SummaryTabletFragment.newInstance(position);
}

@Override
public void onPageScrollStateChanged(int state) {}

@Override
public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {

    adjustSize(position, positionOffset);
}

@Override
public void onPageSelected(int position) {
    mCurrentPage = position;
}

/**
 * Used to adjust the size of each view in the viewpager as the user
 * scrolls.  This provides the effect of children scaling down as they
 * are moved out and back to full size as they come into focus.
 * 
 * @param position
 * @param percent
 */
private void adjustSize(int position, float percent) {

    position += (mScrollingLeft ? 1 : 0);
    int secondary = position + (mScrollingLeft ? -1 : 1);
    int tertiary = position + (mScrollingLeft ? 1 : -1);

    float scaleUp = mScrollingLeft ? percent : 1.0f - percent;
    float scaleDown = mScrollingLeft ? 1.0f - percent : percent;

    float percentOut = scaleUp > BASE_ALPHA ? BASE_ALPHA : scaleUp;
    float percentIn = scaleDown > BASE_ALPHA ? BASE_ALPHA : scaleDown;

    if (scaleUp < BASE_SIZE)
        scaleUp = BASE_SIZE;

    if (scaleDown < BASE_SIZE)
        scaleDown = BASE_SIZE;

    // Adjust the fragments that are, or will be, on screen
    SummaryTabletFragment current = (position < mFragments.size()) ? mFragments.get(position) : null;
    SummaryTabletFragment next = (secondary < mFragments.size() && secondary > -1) ? mFragments.get(secondary) : null;
    SummaryTabletFragment afterNext = (tertiary < mFragments.size() && tertiary > -1) ? mFragments.get(tertiary) : null;

    if (current != null && next != null) {

        // Apply the adjustments to each fragment
        current.transitionFragment(percentIn, scaleUp);
        next.transitionFragment(percentOut, scaleDown);

        if (afterNext != null) {
            afterNext.transitionFragment(BASE_ALPHA, BASE_SIZE);
        }
    }
}

@Override
public void onScrollChanged(int l, int t, int oldl, int oldt) {

    // Keep track of which direction we are scrolling
    mScrollingLeft = (oldl - l) < 0;
}
}

Pecahan

public class SummaryTabletFragment extends BaseTabletFragment {

public final String TAG = this.getClass().getSimpleName();

private final float SCALE_SIZE = 0.8f;

private RelativeLayout mBackground, mCover;
private TextView mTitle;
private VerticalTextView mLeft, mRight;

private String mTitleText;
private Integer mColor;

private boolean mInit = false;
private Float mScale, mPercent;

private GrowPagerAdapter mAdapter;
private int mCurrentPosition = 0;

public String getTitleText() {
    return mTitleText;
}

public void setTitleText(String titleText) {
    this.mTitleText = titleText;
}

public static SummaryTabletFragment newInstance(int position) {

    SummaryTabletFragment fragment = new SummaryTabletFragment();
    fragment.setRetainInstance(true);

    Bundle args = new Bundle();
    args.putInt("position", position);
    fragment.setArguments(args);

    return fragment;
}

@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
    super.onCreateView(inflater, container, savedInstanceState);

    mRoot = inflater.inflate(R.layout.tablet_dummy_view, null);

    setupViews();
    configureView();

    return mRoot;
}

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

    if (savedInstanceState != null) {
        mColor = savedInstanceState.getInt("color", Color.BLACK);
    }

    configureView();
}

@Override
public void onSaveInstanceState(Bundle outState)  {

    outState.putInt("color", mColor);

    super.onSaveInstanceState(outState);
}

@Override
public int getPosition() {
    return getArguments().getInt("position", -1);
}

@Override
public void setPosition(int position) {
    getArguments().putInt("position", position);
}

public void onResume() {
    super.onResume();

    mAdapter = mActivity.getPagerAdapter();
    mAdapter.addFragment(this);
    mCurrentPosition = mAdapter.getCurrentPage();

    if ((getPosition() == (mCurrentPosition + 1) || getPosition() == (mCurrentPosition - 1)) && !mInit) {
        mInit = true;
        transitionFragment(GrowPagerAdapter.BASE_ALPHA, GrowPagerAdapter.BASE_SIZE);
        return;
    }

    if (getPosition() == mCurrentPosition && !mInit) {
        mInit = true;
        transitionFragment(0.00f, 1.0f);
    }
}

private void setupViews() {

    mCover = (RelativeLayout) mRoot.findViewById(R.id.cover);
    mLeft = (VerticalTextView) mRoot.findViewById(R.id.title_left);
    mRight = (VerticalTextView) mRoot.findViewById(R.id.title_right);
    mBackground = (RelativeLayout) mRoot.findViewById(R.id.root);
    mTitle = (TextView) mRoot.findViewById(R.id.title);
}

private void configureView() {

    Fonts.applyPrimaryBoldFont(mLeft, 15);
    Fonts.applyPrimaryBoldFont(mRight, 15);

    float[] size = UiUtils.getScreenMeasurements(mActivity);
    int width = (int) (size[0] * SCALE_SIZE);
    int height = (int) (size[1] * SCALE_SIZE);

    RelativeLayout.LayoutParams params = new RelativeLayout.LayoutParams(width, height);
    mBackground.setLayoutParams(params);

    if (mScale != null)
        transitionFragment(mPercent, mScale);

    setRandomBackground();

    setTitleText("Fragment " + getPosition());

    mTitle.setText(getTitleText().toUpperCase());
    mLeft.setText(getTitleText().toUpperCase());
    mRight.setText(getTitleText().toUpperCase());

    mLeft.setOnClickListener(new OnClickListener() {

        @Override
        public void onClick(View v) {

            mActivity.showNextPage();
        }
    });

    mRight.setOnClickListener(new OnClickListener() {

        @Override
        public void onClick(View v) {

            mActivity.showPrevPage();
        }
    });
}

private void setRandomBackground() {

    if (mColor == null) {
        Random r = new Random();
        mColor = Color.rgb(r.nextInt(255), r.nextInt(255), r.nextInt(255));
    }

    mBackground.setBackgroundColor(mColor);
}

public void transitionFragment(float percent, float scale) {

    this.mScale = scale;
    this.mPercent = percent;

    if (getView() != null && mCover != null) {

        getView().setScaleX(scale);
        getView().setScaleY(scale);

        mCover.setAlpha(percent);
        mCover.setVisibility((percent <= 0.05f) ? View.GONE : View.VISIBLE);
    }
}

@Override
public String getFragmentTitle() {
    return null;
}
}
saulpower
sumber
0

Solusi saya: Saya menetapkan hampir setiap Tampilan sebagai static. Sekarang aplikasi saya berinteraksi dengan sempurna. Mampu memanggil metode statis dari mana-mana mungkin bukan gaya yang baik, tetapi mengapa harus bermain-main dengan kode yang tidak berfungsi? Saya membaca banyak pertanyaan dan jawaban mereka di SO dan tidak ada solusi yang membawa kesuksesan (bagi saya).

Saya tahu itu dapat membocorkan memori, dan membuang tumpukan, dan kode saya tidak akan cocok untuk proyek lain, tetapi saya tidak merasa takut tentang ini - Saya menguji aplikasi pada perangkat dan kondisi yang berbeda, tidak ada masalah sama sekali, Android Platform sepertinya bisa menangani ini. UI disegarkan setiap detik dan bahkan pada perangkat S2 ICS (4.0.3) aplikasi mampu menangani ribuan penanda geografis.

Martin Pfeffer
sumber
0

Saya menghadapi masalah yang sama tetapi ViewPager saya berada di dalam TopFragment yang membuat dan menyetel adaptor menggunakan setAdapter(new FragmentPagerAdapter(getChildFragmentManager())).

Saya memperbaiki masalah ini dengan onAttachFragment(Fragment childFragment)mengganti di TopFragment seperti ini:

@Override
public void onAttachFragment(Fragment childFragment) {
    if (childFragment instanceof OnboardingDiamondsFragment) {
        mChildFragment = (ChildFragment) childFragment;
    }

    super.onAttachFragment(childFragment);
}

Seperti yang sudah diketahui (lihat jawaban di atas), saat childFragmentManager membuat ulang dirinya sendiri, ia juga membuat fragmen yang ada di dalam viewPager.
Bagian pentingnya adalah setelah itu, dia memanggil onAttachFragment dan sekarang kita memiliki referensi ke fragmen baru yang dibuat ulang!

Semoga ini akan membantu siapa pun yang mendapatkan Q lama seperti saya :)

Shirane85
sumber
0

Saya memecahkan masalah dengan menyimpan fragmen di SparceArray:

public abstract class SaveFragmentsPagerAdapter extends FragmentPagerAdapter {

    SparseArray<Fragment> fragments = new SparseArray<>();

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

    @Override
    public Object instantiateItem(ViewGroup container, int position) {
        Fragment fragment = (Fragment) super.instantiateItem(container, position);
        fragments.append(position, fragment);
        return fragment;
    }

    @Nullable
    public Fragment getFragmentByPosition(int position){
        return fragments.get(position);
    }

}
xaxtix.dll
sumber
0

Asal kamu tahu...

Menambah litani kesengsaraan dengan kelas-kelas ini, ada bug yang cukup menarik yang patut dibagikan.

Saya menggunakan ViewPager untuk menavigasi pohon item (pilih item dan view pager menganimasikan bergulir ke kanan, dan cabang berikutnya muncul, menavigasi kembali, dan ViewPager bergulir ke arah yang berlawanan untuk kembali ke node sebelumnya) .

Masalahnya muncul saat saya mendorong dan mengeluarkan fragmen dari akhir FragmentStatePagerAdapter. Cukup pintar untuk memperhatikan bahwa item berubah, dan cukup pintar untuk membuat dan mengganti fragmen ketika item telah berubah. Namun tidak cukup pintar untuk membuang status fragmen, atau cukup pintar untuk memangkas status fragmen yang disimpan secara internal saat ukuran adaptor berubah. Jadi, ketika Anda meletuskan sebuah item, dan mendorong yang baru ke bagian akhir, fragmen untuk item baru mendapatkan status tersimpan dari fragmen untuk item lama, yang menyebabkan kerusakan mutlak dalam kode saya. Fragmen saya membawa data yang mungkin memerlukan banyak pekerjaan untuk diambil ulang dari internet, jadi tidak menyimpan status bukanlah pilihan.

Saya tidak memiliki solusi yang bersih. Saya menggunakan sesuatu seperti ini:

  public void onSaveInstanceState(Bundle outState) {
    IFragmentListener listener = (IFragmentListener)getActivity();
    if (listener!= null)
    {
        if (!listener.isStillInTheAdapter(this.getAdapterItem()))
        {
            return; // return empty state.
        }

    }
    super.onSaveInstanceState(outState);

    // normal saving of state for flips and 
    // paging out of the activity follows
    ....
  }

Solusi yang tidak sempurna karena instance fragmen baru masih mendapatkan SaveState Bundle, tetapi setidaknya tidak membawa data usang.

Robin Davies
sumber
0

Karena orang tidak cenderung membaca komentar, berikut adalah jawaban yang sebagian besar meniru apa yang saya tulis di sini :

akar penyebab masalah ini adalah kenyataan bahwa sistem android tidak memanggil getItemuntuk mendapatkan fragmen yang sebenarnya ditampilkan, tetapi instantiateItem. Metode ini pertama kali mencoba mencari dan menggunakan kembali instance fragmen untuk tab tertentu di FragmentManager. Hanya jika pencarian ini gagal (yang terjadi hanya pertama kali saat FragmentManagerbaru dibuat) maka getItemakan dipanggil. Itu untuk alasan yang jelas untuk tidak membuat ulang fragmen (yang mungkin berat) misalnya setiap kali pengguna memutar perangkatnya.
Untuk mengatasi ini, alih-alih membuat fragmen Fragment.instantiatedalam aktivitas Anda, Anda harus melakukannya dengan pagerAdapter.instantiateItemdan semua panggilan ini harus diapit oleh startUpdate/finishUpdatepanggilan metode yang masing-masing memulai / melakukan transaksi fragmen.getItem harus menjadi tempat di mana fragmen benar-benar dibuat menggunakan konstruktornya masing-masing.

List<Fragment> fragments = new Vector<Fragment>();

@Override protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.myLayout);
        ViewPager viewPager = (ViewPager) findViewById(R.id.myViewPager);
        MyPagerAdapter adapter = new MyPagerAdapter(getSupportFragmentManager());
        viewPager.setAdapter(adapter);
        ((TabLayout) findViewById(R.id.tabs)).setupWithViewPager(viewPager);

        adapter.startUpdate(viewPager);
        fragments.add(adapter.instantiateItem(viewPager, 0));
        fragments.add(adapter.instantiateItem(viewPager, 1));
        // and so on if you have more tabs...
        adapter.finishUpdate(viewPager);
}

class MyPagerAdapter extends FragmentPagerAdapter {

        public MyPagerAdapter(FragmentManager manager) {super(manager);}

        @Override public int getCount() {return 2;}

        @Override public Fragment getItem(int position) {
            if (position == 0) return new Fragment0();
            if (position == 1) return new Fragment1();
            return null;  // or throw some exception
        }

        @Override public CharSequence getPageTitle(int position) {
            if (position == 0) return getString(R.string.tab0);
            if (position == 1) return getString(R.string.tab1);
            return null;  // or throw some exception
        }
}
morgwai
sumber