поддержка FragmentPagerAdapter содержит ссылку на старые фрагменты

109

ПОСЛЕДНЯЯ ИНФОРМАЦИЯ:

Я сузил свою проблему до проблемы с fragmentManager, сохраняющим экземпляры старых фрагментов, и мой просмотрщик не синхронизирован с моим FragmentManager. См. Эту проблему ... http://code.google.com/p/android/issues/detail?id=19211#makechanges . Я до сих пор не знаю, как это решить. Какие-либо предложения...

Я пытался отладить это в течение долгого времени, и любая помощь будет принята с благодарностью. Я использую FragmentPagerAdapter, который принимает список таких фрагментов:

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

Реализация стандартная. Я использую ActionBarSherlock и библиотеку вычислимости v4 для фрагментов.

Моя проблема в том, что после выхода из приложения и открытия нескольких других приложений и возврата фрагменты теряют свою ссылку обратно на FragmentActivity (т.е. getActivity() == null). Я не могу понять, почему это происходит. Я пытался установить вручную, setRetainInstance(true);но это не помогает. Я полагал, что это происходит, когда моя FragmentActivity разрушается, однако это все равно происходит, если я открываю приложение до того, как получаю сообщение журнала. Есть идеи?

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

Адаптер:

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

    }

}

Один из моих фрагментов удален, но я сказал, что все раздели и все еще не работает ...

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

}

}

Метод обновления вызывается, когда пользователь взаимодействует с другим фрагментом, и getActivity возвращает значение null. Вот метод, который вызывает другой фрагмент ...

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

Я считаю, что когда приложение уничтожается, а затем создается снова, вместо того, чтобы просто запускать приложение до фрагмента по умолчанию, приложение запускается, а затем viewpager переходит на последнюю известную страницу. Это кажется странным, разве приложение не должно просто загружаться в фрагмент по умолчанию?

Maurycy
источник
27
Фрагмент Android - отстой!
Hamidreza Sadegh
13
Боже, я люблю твои сообщения журнала: D
oli.G

Ответы:

120

Вы столкнулись с проблемой, потому что вы создаете экземпляры и храните ссылки на свои фрагменты вне PagerAdapter.getItem, и пытаетесь использовать эти ссылки независимо от ViewPager. Как говорит Сераф, у вас есть гарантии, что фрагмент был создан / добавлен в ViewPager в определенное время - это следует рассматривать как деталь реализации. ViewPager выполняет отложенную загрузку своих страниц; по умолчанию загружается только текущая страница, а также страницы слева и справа.

Если вы поместите свое приложение в фоновый режим, фрагменты, которые были добавлены в диспетчер фрагментов, сохраняются автоматически. Даже если ваше приложение убито, эта информация восстанавливается при перезапуске приложения.

Теперь представьте, что вы просмотрели несколько страниц, фрагменты A, B и C. Вы знаете, что они были добавлены в диспетчер фрагментов. Поскольку вы используете, FragmentPagerAdapterа не FragmentStatePagerAdapter, эти фрагменты все равно будут добавляться (но потенциально отсоединяться) при прокрутке к другим страницам.

Учтите, что затем вы выполняете фоновое изображение для своего приложения, а затем его убивают. Когда вы вернетесь, Android вспомнит, что раньше у вас были фрагменты A, B и C в диспетчере фрагментов, поэтому он воссоздает их для вас, а затем добавляет их. Однако те, которые теперь добавлены в диспетчер фрагментов, НЕ являются теми, которые есть в вашем списке фрагментов в вашей деятельности.

FragmentPagerAdapter не будет пытаться вызвать, getPositionесли уже есть фрагмент, добавленный для этой конкретной позиции страницы. Фактически, поскольку фрагмент, воссозданный Android, никогда не будет удален, у вас нет никакой надежды заменить его вызовом getPosition. Получить указание на него также довольно сложно, потому что он был добавлен с тегом, который вам неизвестен. Это сделано специально; вам не рекомендуется возиться с фрагментами, которыми управляет пейджер представления. Вы должны выполнять все свои действия внутри фрагмента, взаимодействовать с действием и запрашивать переход на определенную страницу, если это необходимо.

Теперь вернемся к вашей проблеме с отсутствующим действием. Вызов pagerAdapter.getItem(1)).update(id, name)после того, как все это произошло, возвращает вам фрагмент в вашем списке, который еще не был добавлен в диспетчер фрагментов , поэтому у него не будет ссылки на Activity. Я бы посоветовал вашему методу обновления изменить некоторую общую структуру данных (возможно, управляемую действием), а затем, когда вы переходите на определенную страницу, она может рисовать себя на основе этих обновленных данных.

Антонит
источник
1
Мне нравится ваше решение, поскольку оно очень элегантно и, вероятно, будет реорганизовывать мой код, однако, как вы сказали, я хотел сохранить 100% логики и данных во фрагменте. Для вашего решения потребуется в значительной степени сохранить все данные в FragmentActivity, а затем использовать каждый фрагмент просто для обработки логики отображения. Как я уже сказал, я предпочитаю это, но взаимодействие между фрагментами очень тяжелое, и это может раздражать. В любом случае спасибо за подробное объяснение. Вы объяснили это намного лучше, чем я.
Maurycy
28
Вкратце: никогда не держите ссылку на Фрагмент вне
Адаптера
2
Итак, как экземпляр Activity получает доступ к экземпляру FragmentPagerAdapter, если он не создал экземпляр FragmentPagerAdapter? Разве будущие экземпляры не будут просто повторно создавать FragmentPagerAdapter и все его экземпляры фрагментов? Должен ли FragmentPagerAdapter реализовывать все интерфейсы фрагментов для управления связью между фрагментами?
Эрик Х.
утверждение "Получить указатель на него также довольно сложно, чтобы получить ссылку на него, потому что он был добавлен с тегом, который вам неизвестен. Это сделано намеренно; вам не рекомендуется вмешиваться в фрагменты, которыми управляет пейджер представления. . " ложно. очень легко получить справку по телефону, instantiateItemи вы действительно должны делать это в onCreateсвоей деятельности. см. подробности здесь: stackoverflow.com/questions/14035090/…
morgwai
в общем, создавать экземпляры фрагментов самостоятельно без загрузки - изворотливо, потому что в сценарии «уничтожить и воссоздать» вы можете обработать фрагмент, отдельный от того, который был фактически воссоздан и показан
hmac
108

Я нашел простое решение, которое сработало для меня.

Сделайте так, чтобы ваш адаптер фрагмента расширял FragmentStatePagerAdapter вместо FragmentPagerAdapter и переопределил метод onSave для возврата значения null

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

Это не позволяет Android воссоздавать фрагмент


Через день я нашел другое, лучшее решение.

Вызовите setRetainInstance(true)все свои фрагменты и сохраните где-нибудь ссылки на них. Я сделал это в статической переменной в своей деятельности, потому что она объявлена ​​как singleTask, и фрагменты могут оставаться неизменными все время.

Таким образом, Android не воссоздает фрагменты, а использует те же экземпляры.

mc.dev
источник
4
Спасибо, спасибо, большое спасибо, у меня действительно нет слов, чтобы поблагодарить тебя, Мик, я преследовал эту проблему последние 10 дней и перепробовал столько методов, но эти четыре волшебные строчки спасли мне жизнь :)
7
иметь статическую ссылку на фрагменты и / или действия - очень рискованно, так как это может очень легко вызвать утечку памяти. Конечно, если вы будете осторожны, вы можете справиться с этим довольно легко, установив для них значение null, когда они больше не нужны.
разработчик Android
Это сработало для меня. Фрагменты и соответствующие им представления сохраняют свои ссылки после сбоя и перезагрузки приложения. Спасибо!
swebal
Я использую setRetainInstance(true)с FragmentPagerAdapter. Все работает хорошо. Но когда я поворачиваю устройство, в адаптере остаются фрагменты, но фрагменты не отображаются. Также не вызываются методы жизненного цикла фрагментов. Кто-нибудь может помочь?
Jonas
1
вы спасли мне день !!! Искал эту ошибку уже 3 дня. У меня был Viewpager с 2 фрагментами внутри SingleTask Activity и с включенным флагом «Dont keep Activities». Большое спасибо!!!!
матрица
29

Я решил эту проблему, получив доступ к моим фрагментам напрямую через FragmentManager, а не через FragmentPagerAdapter, как это. Сначала мне нужно выяснить тег фрагмента, автоматически созданного FragmentPagerAdapter ...

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

Затем я просто получаю ссылку на этот фрагмент и делаю то, что мне нужно, вот так ...

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

Внутри моих фрагментов я установил setRetainInstance(false);так, чтобы я мог вручную добавлять значения в пакет savedInstanceState.

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

    super.onSaveInstanceState(outState);
}

а затем в OnCreate я беру этот ключ и при необходимости восстанавливаю состояние фрагмента. Простое решение, которое было трудно (по крайней мере, для меня) понять.

Maurycy
источник
У меня проблема, похожая на вашу, но я не совсем понимаю ваше объяснение. Не могли бы вы предоставить более подробную информацию? У меня также есть и адаптер, который хранит фрагменты в списке, но когда я возобновляю свое приложение из недавних приложений, приложения вылетают из-за того, что есть какой-то отдельный фрагмент. Проблема здесь stackoverflow.com/questions/11631408/…
Георгий Гобозов
3
Каково ваше «мое» в ссылке на фрагмент?
Джош
Все мы знаем, что это решение - не лучший подход, но это самый простой способ использовать FragmentByTagв ViewPager.
Ёнджэ
вот способ доступа к фрагментам, не полагаясь на совместимость с внутренним способом присвоения тегов: stackoverflow.com/questions/14035090/…
morgwai
26

Глобальное рабочее протестированное решение.

getSupportFragmentManager()несколько раз сохраняет пустую ссылку, и пейджер просмотра не создает новую, поскольку находит ссылку на тот же фрагмент. Таким образом, это использование getChildFragmentManager()решает проблему простым способом.

Не делайте этого:

new PagerAdapter(getSupportFragmentManager(), fragments);

Сделай это:

new PagerAdapter(getChildFragmentManager() , fragments);

Винаяк
источник
6
это возможно только в том случае, если вы размещаете (и создаете экземпляр) pagerAdapter внутри фрагмента, а не внутри своей деятельности. В этом случае вы правы, вам следует использовать по умолчанию childFragmentManager. Использование SupportFragmentManager по умолчанию было бы неправильным
Klitos G.
Очень точно. Решил мою проблему без использования хаков. Спасибо! Моя версия Kotlin превратилась FragmentStatePagerAdapter(activity!!.supportFragmentManager)в более FragmentStatePagerAdapter(childFragmentManager)
удобную для восприятия
7

Не пытайтесь взаимодействовать между фрагментами в ViewPager. Вы не можете гарантировать, что другой фрагмент прикреплен или даже существует. Вместо того, чтобы менять заголовок панели действий из фрагмента, вы можете сделать это из своей активности. Используйте для этого стандартный шаблон интерфейса:

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);
    }
}
серафим
источник
Конечно, сейчас будет включен код ... Я уже регистрирую эти методы. OnDetach вызывается, когда onStop вызывается в fragmentActivity. onAttach вызывается непосредственно перед onCreate FragmentActivity.
Maurycy
Хм, спасибо, но проблема не устранена. Вместо этого я установил имя обратного вызова. Можно ли не вложить логику во Фрагмент? У меня есть мои фрагменты, запускающие службы и другие элементы, которые требуют ссылки на действие. Мне бы пришлось переместить всю логику моих приложений в основное действие, чтобы избежать возникшей у меня проблемы. Это кажется немного ненужным.
Maurycy
Хорошо, поэтому я просто полностью проверил это и закомментировал весь свой код ... за исключением обратного вызова для изменения заголовка. КОГДА приложение запускается впервые, и я перехожу на этот экран, название заголовка изменяется np. После загрузки нескольких приложений и последующего возврата заголовок больше не меняется. Похоже, обратный вызов получен на старом экземпляре действия. Я не могу придумать другого объяснения
Мауриций
я понял, что это проблема с fragmentManager и этой проблемой ... code.google.com/p/android/issues/detail?id=19211 . Я до сих пор не знаю, как это решить
Maurycy
5

Вы можете удалить фрагменты при уничтожении окна просмотра, в моем случае я удалил их на onDestroyView()своем фрагменте:

@Override
public void onDestroyView() {

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

    super.onDestroyView();
}
Раони Новеллино
источник
Спасибо, ваше решение работает. Требуется, что ViewPagerтоже на базе childFragmentManagerадаптера (не fragmentManager). Также работает другой вариант: не использовать onDestroyView, а удалить дочерние фрагменты перед ViewPagerсозданием адаптера.
CoolMind 01
4

После нескольких часов поиска аналогичной проблемы я думаю, что есть другое решение. По крайней мере, это сработало для меня, и мне нужно изменить только пару строк.

Это проблема, с которой я столкнулся, у меня есть активность с пейджером просмотра, который использует FragmentStatePagerAdapter с двумя фрагментами. Все работает нормально, пока я не заставлю действие быть уничтоженным (параметры разработчика) или пока я не поверну экран. Я сохраняю ссылку на два фрагмента после их создания внутри метода getItem.

В этот момент действие будет создано снова, и на этом этапе все работает нормально, но я потерял ссылку на мои фрагменты, поскольку getItem больше не вызывается.

Вот как я исправил эту проблему внутри 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;
    }

Вы не получите вызов getItem снова, если адаптер уже имеет внутреннюю ссылку на него, и вам не следует это менять. Вместо этого вы можете получить используемый фрагмент, посмотрев на этот другой метод instantiateItem (), который будет вызываться для каждого из ваших фрагментов.

Надеюсь, это кому-нибудь поможет.

Ланселот
источник
Что делать, если у вас много фрагментов? Вы действительно предпочли бы сохранить ссылку для каждого из них? Разве это плохо для памяти?
Android-разработчик
Все зависит от того, чего именно вы пытаетесь достичь. Обычно с этими пейджерами просмотра вы не можете удерживать более 3 фрагментов одновременно. Один посередине и один с каждой стороны. Для того, что я хочу, мне действительно нужна ссылка на фрагменты, но я знаю, что мне нужно иметь дело только с двумя.
Lancelot
0

Поскольку FragmentManager позаботится о восстановлении ваших фрагментов, как только будет вызван метод onResume (), у меня есть вызов фрагмента для действия и добавление себя в список. В моем случае я храню все это в своей реализации PagerAdapter. Каждый фрагмент знает свою позицию, потому что он добавляется к аргументам фрагмента при создании. Теперь, когда мне нужно манипулировать фрагментом по определенному индексу, все, что мне нужно сделать, это использовать список из моего адаптера.

Ниже приведен пример адаптера для настраиваемого ViewPager, который будет увеличивать фрагмент при перемещении в фокус и уменьшать его при перемещении из фокуса. Помимо классов адаптера и фрагмента, у меня есть все, что вам нужно, это чтобы родительская активность могла ссылаться на переменную адаптера, и все настроено.

адаптер

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

Фрагмент

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;
}
}
солонка
источник
0

Мое решение: я установил почти все View как static. Теперь мое приложение отлично работает. Возможность вызывать статические методы отовсюду - это, возможно, не лучший стиль, но зачем играть с кодом, который не работает? Я прочитал много вопросов и их ответы здесь, на SO, и ни одно решение не принесло успеха (для меня).

Я знаю, что это может привести к утечке памяти и потере кучи, и мой код не будет соответствовать другим проектам, но я не боюсь этого - я тестировал приложение на разных устройствах и в разных условиях, никаких проблем, Android Платформа, похоже, справится с этим. Пользовательский интерфейс обновляется каждую секунду, и даже на устройстве S2 ICS (4.0.3) приложение способно обрабатывать тысячи гео-маркеров.

Мартин Пфеффер
источник
0

Я столкнулся с той же проблемой, но мой ViewPager находился внутри TopFragment, который создал и установил адаптер с помощью setAdapter(new FragmentPagerAdapter(getChildFragmentManager())).

Я исправил эту проблему, переопределив onAttachFragment(Fragment childFragment)в TopFragment следующим образом:

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

    super.onAttachFragment(childFragment);
}

Как уже известно (см. Ответы выше), когда childFragmentManager воссоздает себя, он также создает фрагменты, которые были внутри viewPager.
Важная часть состоит в том, что после этого он вызывает onAttachFragment, и теперь у нас есть ссылка на новый воссозданный фрагмент!

Надеюсь, это поможет любому, кто получит этот старый Q, как я :)

Ширане85
источник
0

Решил проблему, сохранив фрагменты в 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
источник
0

Просто чтобы вы знали ...

В дополнение к списку проблем, связанных с этими классами, есть довольно интересная ошибка, которой стоит поделиться.

Я использую ViewPager для навигации по дереву элементов (выберите элемент, и пейджер представит анимацию прокрутки вправо, появится следующая ветвь, перейдите назад, а ViewPager прокручивает в противоположном направлении, чтобы вернуться к предыдущему узлу) .

Проблема возникает, когда я выталкиваю и выталкиваю фрагменты с конца FragmentStatePagerAdapter. Достаточно умен, чтобы заметить, что элементы меняются, и достаточно умен, чтобы создавать и заменять фрагмент при изменении элемента. Но недостаточно умен, чтобы отбросить состояние фрагмента, или недостаточно умен, чтобы обрезать внутренне сохраненные состояния фрагмента при изменении размера адаптера. Итак, когда вы вставляете элемент и вставляете новый в конец, фрагмент для нового элемента получает сохраненное состояние фрагмента для старого элемента, что вызвало абсолютный хаос в моем коде. Мои фрагменты несут данные, для получения которых из Интернета может потребоваться много работы, поэтому не сохранять состояние действительно не вариант.

У меня нет чистого обходного пути. Я использовал что-то вроде этого:

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

Неидеальное решение, потому что новый экземпляр фрагмента по-прежнему получает пакет savedState, но, по крайней мере, он не содержит устаревших данных.

Робин Дэвис
источник
0

Поскольку люди не склонны читать комментарии, вот ответ, который в основном дублирует то, что я написал здесь :

Основная причина проблемы заключается в том, что система Android не вызывает getItemфактически отображаемых фрагментов, а instantiateItem. Этот метод сначала пытается найти и повторно использовать экземпляр фрагмента для данной вкладки в FragmentManager. Вызывается только в случае сбоя этого поиска (что происходит только при первом FragmentManagerсоздании) getItem. По очевидным причинам не следует воссоздавать фрагменты (которые могут быть тяжелыми), например, каждый раз, когда пользователь поворачивает свое устройство.
Чтобы решить эту проблему, вместо того, чтобы создавать фрагменты с помощью Fragment.instantiateв своей деятельности, вы должны сделать это, pagerAdapter.instantiateItemи все эти вызовы должны быть окружены startUpdate/finishUpdateвызовами методов, которые запускают / фиксируют транзакцию фрагмента соответственно.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
источник