FragmentPagerAdapter не восстанавливает фрагменты при изменении ориентации

РЕДАКТИРОВАТЬ: я узнал, что действие сохраняет экземпляр, но сохраненные данные фрагментов не соответствуют сохраненному состоянию действия. Я сохраняю текущее время в outState, но оно не доходит до конца, так как действие не имеет ничего в своем SavedInstanceState на время и возвращает 'null' на время, если я печатаю его в logcat. ..

Я создаю приложение со встроенным таймером обратного отсчета и обратного отсчета. Основное действие по размещению таймеров — это FragmentActivity, в котором размещается FragementPagerAdapter, который раздувает два фрагмента в коде (я не даю идентификатор фрагментам в XML поскольку они не определены как фрагменты в XML-файле). Все отлично работает до тех пор, пока не изменится ориентация, а затем деятельность выглядит так, будто она теряет контакт со старыми фрагментами и просто выбирает создание новых. Это означает, что любой текущий обратный отсчет теряется, и любое выбранное время также теряется при изменении конфигурации. Мне нужно будет вести подсчет (если он запущен) и любые отображаемые в данный момент числа....

Я знаю, что адаптер должен обрабатывать эти вещи самостоятельно, и я устанавливаю SetRetainInstance(true) как в OnCreate, так и в OnCreateView.

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

В некоторых других решениях я переопределяю instanceItem, но кажется, что он используется только для сброса обратных вызовов, другие изменяют настройку в манифесте (неодобрительно).... Другие решения выглядят так, как будто я все правильно настроил. .но очевидно, я что-то путаю по пути. Я даю только код для FragmentActivity, FragementPagerAdapter и немного кода фрагмента, так как я не хочу спамить сообщение кодом, который может не быть проблемой.

Фрагментактивность

public class Timer_Main extends FragmentActivity {
    ViewPager pager;
    Timer_Pager mAdapter;

    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.timer_pager);

        mAdapter = new Timer_Pager(this, getSupportFragmentManager());

        pager = (ViewPager) findViewById(R.id.timer_pager_display);
        pager.setAdapter(mAdapter);

    }

    public static String getTitle(Context ctxt, int position) {
                // TODO Auto-generated method stub
        return null;
    }

    @Override
    protected void onSaveInstanceState(Bundle outState) {
        super.onSaveInstanceState(outState);
        outState.putInt("pageItem", pager.getCurrentItem());

    }

    @Override
    protected void onRestoreInstanceState(Bundle savedInstanceState) {
        super.onRestoreInstanceState(savedInstanceState);

    }
}    

FragementPagerAdapter

public class Timer_Pager extends FragmentPagerAdapter{
    Context ctxt = null;
    public Timer_Pager(Context ctxt, FragmentManager fm) {
        super(fm);
        this.ctxt = ctxt;
        }


    @Override
    public Fragment getItem(int position) {

        switch (position) {
        case 0: {
            return(Countdown_Fragment.newInstance());
        }
        case 1:
            return(Countup_Fragment.newInstance());
        }
        return null;
    }

    @Override
    public String getPageTitle(int position) {

        switch (position) {
        case 0: 
            return(String.format(ctxt.getString(R.string.Countdown_label)));
        case 1: 
            return(String.format(ctxt.getString(R.string.Countup_label)));
        }
        return null;
    }

    @Override
    public int getCount() {
        // doing only three pages, right now...one for countdown, one for countup,         and one for Tabata.
        return 2;
    }

    private static String makeFragmentName(int viewId, int position)
    {
         return "android:switcher:" + viewId + ":" + position;
    }
}    

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

public class Countdown_Fragment extends Fragment implements OnClickListener {
    Calendar CountdownTime = Calendar.getInstance();
    static AutoResizeTextView tv;
    static ImageButton HoursUp;
    static ImageButton HoursDown;
    static ImageButton MinutesUp;
    static ImageButton MinutesDown;
    static ImageButton SecondsUp;
    static ImageButton SecondsDown;
    static ImageButton Reset;
    static ImageButton StartPausecount;
    static ImageButton Stopcount;
    static Boolean Arewecounting = false;
    static Boolean Arewecountingdown = false;
    static AutoResizeTextView ThreetwooneGO;
    static MyCountdownTimer Countdown_Timer_Activity;
    ThreetwooneCount Start_countdown;
    static Long MillstoPass;

    static Countdown_Fragment newInstance() {

        Countdown_Fragment frag = new Countdown_Fragment();
        return (frag);
    }

    @Override
    public void onSaveInstanceState(Bundle outState) {

        outState.putString("CurrentTimer", (String) tv.getText());
        Log.v("status", "saved fragment state" + tv.getText());
        super.onSaveInstanceState(outState);
    }

    @Override
    public void onAttach(Activity activity) {
        super.onAttach(activity);

    }

    @Override
    public void onCreate(Bundle savedInstanceState) {
        setRetainInstance(true);
        super.onCreate(savedInstanceState);
    }

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

            setRetainInstance(true);
            View result = inflater.inflate(R.layout.countdown_timer_layout,
                container, false);
        tv = (AutoResizeTextView) result.findViewById(R.id.timer_textview);
        tv.setOnClickListener(new View.OnClickListener() {
            public void onClick(View arg0) {
                new TimePickerDialog(getActivity(), t, 0, 0, true).show();
            }
        });
        if (savedInstanceState != null) {
            Log.v("status", "fragment is NOT empty");
            tv.setText(savedInstanceState.getString("CurrentTimer",
                    tv.toString()));
        } else {
            Log.v("status", "fragment is empty");
        //  tv.setText("00:00:00");

        }
        tv.resizeText();

        ThreetwooneGO = (AutoResizeTextView) result
                .findViewById(R.id.timer_countdown_text);

        HoursUp = (ImageButton) result.findViewById(R.id.hours_up);
        HoursDown = (ImageButton) result.findViewById(R.id.hours_down);
        MinutesUp = (ImageButton) result.findViewById(R.id.minutes_up);
        MinutesDown = (ImageButton) result.findViewById(R.id.minutes_down);
        SecondsUp = (ImageButton) result.findViewById(R.id.seconds_up);
        SecondsDown = (ImageButton) result.findViewById(R.id.seconds_down);
        Reset = (ImageButton) result.findViewById(R.id.reset);
        StartPausecount = (ImageButton) result
                .findViewById(R.id.startpausecount);
        Stopcount = (ImageButton) result.findViewById(R.id.stopcount);

        HoursUp.setOnClickListener(this);
        HoursDown.setOnClickListener(this);
        SecondsUp.setOnClickListener(this);
        SecondsDown.setOnClickListener(this);
        MinutesUp.setOnClickListener(this);
        MinutesDown.setOnClickListener(this);
        Reset.setOnClickListener(this);
        StartPausecount.setOnClickListener(this);
        Stopcount.setOnClickListener(this);

        return (result);
    }

    public void chooseTime(View v) {
        new TimePickerDialog(getActivity(), t, 0, 0, true).show();
    }

    TimePickerDialog.OnTimeSetListener t = new TimePickerDialog.OnTimeSetListener() {
        public void onTimeSet(TimePicker view, int hourOfDay, int minute) {
            CountdownTime.set(Calendar.HOUR_OF_DAY, hourOfDay);
            CountdownTime.set(Calendar.MINUTE, minute);
            CountdownTime.set(Calendar.SECOND, 0);
            CountdownTime.set(Calendar.MILLISECOND, 0);

            updateLabel();
        }
    };

    private void updateLabel() {

        String entiretime;
        entiretime = String.format("%tT", CountdownTime);
        tv.setText(entiretime);
    }

Вот мой таймер, который я использую для обратного отсчета....

public class MyCountdownTimer {

private long millisInFuture;
private long countDownInterval;
Countdown_Fragment ctxt;
AutoResizeTextView Timer_edit;
private volatile boolean IsStopped = false;

public MyCountdownTimer(long pMillisInFuture, long pCountDownInterval,
        AutoResizeTextView Artv) {
    this.millisInFuture = pMillisInFuture;
    this.countDownInterval = pCountDownInterval;

    Timer_edit = Artv;

}

public void Start() {

    final Handler handler = new Handler();
    final Runnable counter = new Runnable() {

        public void run() {
            if (IsStopped == false) {
                if (millisInFuture <= 0) {
                    Countdown_Fragment.done_counting();
                } else {
                    long sec = millisInFuture / 1000;
                    Timer_edit.setText(String.format("%02d:%02d:%02d",
                            sec / 3600, (sec % 3600) / 60, (sec % 60)));
                    millisInFuture -= countDownInterval;
                    handler.postDelayed(this, countDownInterval);
                }
            }
        }
    };

    handler.postDelayed(counter, countDownInterval);
}

public void Cancel() {
    IsStopped = true;
    Timer_edit = null;
    Log.v("status", "Main Timer cancelled");
    // this.ctxt = null;

}
public long whatisourcount(){
    return(millisInFuture);
}
}

person Jeff DePiazza    schedule 25.08.2013    source источник


Ответы (1)


Как оказалось, RetainInstance вызывал конфликт между собой и ViewPager/Adapter/FragmentManager, выполняющими свои функции. Его удаление привело к тому, что пейджер правильно перестроил фрагмент, включая TextView, который у меня был, чего раньше не было. Я также начинаю получать Bundle в OnCreateView, где раньше он всегда был нулевым, а для RetainInstance установлено значение True.

Мне пришлось удалить RetainInstance и использовать OnSaveInstanceState и OnCreateView, чтобы передать текущее состояние фрагмента до его уничтожения, а затем повторно создать его в OnCreateView, чтобы сбросить фрагмент в его состояние до его уничтожения.

Я надеялся, что Runnable, который я использовал для обратного отсчета, выживет, или я смогу снова подключить его, но я не мог найти способ. Мне пришлось сохранить текущий счет в миллисекундах и вернуться к фрагменту, чтобы продолжить с того места, где он остановился. Это не так уж важно, но мне любопытно посмотреть, сможете ли вы действительно снова прикрепить все эти вещи. Runnable по-прежнему продолжается после изменения конфигурации, но он больше ничего не обновляет в пользовательском интерфейсе, поэтому я пытаюсь отменить обратные вызовы и обнулить их, когда я нахожусь внутри OnSaveInstanceState.

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

person Jeff DePiazza    schedule 30.08.2013
comment
Привет .. У меня похожая проблема. Есть ли способ сохранить заданный экземпляр и заставить его работать, потому что в противном случае я должен переписать всю свою логику. Заранее спасибо :) - person Nick; 05.09.2014
comment
Не смог найти обходной путь - person Jeff DePiazza; 03.10.2014