pageradapter - viewpager android пример




ViewPager и фрагменты-какой способ сохранить состояние фрагмента? (7)

Если у кого-то возникают проблемы с их FragmentStatePagerAdapter, они не восстанавливают состояние своих фрагментов ... т.е. ... Fragments создаются FragmentStatePagerAdapter вместо того, чтобы восстанавливать их из состояния ...

Убедитесь, что вы вызываете ViewPager.setOffscreenPageLimit() ПЕРЕД вызовом ViewPager.setAdapeter(fragmentStatePagerAdapter)

При вызове ViewPager.setOffscreenPageLimit() ... ViewPager немедленно посмотрит на свой адаптер и попытается получить его фрагменты. Это может произойти до того, как у ViewPager появилась возможность восстановить фрагменты из savedInstanceState (таким образом создавая новые фрагменты, которые нельзя повторно инициализировать из SavedInstanceState, потому что они новы).

Фрагменты кажутся очень приятными для разделения логики пользовательского интерфейса на некоторые модули. Но вместе с ViewPager его жизненный цикл по-прежнему остается туманным для меня. Поэтому мысли Гуру очень нужны!

редактировать

См. Немой вариант ниже ;-)

Объем

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

проблема

Когда активность воссоздается (например, при изменении ориентации), так же, как и фрагменты ViewPager . Код (вы найдете ниже) говорит, что каждый раз, когда создается действие, я пытаюсь создать новый ViewPager фрагментов ViewPager же, как и фрагменты (может быть, это проблема), но FragmentManager уже имеет все эти фрагменты где-то (где?) и запускает механизм отдыха для них. Таким образом, механизм отдыха вызывает «старый» фрагмент onAttach, onCreateView и т. Д. С моим вызовом интерфейса обратного вызова для инициирования данных с помощью реализованного метода Activity. Но этот метод указывает на вновь созданный фрагмент, созданный с помощью метода onCreate Activity.

вопрос

Возможно, я использую неправильные шаблоны, но даже книга Android 3 Pro не имеет большого значения. Итак, пожалуйста , дайте мне один-два удара и укажите, как это сделать правильно. Большое спасибо!

Код

Основная деятельность

public class DashboardActivity extends BasePagerActivity implements OnMessageListActionListener {

private MessagesFragment mMessagesFragment;

@Override
protected void onCreate(Bundle savedInstanceState) {
    Logger.d("Dash onCreate");
    super.onCreate(savedInstanceState);

    setContentView(R.layout.viewpager_container);
    new DefaultToolbar(this);

    // create fragments to use
    mMessagesFragment = new MessagesFragment();
    mStreamsFragment = new StreamsFragment();

    // set titles and fragments for view pager
    Map<String, Fragment> screens = new LinkedHashMap<String, Fragment>();
    screens.put(getApplicationContext().getString(R.string.dashboard_title_dumb), new DumbFragment());
    screens.put(getApplicationContext().getString(R.string.dashboard_title_messages), mMessagesFragment);

    // instantiate view pager via adapter
    mPager = (ViewPager) findViewById(R.id.viewpager_pager);
    mPagerAdapter = new BasePagerAdapter(screens, getSupportFragmentManager());
    mPager.setAdapter(mPagerAdapter);

    // set title indicator
    TitlePageIndicator indicator = (TitlePageIndicator) findViewById(R.id.viewpager_titles);
    indicator.setViewPager(mPager, 1);

}

/* set of fragments callback interface implementations */

@Override
public void onMessageInitialisation() {

    Logger.d("Dash onMessageInitialisation");
    if (mMessagesFragment != null)
        mMessagesFragment.loadLastMessages();
}

@Override
public void onMessageSelected(Message selectedMessage) {

    Intent intent = new Intent(this, StreamActivity.class);
    intent.putExtra(Message.class.getName(), selectedMessage);
    startActivity(intent);
}

BasePagerActivity aka helper

public class BasePagerActivity extends FragmentActivity {

BasePagerAdapter mPagerAdapter;
ViewPager mPager;
}

адаптер

public class BasePagerAdapter extends FragmentPagerAdapter implements TitleProvider {

private Map<String, Fragment> mScreens;

public BasePagerAdapter(Map<String, Fragment> screenMap, FragmentManager fm) {

    super(fm);
    this.mScreens = screenMap;
}

@Override
public Fragment getItem(int position) {

    return mScreens.values().toArray(new Fragment[mScreens.size()])[position];
}

@Override
public int getCount() {

    return mScreens.size();
}

@Override
public String getTitle(int position) {

    return mScreens.keySet().toArray(new String[mScreens.size()])[position];
}

// hack. we don't want to destroy our fragments and re-initiate them after
@Override
public void destroyItem(View container, int position, Object object) {

    // TODO Auto-generated method stub
}

}

Фрагмент

public class MessagesFragment extends ListFragment {

private boolean mIsLastMessages;

private List<Message> mMessagesList;
private MessageArrayAdapter mAdapter;

private LoadMessagesTask mLoadMessagesTask;
private OnMessageListActionListener mListener;

// define callback interface
public interface OnMessageListActionListener {
    public void onMessageInitialisation();
    public void onMessageSelected(Message selectedMessage);
}

@Override
public void onAttach(Activity activity) {
    super.onAttach(activity);
    // setting callback
    mListener = (OnMessageListActionListener) activity;
    mIsLastMessages = activity instanceof DashboardActivity;

}

@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
    inflater.inflate(R.layout.fragment_listview, container);
    mProgressView = inflater.inflate(R.layout.listrow_progress, null);
    mEmptyView = inflater.inflate(R.layout.fragment_nodata, null);
    return super.onCreateView(inflater, container, savedInstanceState);
}

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

    // instantiate loading task
    mLoadMessagesTask = new LoadMessagesTask();

    // instantiate list of messages
    mMessagesList = new ArrayList<Message>();
    mAdapter = new MessageArrayAdapter(getActivity(), mMessagesList);
    setListAdapter(mAdapter);
}

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

public void onListItemClick(ListView l, View v, int position, long id) {
    Message selectedMessage = (Message) getListAdapter().getItem(position);
    mListener.onMessageSelected(selectedMessage);
    super.onListItemClick(l, v, position, id);
}

/* public methods to load messages from host acitivity, etc... */
}

Решение

Глубокое решение состоит в том, чтобы сохранить фрагменты внутри onSaveInstanceState (активности хозяина) с помощью putFragment и получить их внутри onCreate через getFragment. Но у меня все еще странное ощущение, что все должно работать не так ... Смотрите код ниже:

    @Override
protected void onSaveInstanceState(Bundle outState) {

    super.onSaveInstanceState(outState);
    getSupportFragmentManager()
            .putFragment(outState, MessagesFragment.class.getName(), mMessagesFragment);
}

protected void onCreate(Bundle savedInstanceState) {
    Logger.d("Dash onCreate");
    super.onCreate(savedInstanceState);

    ...
    // create fragments to use
    if (savedInstanceState != null) {
        mMessagesFragment = (MessagesFragment) getSupportFragmentManager().getFragment(
                savedInstanceState, MessagesFragment.class.getName());
                StreamsFragment.class.getName());
    }
    if (mMessagesFragment == null)
        mMessagesFragment = new MessagesFragment();
    ...
}

Когда FragmentPagerAdapter добавляет фрагмент в FragmentManager, он использует специальный тег, основанный на конкретной позиции, в которой будет размещен фрагмент. FragmentPagerAdapter.getItem(int position) вызывается только тогда, когда фрагмент для этой позиции не существует. После поворота Android заметит, что он уже создал / сохранил фрагмент для этой конкретной позиции и поэтому просто пытается подключиться к нему с помощью FragmentManager.findFragmentByTag() вместо создания нового. Все это становится бесплатным при использовании FragmentPagerAdapter и поэтому обычно имеет код инициализации фрагмента внутри метода getItem(int) .

Даже если мы не использовали FragmentPagerAdapter , не рекомендуется создавать новый фрагмент каждый раз в Activity.onCreate(Bundle) . Как вы заметили, когда фрагмент добавлен в FragmentManager, он будет воссоздан для вас после поворота, и нет необходимости добавлять его снова. Это является общей причиной ошибок при работе с фрагментами.

Обычный подход при работе с фрагментами заключается в следующем:

protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);

    ...

    CustomFragment fragment;
    if (savedInstanceState != null) {
        fragment = (CustomFragment) getSupportFragmentManager().findFragmentByTag("customtag");
    } else {
        fragment = new CustomFragment();
        getSupportFragmentManager().beginTransaction().add(R.id.container, fragment, "customtag").commit(); 
    }

    ...

}

При использовании FragmentPagerAdapter мы отказываемся от управления фрагментами к адаптеру и не должны выполнять вышеуказанные шаги. По умолчанию он будет предварительно загружать один фрагмент спереди и сзади текущей позиции (хотя он не уничтожит их, если вы не используете FragmentStatePagerAdapter ). Это контролируется ViewPager.setOffscreenPageLimit(int) . Из-за этого прямое указание методов на фрагменты вне адаптера не гарантируется, поскольку они могут даже не быть живыми.

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

Другой подход заключается в том, чтобы переопределить FragmentPageAdapter.instantiateItem(View, int) и сохранить ссылку на фрагмент, возвращенный из супервызовов, прежде чем возвращать его (у него есть логика, чтобы найти фрагмент, если он уже присутствует).

Для более полной картины взгляните на некоторые из источников FragmentPagerAdapter (короткий) и ViewPager (long).


Что такое BasePagerAdapter ? Вы должны использовать один из стандартных адаптеров пейджера - либо FragmentPagerAdapter либо FragmentStatePagerAdapter , в зависимости от того, хотите ли вы, чтобы фрагменты, которые больше не нужны ViewPager либо поддерживаются (первые), либо сохраняются в их состоянии (последним) и повторно - если необходимо, снова создайте.

Пример кода для использования ViewPager можно найти here

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


Чтобы получить фрагменты после изменения ориентации, вы должны использовать .getTag ().

    getSupportFragmentManager().findFragmentByTag("android:switcher:" + viewPagerId + ":" + positionOfItemInViewPager)

Для немного большей обработки я написал свой собственный ArrayList для моего PageAdapter, чтобы получить фрагмент с помощью viewPagerId и FragmentClass в любой позиции:

public class MyPageAdapter extends FragmentPagerAdapter implements Serializable {
private final String logTAG = MyPageAdapter.class.getName() + ".";

private ArrayList<MyPageBuilder> fragmentPages;

public MyPageAdapter(FragmentManager fm, ArrayList<MyPageBuilder> fragments) {
    super(fm);
    fragmentPages = fragments;
}

@Override
public Fragment getItem(int position) {
    return this.fragmentPages.get(position).getFragment();
}

@Override
public CharSequence getPageTitle(int position) {
    return this.fragmentPages.get(position).getPageTitle();
}

@Override
public int getCount() {
    return this.fragmentPages.size();
}


public int getItemPosition(Object object) {
    //benötigt, damit bei notifyDataSetChanged alle Fragemnts refrehsed werden

    Log.d(logTAG, object.getClass().getName());
    return POSITION_NONE;
}

public Fragment getFragment(int position) {
    return getItem(position);
}

public String getTag(int position, int viewPagerId) {
    //getSupportFragmentManager().findFragmentByTag("android:switcher:" + R.id.shares_detail_activity_viewpager + ":" + myViewPager.getCurrentItem())

    return "android:switcher:" + viewPagerId + ":" + position;
}

public MyPageBuilder getPageBuilder(String pageTitle, int icon, int selectedIcon, Fragment frag) {
    return new MyPageBuilder(pageTitle, icon, selectedIcon, frag);
}


public static class MyPageBuilder {

    private Fragment fragment;

    public Fragment getFragment() {
        return fragment;
    }

    public void setFragment(Fragment fragment) {
        this.fragment = fragment;
    }

    private String pageTitle;

    public String getPageTitle() {
        return pageTitle;
    }

    public void setPageTitle(String pageTitle) {
        this.pageTitle = pageTitle;
    }

    private int icon;

    public int getIconUnselected() {
        return icon;
    }

    public void setIconUnselected(int iconUnselected) {
        this.icon = iconUnselected;
    }

    private int iconSelected;

    public int getIconSelected() {
        return iconSelected;
    }

    public void setIconSelected(int iconSelected) {
        this.iconSelected = iconSelected;
    }

    public MyPageBuilder(String pageTitle, int icon, int selectedIcon, Fragment frag) {
        this.pageTitle = pageTitle;
        this.icon = icon;
        this.iconSelected = selectedIcon;
        this.fragment = frag;
    }
}

public static class MyPageArrayList extends ArrayList<MyPageBuilder> {
    private final String logTAG = MyPageArrayList.class.getName() + ".";

    public MyPageBuilder get(Class cls) {
        // Fragment über FragmentClass holen
        for (MyPageBuilder item : this) {
            if (item.fragment.getClass().getName().equalsIgnoreCase(cls.getName())) {
                return super.get(indexOf(item));
            }
        }
        return null;
    }

    public String getTag(int viewPagerId, Class cls) {
        // Tag des Fragment unabhängig vom State z.B. nach bei Orientation change
        for (MyPageBuilder item : this) {
            if (item.fragment.getClass().getName().equalsIgnoreCase(cls.getName())) {
                return "android:switcher:" + viewPagerId + ":" + indexOf(item);
            }
        }
        return null;
    }
}

Поэтому просто создайте MyPageArrayList с фрагментами:

    myFragPages = new MyPageAdapter.MyPageArrayList();

    myFragPages.add(new MyPageAdapter.MyPageBuilder(
            getString(R.string.widget_config_data_frag),
            R.drawable.ic_sd_storage_24dp,
            R.drawable.ic_sd_storage_selected_24dp,
            new WidgetDataFrag()));

    myFragPages.add(new MyPageAdapter.MyPageBuilder(
            getString(R.string.widget_config_color_frag),
            R.drawable.ic_color_24dp,
            R.drawable.ic_color_selected_24dp,
            new WidgetColorFrag()));

    myFragPages.add(new MyPageAdapter.MyPageBuilder(
            getString(R.string.widget_config_textsize_frag),
            R.drawable.ic_settings_widget_24dp,
            R.drawable.ic_settings_selected_24dp,
            new WidgetTextSizeFrag()));

и добавьте их в viewPager:

    mAdapter = new MyPageAdapter(getSupportFragmentManager(), myFragPages);
    myViewPager.setAdapter(mAdapter);

после этого вы можете получить после изменения ориентации правильный фрагмент, используя его класс:

        WidgetDataFrag dataFragment = (WidgetDataFrag) getSupportFragmentManager()
            .findFragmentByTag(myFragPages.getTag(myViewPager.getId(), WidgetDataFrag.class));

Я придумал это простое и элегантное решение. Он предполагает, что деятельность отвечает за создание фрагментов, и адаптер просто служит им.

Это код адаптера (здесь нет ничего странного, кроме факта, что mFragments - это список фрагментов, поддерживаемых Activity)

class MyFragmentPagerAdapter extends FragmentStatePagerAdapter {

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

    @Override
    public Fragment getItem(int position) {
        return mFragments.get(position);
    }

    @Override
    public int getCount() {
        return mFragments.size();
    }

    @Override
    public int getItemPosition(Object object) {
        return POSITION_NONE;
    }

    @Override
    public CharSequence getPageTitle(int position) {
        TabFragment fragment = (TabFragment)mFragments.get(position);
        return fragment.getTitle();
    }
} 

Вся проблема этого потока заключается в получении ссылок на «старые» фрагменты, поэтому я использую этот код в onCreate Activity.

    if (savedInstanceState!=null) {
        if (getSupportFragmentManager().getFragments()!=null) {
            for (Fragment fragment : getSupportFragmentManager().getFragments()) {
                mFragments.add(fragment);
            }
        }
    }

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


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

Мое дело - я создаю / добавляю страницы динамически и перемещая их в ViewPager, но когда он повернут (onConfigurationChange), я получаю новую страницу, потому что, конечно, OnCreate вызывается снова. Но я хочу сохранить ссылку на все страницы, созданные до поворота.

Проблема. У меня нет уникальных идентификаторов для каждого создаваемого фрагмента, поэтому единственным способом ссылки было как-то сохранить ссылки в массиве, который будет восстановлен после изменения вращения / конфигурации.

Обходной путь. Ключевой концепцией было то, что Activity (который отображает фрагменты) также управляет массивом ссылок на существующие фрагменты, поскольку эта активность может использовать Bundles в onSaveInstanceState

public class MainActivity extends FragmentActivity

Поэтому в рамках этой операции я объявляю частного участника для отслеживания открытых страниц

private List<Fragment> retainedPages = new ArrayList<Fragment>();

Это обновляется каждый раз, когда вызывается и восстанавливаетсяSaveInstanceState в onCreate

@Override
protected void onSaveInstanceState(Bundle outState) {
    retainedPages = _adapter.exportList();
    outState.putSerializable("retainedPages", (Serializable) retainedPages);
    super.onSaveInstanceState(outState);
}

... поэтому, как только он будет сохранен, он может быть восстановлен ...

@Override
public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);

    if (savedInstanceState != null) {
        retainedPages = (List<Fragment>) savedInstanceState.getSerializable("retainedPages");
    }
    _mViewPager = (CustomViewPager) findViewById(R.id.viewPager);
    _adapter = new ViewPagerAdapter(getApplicationContext(), getSupportFragmentManager());
    if (retainedPages.size() > 0) {
        _adapter.importList(retainedPages);
    }
    _mViewPager.setAdapter(_adapter);
    _mViewPager.setCurrentItem(_adapter.getCount()-1);
}

Это были необходимые изменения в основной деятельности, поэтому мне нужны были члены и методы в моем FragmentPagerAdapter, чтобы это работало, поэтому внутри

public class ViewPagerAdapter extends FragmentPagerAdapter

идентичная конструкция (как показано выше в MainActivity)

private List<Fragment> _pages = new ArrayList<Fragment>();

и эта синхронизация (как используется выше в onSaveInstanceState) поддерживается, в частности, методами

public List<Fragment> exportList() {
    return _pages;
}

public void importList(List<Fragment> savedPages) {
    _pages = savedPages;
}

И наконец, в классе фрагментов

public class CustomFragment extends Fragment

для того, чтобы все это работало, произошли два изменения: сначала

public class CustomFragment extends Fragment implements Serializable

а затем добавьте это в onCreate, чтобы фрагменты не были уничтожены

setRetainInstance(true);

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


добавлять:

   @SuppressLint("ValidFragment")

перед вашим классом.

он не работает, делает что-то вроде этого:

@SuppressLint({ "ValidFragment", "HandlerLeak" })




android-viewpager