підтримка FragmentPagerAdapter містить посилання на старі фрагменти


109

ОСТАННЯ ІНФОРМАЦІЯ:

я звузив свою проблему до проблеми з тим, що fragmentManager зберігає екземпляри старих фрагментів, і моя панель перегляду не синхронізована з моїм FragmentManager. Дивіться цю проблему ... http://code.google.com/p/android/isissue/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 повертається до нуля. Ось метод, який викликає інший фрагмент ...

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

Я вважаю, що при знищенні програми потім створюється знову, а не просто запускає додаток до фрагмента за замовчуванням, додаток запускається, а потім viewpager переходить на останню відому сторінку. Це здається дивним, чи не слід додаток просто завантажувати фрагмент за замовчуванням?


27
Фрагмент Android відстій!
Хамідреза Садег

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)після того, як усе це відбулося, повертає вам фрагмент у вашому списку, який ще потрібно додати до менеджера фрагментів , і тому він не матиме посилання на активність. Я б сказав, що ваш метод оновлення повинен змінити структуру даних загального користування (можливо, керованою діяльністю), і тоді, коли ви переходите на певну сторінку, вона може малювати себе на основі цих оновлених даних.


1
Мені подобається ваше рішення, оскільки воно дуже елегантне і, швидше за все, переробляє мій код, проте, як ви сказали, я хотів би зберегти 100% логіки та даних у фрагменті. Для вашого рішення знадобиться велика кількість збереження всіх даних у FragmentActivity, а потім використовувати кожен фрагмент просто для обробки логіки відображення. Як я вже говорив, я вважаю за краще це, але взаємодія між фрагментами є надто важкою, і це може набридливо управляти. Все одно дякую за ваше детальне пояснення. Ви пояснили це набагато краще, ніж я міг.
Мерісі

28
Коротко: ніколи не
посилайтеся

2
Отже, як екземпляр Activity отримує доступ до екземпляра FragmentPagerAdapter, якщо він не створив екземпляр FragmentPagerAdapter? Чи не стануть майбутні екземпляри лише інстанціювати FragmentPagerAdapter та всі його фрагменти? Чи повинен FragmentPagerAdapter реалізувати всі інтерфейсні фрагменти для управління зв’язком між фрагментами?
Ерік Х.

вислів "Отримати ручку на ньому також досить складно отримати посилання на нього, тому що він був доданий з невідомим вам тегом. Це за задумом; ви не хочете возитися з фрагментами, якими керує пейджер перегляду" . " неправдиво. отримати довідку дуже просто, зателефонувавши, instantiateItemі ви насправді повинні зробити це у onCreateсвоїй діяльності. докладніше тут: stackoverflow.com/questions/14035090 / ...
morgwai

загалом, це хитрощі самостійно створювати фрагменти без завантаження, оскільки в сценарії "знищити та відтворити" ти можеш перетворити окремий фрагмент на той, який насправді відтворений та показаний
hmac

108

Я знайшов просте рішення, яке працювало на мене.

Зробіть, щоб ваш адаптер фрагмента розширював FragmentStatePagerAdapter замість FragmentPagerAdapter та метод заміни onSave, щоб повернути нуль

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

Це заважає андроїду відтворити фрагмент


Через день я знайшов інше і краще рішення.

Зателефонуйте setRetainInstance(true)до всіх своїх фрагментів і десь збережіть посилання на них. Я робив це в статичній змінній у своїй діяльності, тому що це оголошено як одиночне завдання і фрагменти можуть залишатися однаковими весь час.

Таким чином андроїд не відтворює фрагменти, а використовує ті самі екземпляри.


4
Дякую, спасибі, спасибі, я справді не маю слів, щоб подякувати тобі Мік, я переслідував цю проблему за останні 10 днів і спробував так багато методів. Але ці чотири магічні лінії врятували мені життя :)

7
статичне посилання на фрагменти та / або діяльність - дуже ризикована справа, оскільки це може дуже легко спричинити витік пам'яті. Звичайно, якщо ви обережні, ви можете впоратися з цим досить легко, встановивши їх на нуль, коли це вже не потрібно.
андроїд розробник

Це працювало для мене. Фрагменти та відповідні представлення зберігають посилання після збоїв і перезавантаження програми. Дякую!
підопічна

Я використовую setRetainInstance(true)с FragmentPagerAdapter. Все працює добре. Але коли я обертаю пристрій, у адаптері все ще є фрагменти, але фрагменти не показані. Методи життєвого циклу фрагментів також не називаються. Хтось може допомогти?
Йонас

1
ти врятував мій день !!! Поки що шукали цю помилку протягом 3 днів. У мене був Viewpager з 2 фрагментами всередині SingleTask Activity та з увімкненим прапором "Dont Keep Activity". Дуже дякую!!!!
матриця

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);так, щоб я міг вручну додавати значення до пакету збереженогоInstanceState.

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

    super.onSaveInstanceState(outState);
}

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


У мене є проблема, схожа на вас, але я не зовсім зрозумів ваше пояснення, чи можете ви надати більше деталей? Я також є і адаптер, який зберігає фрагменти в списку, але коли я відновляю свою програму з останніх програм, збої програм, оскільки є якийсь окремий фрагмент. Проблема тут stackoverflow.com/questions/11631408/…
Георгій Гобозов,

3
Яке ваше "моє" у фрагменті, що посилається?
Джош

Ми всі знаємо, що це рішення не є гарним підходом, але це найпростіший спосіб використання FragmentByTagу ViewPager.
Youngjae

ось спосіб , як фрагменти доступу , не покладаючись на сумісність з внутрішнім шляхом присвоєння назв stackoverflow.com/questions/14035090 / ...
morgwai

26

Глобальне перевірене робоче рішення.

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

Не робіть цього:

new PagerAdapter(getSupportFragmentManager(), fragments);

Зробити це:

new PagerAdapter(getChildFragmentManager() , fragments);


6
це стане можливим лише в тому випадку, якщо ви розміщуєте (і інстанціюєте) pagerAdapter всередині фрагмента, а не всередині вашої діяльності. У цьому випадку ви маєте рацію, за замовчуванням слід використовувати childFragmentManager. Використання SupportFragmentManager було б помилково за замовчуванням
Klitos G.

Дуже точно. Вирішив мою проблему без використання хаків. Дякую! Моя версія 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 викликається у фрагментіActivity. onAttach викликається безпосередньо перед створенням FragmentActivity.
Мерісі

Хм дякую, але проблема все ще зберігається. Я замість цього встановив ім'я зворотного дзвінка. Чи не можу я вкласти у Фрагмент логіку? У мене є фрагменти служб, що знімають вогонь, та інших елементів, які потребують посилання на діяльність. Мені доведеться перенести всю свою логіку програм на основну діяльність, щоб уникнути проблеми, що виникає. Це здається трохи зайвим.
Мерісі

Гаразд, я просто перевірив це повністю і прокоментував весь свій код ... за винятком зворотного дзвінка, щоб змінити назву. Коли програма запущена вперше, і я переходжу на цей екран, ім'я заголовка змінюється np. Після завантаження декількох додатків потім повернення заголовок більше не змінюється. Схоже, зворотній зв'язок надходить у старий екземпляр активності. Я не можу придумати ще одне пояснення
Морісі

я змусив це зважати на проблему з fragmentManager та проблемою ... code.google.com/p/android/isissue/detail?id=19211 . У мене ще немає поняття, як це вирішити
Морісі

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

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 (), який буде викликаний для кожного з ваших фрагментів.

Сподіваюся, що хтось допомагає.


Що робити, якщо у вас багато фрагментів? Ви дійсно вважаєте за краще зберегти посилання на кожну з них? Хіба це не погано для пам’яті?
андроїд розробник

Все залежить від того, чого саме ви намагаєтеся досягти. Зазвичай за допомогою цих пейджерів перегляду ви не вміщуєте більше трьох фрагментів одночасно. Той, що посередині, і той, що знаходиться з кожного боку. Для того, що я хочу, мені дійсно потрібно мати посилання на фрагменти, але я знаю, що мені потрібно розібратися лише з двома.
Ланселот

0

Оскільки FragmentManager піклується про відновлення ваших фрагментів, як тільки буде викликаний метод onResume (), фрагмент викликає активність та додає себе до списку. У моєму випадку я все це зберігаю в моїй реалізації PagerAdapter. Кожен фрагмент знає свою позицію, оскільки він додається до аргументів фрагмента про створення. Тепер, коли мені потрібно маніпулювати фрагментом за певним індексом, все, що мені потрібно зробити, - це використовувати список з мого адаптера.

Нижче наводиться приклад адаптера для користувальницького ViewPager, який буде рости фрагмент у міру його переміщення у фокус, і масштабувати його вниз, коли він рухається поза фокусом. Окрім класів Adapter and Fragment, у мене тут все, що потрібно, щоб батьківська діяльність мала можливість посилатися на змінну адаптера, і ви налаштовані.

Перехідник

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

Моє рішення: я майже кожен вид встановлюю як 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, як я :)


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

}

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

Недосконале рішення, оскільки новий екземпляр фрагмента все ще отримує пакет збережених держав, але, принаймні, він не містить застарілих даних.


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
        }
}
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.