wsparcie FragmentPagerAdapter przechowuje odniesienia do starych fragmentów

109

NAJNOWSZE INFORMACJE:

zawęziłem swój problem do problemu, w którym fragmentManager zachowuje wystąpienia starych fragmentów i mój viewpager nie jest zsynchronizowany z moim FragmentManager. Zobacz ten problem ... http://code.google.com/p/android/issues/detail?id=19211#makechanges . Nadal nie mam pojęcia, jak to rozwiązać. Jakieś sugestie...

Próbowałem to debugować przez długi czas i każda pomoc byłaby bardzo wdzięczna. Używam FragmentPagerAdapter, który akceptuje listę fragmentów, takich jak:

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

Wdrożenie jest standardowe. Używam biblioteki obliczalności ActionBarSherlock i v4 dla fragmentów.

Mój problem polega na tym, że po opuszczeniu aplikacji i otwarciu kilku innych aplikacji i powrocie fragmenty tracą odniesienie z powrotem do FragmentActivity (tj. getActivity() == null). Nie wiem, dlaczego tak się dzieje. Próbowałem ustawić ręcznie, setRetainInstance(true);ale to nie pomaga. Pomyślałem, że dzieje się tak, gdy moja FragmentActivity zostanie zniszczona, jednak dzieje się tak nadal, jeśli otworzę aplikację, zanim otrzymam komunikat dziennika. Czy są jakieś pomysły?

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

Adapter:

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();

    }

}

Jeden z moich fragmentów został usunięty, ale rozebrałem wszystko, co zostało rozebrane i nadal nie działa ...

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);
    }

}

}

Metoda update jest wywoływana, gdy użytkownik wchodzi w interakcję z innym fragmentem, a getActivity zwraca wartość null. Oto metoda, którą wywołuje drugi fragment ...

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

Uważam, że gdy aplikacja zostanie zniszczona, a następnie utworzona ponownie, zamiast po prostu uruchamiać aplikację do domyślnego fragmentu, aplikacja uruchamia się, a następnie przeglądarka przechodzi do ostatniej znanej strony. Wydaje się to dziwne, czy aplikacja nie powinna po prostu załadować się do domyślnego fragmentu?

Maurycy
źródło
27
Fragment Androida jest do niczego!
Hamidreza Sadegh
13
Boże, kocham wasze wiadomości z dziennika: D
oli.G

Odpowiedzi:

120

PagerAdapter.getItemWystępuje problem, ponieważ tworzysz instancje i przechowujesz odniesienia do swoich fragmentów poza programem i próbujesz ich używać niezależnie od ViewPagera. Jak mówi Seraph, masz gwarancje, że fragment został utworzony / dodany w ViewPager w określonym czasie - należy to uznać za szczegół implementacji. ViewPager leniwie ładuje swoje strony; domyślnie ładuje tylko bieżącą stronę oraz tę po lewej i po prawej stronie.

Jeśli umieścisz aplikację w tle, fragmenty, które zostały dodane do menedżera fragmentów, zostaną automatycznie zapisane. Nawet jeśli Twoja aplikacja zostanie zabita, te informacje zostaną przywrócone po ponownym uruchomieniu aplikacji.

Teraz pomyśl, że obejrzałeś kilka stron, fragmenty A, B i C. Wiesz, że zostały one dodane do menedżera fragmentów. Ponieważ używasz, FragmentPagerAdaptera nieFragmentStatePagerAdapter , te fragmenty będą nadal dodawane (ale potencjalnie odłączane) podczas przewijania do innych stron.

Weź pod uwagę, że następnie uruchomisz swoją aplikację w tle, a następnie zostanie ona zabita. Kiedy wrócisz, Android zapamięta, że ​​kiedyś miałeś fragmenty A, B i C w menedżerze fragmentów, więc odtwarza je dla Ciebie, a następnie dodaje. Jednak te, które są teraz dodane do menedżera fragmentów, NIE są tymi, które masz na liście fragmentów w swojej aktywności.

FragmentPagerAdapter nie podejmie próby wywołania, getPositionjeśli istnieje już fragment dodany dla tej konkretnej pozycji na stronie. W rzeczywistości, ponieważ fragment odtworzony przez Androida nigdy nie zostanie usunięty, nie masz nadziei na zastąpienie go wezwaniem do getPosition. Uzyskanie do niego uchwytu jest również dość trudne, aby uzyskać do niego odniesienie, ponieważ został dodany z nieznanym ci tagiem. Jest to zgodne z projektem; odradza się manipulowanie fragmentami, którymi zarządza pager widoku. Powinieneś wykonywać wszystkie swoje działania w obrębie fragmentu, komunikować się z działaniem i w razie potrzeby prosić o przejście do określonej strony.

Wróćmy teraz do problemu z brakującą aktywnością. Wywołanie pagerAdapter.getItem(1)).update(id, name)po tym wszystkim powoduje zwrócenie fragmentu z listy, który nie został jeszcze dodany do menedżera fragmentów , a więc nie będzie miał odniesienia do działania. Sugerowałbym, że twoja metoda aktualizacji powinna zmodyfikować jakąś udostępnioną strukturę danych (prawdopodobnie zarządzaną przez działanie), a następnie, kiedy przejdziesz do określonej strony, może ona sama rysować się na podstawie tych zaktualizowanych danych.

antonyt
źródło
1
Podoba mi się twoje rozwiązanie, ponieważ jest bardzo eleganckie i prawdopodobnie zrefaktoruje mój kod, jednak jak powiedziałeś, chciałem zachować 100% logiki i danych w tym fragmencie. Twoje rozwiązanie wymagałoby prawie utrzymania wszystkich danych w FragmentActivity, a następnie użycia każdego fragmentu po prostu do obsługi logiki wyświetlania. Jak powiedziałem, wolę to, ale interakcja między fragmentami jest bardzo ciężka i może być irytująca. W każdym razie dziękuję za szczegółowe wyjaśnienie. Wyjaśniłeś to o wiele lepiej niż ja.
Maurycy
28
Krótko: nigdy nie trzymaj odniesienia do fragmentu poza adapterem
passsy
2
W jaki sposób wystąpienie Activity uzyskuje dostęp do wystąpienia FragmentPagerAdapter, jeśli nie utworzyło wystąpienia FragmentPagerAdapter? Czy przyszłe wystąpienia nie przywrócą po prostu fragmentu FragmentPagerAdapter i wszystkich jego wystąpień fragmentów? Czy FragmentPagerAdapter powinien implementować wszystkie interfejsy fragmentów w celu zarządzania komunikacją między fragmentami?
Eric H.
stwierdzenie "Uzyskanie do niego uchwytu jest również dość trudne, aby uzyskać do niego odniesienie, ponieważ został on dodany z nieznanym tobie znacznikiem. Jest to zgodne z projektem; zniechęca się do majstrowania przy fragmentach, którymi zarządza pager widoku . ” to fałsz. bardzo łatwo jest uzyskać referencje dzwoniąc instantiateItemi faktycznie powinieneś to zrobić w ramach onCreateswojej działalności. zobacz szczegóły tutaj: stackoverflow.com/questions/14035090/…
morgwai
generalnie tworzenie instancji fragmentów bez wczytywania jest
ryzykowne,
108

Znalazłem proste rozwiązanie, które zadziałało.

Spraw, aby adapter fragmentu rozszerzał FragmentStatePagerAdapter zamiast FragmentPagerAdapter i przesłonił metodę onSave, aby zwrócić wartość null

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

Zapobiega to odtworzeniu fragmentu przez Androida


Dzień później znalazłem inne, lepsze rozwiązanie.

Wezwij setRetainInstance(true)wszystkie swoje fragmenty i zapisz gdzieś odniesienia do nich. Zrobiłem to w zmiennej statycznej w mojej aktywności, ponieważ jest zadeklarowana jako singleTask, a fragmenty mogą pozostać takie same przez cały czas.

W ten sposób android nie odtwarza fragmentów, ale używa tych samych instancji.

mc.dev
źródło
4
Dzięki, dzięki, dzięki Tak bardzo, naprawdę nie mam słów, żeby ci podziękować Mik, goniłem ten numer z ostatnich 10 dni i próbowałem tylu metod, ale te cztery magiczne
7
posiadanie statycznego odniesienia do fragmentów i / lub działań jest bardzo ryzykowne, ponieważ może bardzo łatwo spowodować wycieki pamięci. Oczywiście, jeśli jesteś ostrożny, możesz sobie z tym dość łatwo poradzić, ustawiając je na zero, gdy nie są już potrzebne.
programista Androida
To zadziałało dla mnie. Fragmenty i ich widoki zachowują swoje linki po awarii aplikacji i ponownym uruchomieniu. Dzięki!
Swebal
Używam setRetainInstance(true)z FragmentPagerAdapter. Wszystko działa dobrze. Ale kiedy obracam urządzenie, adapter nadal ma fragmenty, ale fragmenty nie są wyświetlane. Metody cyklu życia fragmentów również nie są nazywane. Czy ktoś może pomóc?
Jonas
1
uratowałeś mój dzień !!! Do tej pory szukałem tego błędu przez 3 dni. Miałem Viewpager z 2 fragmentami w ramach działania SingleTask i z włączoną flagą „Nie zachowuj działań”. Wielkie dzięki!!!!
matryca
29

Rozwiązałem ten problem, uzyskując dostęp do moich fragmentów bezpośrednio przez FragmentManager zamiast za pośrednictwem FragmentPagerAdapter w ten sposób. Najpierw muszę znaleźć tag fragmentu wygenerowanego automatycznie przez FragmentPagerAdapter ...

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

Wtedy po prostu dostaję odniesienie do tego fragmentu i robię to, czego potrzebuję ...

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

Wewnątrz moich fragmentów ustawiłem setRetainInstance(false);tak, żebym mógł ręcznie dodawać wartości do pakietu saveInstanceState.

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

    super.onSaveInstanceState(outState);
}

a następnie w OnCreate chwytam ten klucz i przywracam stan fragmentu w razie potrzeby. Proste rozwiązanie, które było trudne (przynajmniej dla mnie) do wymyślenia.

Maurycy
źródło
Mam problem podobny do ciebie, ale nie do końca rozumiem twoje wyjaśnienie, czy możesz podać więcej szczegółów? Mam również adapter, który przechowuje fragmenty na liście, ale kiedy wznawiam moją aplikację z ostatnich aplikacji, aplikacje ulegają awarii, ponieważ jest jakiś odłączony fragment. Problem jest tutaj stackoverflow.com/questions/11631408/ ...
Georgy Gobozov
3
Jakie jest twoje „moje” w odniesieniu do fragmentu?
Josh
Wszyscy wiemy, że to rozwiązanie nie jest dobrym podejściem, ale jest najłatwiejszym sposobem użycia FragmentByTagw ViewPager.
Youngjae
oto sposób, jak uzyskać dostęp do fragmentów bez polegania na zgodności z wewnętrznym sposobem przypisywania tagów: stackoverflow.com/questions/14035090/ ...
morgwai
26

Globalne, sprawdzone rozwiązanie.

getSupportFragmentManager()czasami zachowuje zerowe odniesienie, a przeglądarka stron nie tworzy nowego, ponieważ znajduje odniesienie do tego samego fragmentu. Aby przezwyciężyć to użycie, getChildFragmentManager()rozwiązuje problem w prosty sposób.

Nie rób tego:

new PagerAdapter(getSupportFragmentManager(), fragments);

Zrób to:

new PagerAdapter(getChildFragmentManager() , fragments);

Vinayak
źródło
6
byłoby to możliwe tylko wtedy, gdy hostujesz (i tworzysz instancję) pagerAdapter wewnątrz fragmentu, a nie wewnątrz swojej aktywności. W takim przypadku masz rację, powinieneś domyślnie używać childFragmentManager. Używanie SupportFragmentManager byłoby domyślnie niewłaściwe
Klitos G.
Bardzo dokładny. Rozwiązałem mój problem bez użycia hacków. Dzięki! Moja wersja Kotlina stała FragmentStatePagerAdapter(activity!!.supportFragmentManager)się łatwiejsza do oglądania FragmentStatePagerAdapter(childFragmentManager):)
ecth
7

Nie próbuj wchodzić w interakcje między fragmentami w ViewPager. Nie możesz zagwarantować, że inny fragment jest dołączony lub nawet istnieje. Zamiast zmieniać tytuł paska akcji z fragmentu, możesz to zrobić z poziomu swojej aktywności. Użyj do tego standardowego wzorca interfejsu:

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);
    }
}
Serafin
źródło
Jasne, będzie teraz zawierać kod ... Już rejestruję te metody. Metoda onDetach jest wywoływana, gdy onStop jest wywoływany w fragmentActivity. metoda onAttach jest wywoływana tuż przed onCreate elementu FragmentActivity.
Maurycy
Hmm dziękuję, ale problem nadal występuje. Zamiast tego ustawiłem nazwę jako callback. Czy nie mogę umieścić logiki we fragmencie? Mam swoje fragmenty odpalania usług i innych elementów, które wymagają odniesienia do działalności. Musiałbym przenieść logikę wszystkich moich aplikacji do głównego działania, aby uniknąć problemu, który mam. Wydaje się to trochę niepotrzebne.
Maurycy
Ok, więc po prostu przetestowałem to w pełni i skomentowałem cały mój kod ... z wyjątkiem wywołania zwrotnego do zmiany tytułu. Kiedy aplikacja jest uruchamiana po raz pierwszy i przechodzę do tego ekranu, nazwa tytułu się zmienia np. Po załadowaniu kilku aplikacji powrót do tytułu już się nie zmienia. Wygląda na to, że wywołanie zwrotne jest odbierane w starej instancji działania. Nie mogę wymyślić innego wyjaśnienia
Maurycy
Sprowadziłem to do problemu z fragmentManager, a ten problem ... code.google.com/p/android/issues/detail?id=19211 . Nadal nie mam pojęcia, jak to rozwiązać
Maurycy
5

Możesz usunąć fragmenty, gdy niszczę przeglądarkę, w moim przypadku usunąłem je onDestroyView()z mojego fragmentu:

@Override
public void onDestroyView() {

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

    super.onDestroyView();
}
Raoni Novellino
źródło
Dzięki, Twoje rozwiązanie działa. Wymagane jest, aby ViewPageropierał się również na childFragmentManageradapterze (nie fragmentManager). Działa również inny wariant: nie używaj onDestroyView, ale usuń fragmenty potomne przed ViewPagerutworzeniem adaptera.
CoolMind
4

Po kilku godzinach szukania podobnego problemu myślę, że mam inne rozwiązanie. Ten przynajmniej zadziałał dla mnie i muszę zmienić tylko kilka linii.

To jest problem, który miałem, mam działanie z pagerem widoku, który używa FragmentStatePagerAdapter z dwoma fragmentami. Wszystko działa dobrze, dopóki nie wymuszę zniszczenia działania (opcje programistyczne) lub nie obrócę ekranu. Zachowuję odniesienie do dwóch fragmentów po ich utworzeniu w metodzie getItem.

W tym momencie działanie zostanie utworzone ponownie i na tym etapie wszystko działa dobrze, ale straciłem odniesienie do moich fragmentów, ponieważ getItem nie jest ponownie wywoływane.

Oto jak rozwiązałem ten problem wewnątrz 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;
    }

Nie otrzymasz ponownie wywołania getItem, jeśli adapter ma już wewnętrzne odwołanie do niego i nie powinieneś tego zmieniać. Zamiast tego możesz uzyskać fragment, którego jest używany, patrząc na tę inną metodę instantiateItem (), która zostanie wywołana dla każdego z twoich fragmentów.

Mam nadzieję, że to pomoże każdemu.

Lancelot
źródło
Co jeśli masz dużo fragmentów? czy naprawdę wolałbyś zapisać referencje dla każdego z nich? Czy to nie jest złe dla pamięci?
programista Androida
Wszystko zależy od tego, co dokładnie próbujesz osiągnąć. Zwykle z tymi pagerami widoku nie możesz przechowywać więcej niż 3 fragmentów naraz. Ten w środku i ten po każdej stronie. Do tego, czego chcę, naprawdę potrzebuję odniesienia do fragmentów, ale wiem, że mam do czynienia tylko z dwoma.
Lancelot
0

Ponieważ FragmentManager zajmie się przywróceniem twoich fragmentów, gdy tylko zostanie wywołana metoda onResume (), mam wywołanie fragmentu do działania i dodanie go do listy. W moim przypadku przechowuję to wszystko w mojej implementacji PagerAdapter. Każdy fragment zna swoją pozycję, ponieważ jest dodawany do argumentów fragmentu podczas tworzenia. Teraz, ilekroć muszę manipulować fragmentem w określonym indeksie, wszystko, co muszę zrobić, to użyć listy z mojego adaptera.

Poniżej znajduje się przykład adaptera dla niestandardowego ViewPagera, który będzie powiększał fragment, gdy przejdzie do fokusu, i skaluje go w dół, gdy zniknie. Oprócz klas Adapter i Fragment mam tutaj wszystko, czego potrzebujesz, aby aktywność nadrzędna mogła odwoływać się do zmiennej adaptera i gotowe.

Adapter

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;
}
}

Fragment

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
źródło
0

Moje rozwiązanie: ustawiam prawie każdy widok jako static. Teraz moja aplikacja działa idealnie. Możliwość wywoływania metod statycznych z dowolnego miejsca może nie jest dobrym stylem, ale po co bawić się kodem, który nie działa? Przeczytałem wiele pytań i ich odpowiedzi tutaj na SO i żadne rozwiązanie nie przyniosło sukcesu (dla mnie).

Wiem, że może wyciekać pamięć i stertę odpadów, a mój kod nie będzie pasował do innych projektów, ale nie boję się tego - testowałem aplikację na różnych urządzeniach i warunkach, żadnych problemów, z Androidem Platforma wydaje się być w stanie sobie z tym poradzić. Interfejs użytkownika jest odświeżany co sekundę, a nawet na urządzeniu S2 ICS (4.0.3) aplikacja jest w stanie obsłużyć tysiące znaczników geograficznych.

Martin Pfeffer
źródło
0

Napotkałem ten sam problem, ale mój ViewPager znajdował się wewnątrz TopFragment, który utworzył i ustawił adapter przy użyciu setAdapter(new FragmentPagerAdapter(getChildFragmentManager())).

Naprawiłem ten problem, zastępując onAttachFragment(Fragment childFragment)w TopFragment w następujący sposób:

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

    super.onAttachFragment(childFragment);
}

Jak już wiadomo (patrz odpowiedzi powyżej), kiedy childFragmentManager odtwarza się, tworzy również fragmenty, które znajdowały się wewnątrz viewPager.
Ważną częścią jest to, że po tym wywołuje onAttachFragment i mamy teraz odniesienie do nowo utworzonego fragmentu!

Mam nadzieję, że pomoże to każdemu, kto zdobędzie ten stary Q, tak jak ja :)

Shirane85
źródło
0

Rozwiązałem problem zapisując fragmenty w 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
źródło
0

Tak, żebyś wiedział...

Dodając do litanii nieszczęść z tymi klasami, istnieje dość interesujący błąd, o którym warto się podzielić.

Używam ViewPagera do poruszania się po drzewie elementów (wybierz element, a widok pagera animuje przewijanie w prawo i pojawia się następna gałąź, przejdź wstecz, a ViewPager przewija się w przeciwnym kierunku, aby powrócić do poprzedniego węzła) .

Problem pojawia się, gdy wypycham i wyrywam fragmenty z końca FragmentStatePagerAdapter. Jest wystarczająco sprytny, aby zauważyć, że przedmioty się zmieniają, i wystarczająco inteligentny, aby utworzyć i zastąpić fragment, gdy przedmiot się zmieni. Ale niewystarczająco inteligentny, aby odrzucić stan fragmentu, ani wystarczająco inteligentny, aby przyciąć wewnętrznie zapisane stany fragmentów, gdy zmienia się rozmiar adaptera. Więc kiedy zdejmiesz element i wepchniesz nowy na koniec, fragment nowego elementu otrzyma zapisany stan fragmentu starego elementu, co spowodowało całkowite spustoszenie w moim kodzie. Moje fragmenty zawierają dane, których pobranie z Internetu może wymagać dużo pracy, więc nie zapisywanie stanu tak naprawdę nie wchodziło w grę.

Nie mam prostego rozwiązania. Użyłem czegoś takiego:

  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
    ....
  }

Niedoskonałe rozwiązanie, ponieważ nowa instancja fragmentu nadal otrzymuje pakiet saveState, ale przynajmniej nie zawiera przestarzałych danych.

Robin Davies
źródło
0

Ponieważ ludzie nie czytają komentarzy, oto odpowiedź, która w większości powiela to, co tutaj napisałem :

główną przyczyną problemu jest fakt, że system Android nie wywołuje w getItemcelu uzyskania fragmentów, które są faktycznie wyświetlane, ale instantiateItem. Ta metoda najpierw próbuje wyszukać i ponownie użyć wystąpienia fragmentu dla danej karty w FragmentManager. Dopiero gdy to wyszukiwanie się nie powiedzie (co dzieje się tylko przy pierwszym FragmentManagerutworzeniu), wówczas getItemjest wywoływana. Z oczywistych powodów nie należy odtwarzać fragmentów (które mogą być ciężkie), na przykład za każdym razem, gdy użytkownik obraca swoje urządzenie.
Aby rozwiązać ten problem, zamiast tworzyć fragmenty Fragment.instantiatew swoim działaniu, powinieneś to zrobić w miejscu, w którym fragmenty są naprawdę tworzone przy użyciu odpowiednich konstruktorów.pagerAdapter.instantiateItem a wszystkie te wywołania powinny być otoczone startUpdate/finishUpdatewywołaniami metod, które odpowiednio rozpoczynają / zatwierdzają transakcję fragmentu.getItem

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
źródło