suporte FragmentPagerAdapter contém referência a fragmentos antigos

109

ÚLTIMAS INFORMAÇÕES:

Limitei meu problema a ser um problema com o fragmentManager retendo instâncias de fragmentos antigos e meu viewpager estando fora de sincronia com meu FragmentManager. Veja este problema ... http://code.google.com/p/android/issues/detail?id=19211#makechanges . Ainda não tenho ideia de como resolver isso. Alguma sugestão...

Eu tentei depurar isso por um longo tempo e qualquer ajuda seria muito apreciada. Estou usando um FragmentPagerAdapter que aceita uma lista de fragmentos assim:

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

A implementação é padrão. Estou usando a biblioteca de computabilidade ActionBarSherlock e v4 para Fragments.

Meu problema é que depois de sair do aplicativo e abrir vários outros aplicativos e voltar, os fragmentos perdem sua referência de volta para a FragmentActivity (ou seja. getActivity() == null). Não consigo entender por que isso está acontecendo. Tentei definir manualmente, setRetainInstance(true);mas isso não ajuda. Percebi que isso acontece quando minha FragmentActivity é destruída, no entanto, isso ainda acontece se eu abrir o aplicativo antes de receber a mensagem de log. Existe alguma ideia?

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

O adaptador:

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

    }

}

Um dos meus fragmentos foi removido, mas eu expus tudo que foi removido e ainda não funciona ...

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

}

}

O método de atualização é chamado quando um usuário interage com um fragmento diferente e getActivity está retornando null. Aqui está o método que o outro fragmento está chamando ...

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

Eu acredito que quando o aplicativo é destruído e criado novamente, em vez de apenas iniciar o aplicativo até o fragmento padrão, o aplicativo é iniciado e o viewpager navega para a última página conhecida. Isso parece estranho, o aplicativo não deveria apenas carregar no fragmento padrão?

Maurycy
fonte
27
Fragmento do Android é uma merda!
Hamidreza Sadegh
13
Deus, eu amo suas mensagens de registro: D
oli.G

Respostas:

120

Você está enfrentando um problema porque está instanciando e mantendo referências a seus fragmentos fora de PagerAdapter.getIteme tentando usar essas referências independentemente do ViewPager. Como o Seraph diz, você tem garantias de que um fragmento foi instanciado / adicionado em um ViewPager em um determinado momento - isso deve ser considerado um detalhe de implementação. Um ViewPager carrega lentamente suas páginas; por padrão, ele carrega apenas a página atual e a página à esquerda e à direita.

Se você colocar seu aplicativo em segundo plano, os fragmentos que foram adicionados ao gerenciador de fragmentos serão salvos automaticamente. Mesmo se seu aplicativo for encerrado, essas informações serão restauradas quando você reiniciá-lo.

Agora, considere que você visualizou algumas páginas, Fragmentos A, B e C. Você sabe que eles foram adicionados ao gerenciador de fragmentos. Como você está usando FragmentPagerAdaptere não FragmentStatePagerAdapter, esses fragmentos ainda serão adicionados (mas potencialmente destacados) quando você rolar para outras páginas.

Considere que você coloca seu aplicativo em segundo plano e ele é encerrado. Quando você voltar, o Android se lembrará de que você costumava ter os fragmentos A, B e C no gerenciador de fragmentos e, portanto, ele os recria para você e depois os adiciona. No entanto, aqueles que são adicionados ao gerenciador de fragmentos agora NÃO são os que você tem em sua lista de fragmentos em sua atividade.

O FragmentPagerAdapter não tentará chamar getPositionse já houver um fragmento adicionado para essa posição de página específica. Na verdade, como o fragmento recriado pelo Android nunca será removido, você não tem esperança de substituí-lo por uma chamada para getPosition. Também é muito difícil obter uma referência a ele, porque foi adicionado com uma tag que você não conhece. Isso ocorre por design; você é desencorajado a mexer com os fragmentos que o view pager está gerenciando. Você deve realizar todas as suas ações dentro de um fragmento, comunicando-se com a atividade e solicitando a mudança para uma página específica, se necessário.

Agora, de volta ao seu problema com a atividade ausente. Chamar pagerAdapter.getItem(1)).update(id, name)depois que tudo isso aconteceu retorna o fragmento em sua lista, que ainda não foi adicionado ao gerenciador de fragmentos e, portanto, não terá uma referência de atividade. Eu sugeriria que seu método de atualização deve modificar alguma estrutura de dados compartilhada (possivelmente gerenciada pela atividade) e, em seguida, quando você mover para uma página específica, ele pode desenhar a si mesmo com base nesses dados atualizados.

antonyt
fonte
1
Eu gosto da sua solução, pois é muito elegante e provavelmente irá refatorar meu código, entretanto, como você disse, eu queria manter 100% da lógica e dos dados dentro do fragmento. Sua solução exigiria basicamente manter todos os dados em FragmentActivity e, em seguida, usar cada Fragment simplesmente para lidar com o display Logic. Como eu disse, prefiro isso, mas a interação entre os fragmentos é muito pesada e pode ser chato de gerenciar. De qualquer forma, obrigado por sua explicação detalhada. Você explicou isso muito melhor do que eu.
Maurycy de
28
Curto: nunca mantenha uma referência a um fragmento fora do adaptador
passsy
2
Então, como uma instância de Activity acessa a instância FragmentPagerAdapter se não instanciar o FragmentPagerAdapter? As instâncias futuras apenas reinstanciarão o FragmentPagerAdapter e todas as suas instâncias de fragmento? O FragmentPagerAdapter deve implementar todas as interfaces de fragmento para gerenciar a comunicação entre os fragmentos?
Eric H.
a declaração "Manipular isso também é muito difícil de obter uma referência a ele porque ele foi adicionado com uma tag que você não conhece. Isso ocorre por design; você não é incentivado a mexer com os fragmentos que o pager de visualização está gerenciando . " é falso. é muito fácil obter uma referência telefonando instantiateIteme você deve fazê-lo na onCreateprática. veja os detalhes aqui: stackoverflow.com/questions/14035090/…
morgwai
em geral, é duvidoso instanciar fragmentos sem carregar porque em um cenário de "destruir e recriar" você pode acabar manipulando um fragmento separado daquele que foi realmente recriado e mostrado
hmac
108

Encontrei uma solução simples que funcionou para mim.

Faça com que seu adaptador de fragmento estenda FragmentStatePagerAdapter em vez de FragmentPagerAdapter e substitua o método onSave para retornar nulo

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

Isso evita que o Android recrie o fragmento


Um dia depois, encontrei outra solução melhor.

Peça setRetainInstance(true)todos os seus fragmentos e salve as referências a eles em algum lugar. Fiz isso em uma variável estática em minha atividade, porque ela é declarada como singleTask e os fragmentos podem permanecer os mesmos o tempo todo.

Dessa forma, o Android não recriará fragmentos, mas usará as mesmas instâncias.

mc.dev
fonte
4
Obrigado, obrigado, muito obrigado, realmente não tenho palavras para agradecer a você, Mik, eu estava perseguindo esse problema nos últimos 10 dias e tentei tantos métodos. Mas essas quatro linhas mágicas salvaram minha vida :)
7
ter uma referência estática para fragmentos e / ou atividades é uma coisa muito arriscada, pois pode causar vazamentos de memória com muita facilidade. Obviamente, se você for cuidadoso, poderá lidar com isso facilmente definindo-os como null quando não forem mais necessários.
desenvolvedor Android de
Isso funcionou para mim. Os fragmentos e suas respectivas visualizações mantêm seus links após o aplicativo travar e reinicializar. Obrigado!
swebal
Estou usando setRetainInstance(true)com FragmentPagerAdapter. Tudo funciona bem. Mas quando giro o dispositivo, o adaptador ainda tem os fragmentos, mas os fragmentos não são mostrados. Os métodos de ciclo de vida dos fragmentos também não são chamados. Alguém pode ajudar?
Jonas
1
você salvou meu dia !!! Estou procurando por esse bug há 3 dias. Eu tinha um Viewpager com 2 fragmentos dentro de uma SingleTask Activity e com o flag "Dont keep Activities" habilitado. Muito obrigado!!!!
matriz
29

Resolvi esse problema acessando meus fragmentos diretamente por meio do FragmentManager em vez de por meio do FragmentPagerAdapter. Primeiro, preciso descobrir a tag do fragmento gerado automaticamente pelo FragmentPagerAdapter ...

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

Então, simplesmente obtenho uma referência a esse fragmento e faço o que preciso, então ...

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

Dentro de meus fragmentos, defino o setRetainInstance(false);para que possa adicionar manualmente valores ao pacote savedInstanceState.

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

    super.onSaveInstanceState(outState);
}

e então no OnCreate eu pego aquela chave e restauro o estado do fragmento conforme necessário. Uma solução fácil que foi difícil (pelo menos para mim) de descobrir.

Maurycy
fonte
Eu tenho um problema semelhante ao seu, mas não entendi bem sua explicação. Você pode fornecer mais detalhes? Eu também tenho um adaptador que armazena fragmentos na lista, mas quando estou retomando meu aplicativo de aplicativos recentes, os aplicativos travam porque há algum fragmento separado. O problema está aqui stackoverflow.com/questions/11631408/…
Georgy Gobozov
3
Qual é o seu 'meu' na referência do fragmento?
Josh
Todos nós sabemos que esta solução não é uma boa abordagem, mas é a maneira mais fácil de usar FragmentByTagno ViewPager.
Youngjae
aqui está uma maneira de acessar fragmentos sem depender da compatibilidade com a forma interna de atribuição de tags: stackoverflow.com/questions/14035090/…
morgwai
26

Solução testada de trabalho global.

getSupportFragmentManager()mantém a referência nula algumas vezes e o visualizador de pager não cria novos, pois encontra a referência para o mesmo fragmento. Então, para superar, esse uso getChildFragmentManager()resolve o problema de maneira simples.

Não faça isso:

new PagerAdapter(getSupportFragmentManager(), fragments);

Faça isso:

new PagerAdapter(getChildFragmentManager() , fragments);

Vinayak
fonte
6
isso só seria possível se você estiver hospedando (e instanciando) o pagerAdapter dentro de um Fragment e não dentro de sua atividade. Nesse caso, você está certo, deve usar o childFragmentManager por padrão. Usar o SupportFragmentManager estaria errado por padrão
Klitos G.
Muito preciso. Resolvi meu problema sem usar hacks. Obrigado! Minha versão Kotlin passou de FragmentStatePagerAdapter(activity!!.supportFragmentManager)uma mais fácil de olhar FragmentStatePagerAdapter(childFragmentManager):)
ecth
7

Não tente interagir entre fragmentos no ViewPager. Você não pode garantir que outro fragmento anexado ou mesmo exista. Em vez de alterar o título da barra de ação do fragmento, você pode fazer isso a partir de sua atividade. Use o padrão de interface padrão para isso:

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);
    }
}
Serafim
fonte
Claro que incluiremos código agora ... Já estou registrando esses métodos. O onDetach é chamado quando onStop é chamado na fragmentActivity. onAttach é chamado logo antes de onCreate da FragmentActivity.
Maurycy de
Hmm obrigado, mas o problema ainda persiste. Em vez disso, defini a configuração do nome como um retorno de chamada. Não posso colocar lógica no fragmento? Eu tenho meus fragmentos disparando serviços e outros elementos que requerem uma referência à atividade. Eu teria que mover todas as minhas lógicas de aplicativos para a atividade principal para evitar o problema que estou tendo. Isso parece um pouco desnecessário.
Maurycy de
Ok, então eu apenas testei isso completamente e comentei todo o meu código ... exceto pelo retorno de chamada para alterar o título. Quando o aplicativo é iniciado pela primeira vez e eu mudo para essa tela, o nome do título é alterado np. Depois de carregar vários aplicativos, o título não muda mais. Parece que o retorno de chamada está sendo recebido em uma instância de atividade antiga. Não consigo pensar em outra explicação
Maurycy de
Considerei que isso era um problema com o fragmentManager e esse problema ... code.google.com/p/android/issues/detail?id=19211 . Ainda não tenho ideia de como resolver isso
Maurycy
5

Você pode remover os fragmentos ao destruir o viewpager, no meu caso, removi-os onDestroyView()do meu fragmento:

@Override
public void onDestroyView() {

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

    super.onDestroyView();
}
Raoni Novellino
fonte
Obrigado, sua solução funciona. É necessário que ViewPagertambém seja baseado no childFragmentManageradaptador (não fragmentManager). Outra variante também funciona: não usar onDestroyView, mas remover fragmentos filhos antes da ViewPagercriação do adaptador.
CoolMind 01 de
4

Depois de algumas horas procurando por um problema semelhante, acho que tenho outra solução. Este pelo menos funcionou para mim e eu só tenho que mudar algumas linhas.

Este é o problema que eu tive, eu tenho uma atividade com um pager de exibição que usa um FragmentStatePagerAdapter com dois Fragments. Tudo funciona bem até que eu force a atividade a ser destruída (opções do desenvolvedor) ou gire a tela. Eu mantenho uma referência aos dois fragmentos depois que eles são criados dentro do método getItem.

Nesse ponto, a atividade será criada novamente e tudo funciona bem neste ponto, mas perdi a referência aos meus fragmetns porque getItem não é chamado novamente.

Foi assim que resolvi esse problema, dentro do 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;
    }

Você não receberá uma chamada em getItem novamente se o adaptador já tiver uma referência a ele internamente e você não deve alterar isso. Em vez disso, você pode obter o fragmento que está sendo usado observando este outro método instantiateItem (), que será chamado para cada um de seus fragmentos.

Espero que ajude alguém.

Lancelot
fonte
E se você tiver muitos fragmentos? você realmente prefere salvar uma referência para cada um deles? Não é ruim para a memória?
desenvolvedor do Android
Tudo depende do que exatamente você está tentando alcançar. Normalmente, com esses pagers de visualização, você não mantém mais do que 3 fragmentos por vez. O do meio e o de cada lado. Para o que desejo, realmente preciso ter uma referência aos fragmentos, mas sei que só preciso lidar com dois.
Lancelot
0

Como o FragmentManager se encarregará de restaurar seus Fragments para você assim que o método onResume () for chamado, faço com que o fragmento chame a atividade e o adicione a uma lista. No meu caso, estou armazenando tudo isso na minha implementação do PagerAdapter. Cada fragmento conhece sua posição porque é adicionado aos argumentos do fragmento na criação. Agora, sempre que preciso manipular um fragmento em um índice específico, tudo o que tenho a fazer é usar a lista do meu adaptador.

A seguir está um exemplo de um adaptador para um ViewPager customizado que aumentará o fragmento conforme ele se move para o foco e o redimensionará conforme ele sai de foco. Além das classes Adaptador e Fragmento, tenho aqui tudo que você precisa é que a atividade pai seja capaz de fazer referência à variável do adaptador e pronto.

Adaptador

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

Fragmento

public class SummaryTabletFragment extends BaseTabletFragment {

public final String TAG = this.getClass().getSimpleName();

private final float SCALE_SIZE = 0.8f;

private RelativeLayout mBackground, mCover;
private TextView mTitle;
private VerticalTextView mLeft, mRight;

private String mTitleText;
private Integer mColor;

private boolean mInit = false;
private Float mScale, mPercent;

private GrowPagerAdapter mAdapter;
private int mCurrentPosition = 0;

public String getTitleText() {
    return mTitleText;
}

public void setTitleText(String titleText) {
    this.mTitleText = titleText;
}

public static SummaryTabletFragment newInstance(int position) {

    SummaryTabletFragment fragment = new SummaryTabletFragment();
    fragment.setRetainInstance(true);

    Bundle args = new Bundle();
    args.putInt("position", position);
    fragment.setArguments(args);

    return fragment;
}

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

    mRoot = inflater.inflate(R.layout.tablet_dummy_view, null);

    setupViews();
    configureView();

    return mRoot;
}

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

    if (savedInstanceState != null) {
        mColor = savedInstanceState.getInt("color", Color.BLACK);
    }

    configureView();
}

@Override
public void onSaveInstanceState(Bundle outState)  {

    outState.putInt("color", mColor);

    super.onSaveInstanceState(outState);
}

@Override
public int getPosition() {
    return getArguments().getInt("position", -1);
}

@Override
public void setPosition(int position) {
    getArguments().putInt("position", position);
}

public void onResume() {
    super.onResume();

    mAdapter = mActivity.getPagerAdapter();
    mAdapter.addFragment(this);
    mCurrentPosition = mAdapter.getCurrentPage();

    if ((getPosition() == (mCurrentPosition + 1) || getPosition() == (mCurrentPosition - 1)) && !mInit) {
        mInit = true;
        transitionFragment(GrowPagerAdapter.BASE_ALPHA, GrowPagerAdapter.BASE_SIZE);
        return;
    }

    if (getPosition() == mCurrentPosition && !mInit) {
        mInit = true;
        transitionFragment(0.00f, 1.0f);
    }
}

private void setupViews() {

    mCover = (RelativeLayout) mRoot.findViewById(R.id.cover);
    mLeft = (VerticalTextView) mRoot.findViewById(R.id.title_left);
    mRight = (VerticalTextView) mRoot.findViewById(R.id.title_right);
    mBackground = (RelativeLayout) mRoot.findViewById(R.id.root);
    mTitle = (TextView) mRoot.findViewById(R.id.title);
}

private void configureView() {

    Fonts.applyPrimaryBoldFont(mLeft, 15);
    Fonts.applyPrimaryBoldFont(mRight, 15);

    float[] size = UiUtils.getScreenMeasurements(mActivity);
    int width = (int) (size[0] * SCALE_SIZE);
    int height = (int) (size[1] * SCALE_SIZE);

    RelativeLayout.LayoutParams params = new RelativeLayout.LayoutParams(width, height);
    mBackground.setLayoutParams(params);

    if (mScale != null)
        transitionFragment(mPercent, mScale);

    setRandomBackground();

    setTitleText("Fragment " + getPosition());

    mTitle.setText(getTitleText().toUpperCase());
    mLeft.setText(getTitleText().toUpperCase());
    mRight.setText(getTitleText().toUpperCase());

    mLeft.setOnClickListener(new OnClickListener() {

        @Override
        public void onClick(View v) {

            mActivity.showNextPage();
        }
    });

    mRight.setOnClickListener(new OnClickListener() {

        @Override
        public void onClick(View v) {

            mActivity.showPrevPage();
        }
    });
}

private void setRandomBackground() {

    if (mColor == null) {
        Random r = new Random();
        mColor = Color.rgb(r.nextInt(255), r.nextInt(255), r.nextInt(255));
    }

    mBackground.setBackgroundColor(mColor);
}

public void transitionFragment(float percent, float scale) {

    this.mScale = scale;
    this.mPercent = percent;

    if (getView() != null && mCover != null) {

        getView().setScaleX(scale);
        getView().setScaleY(scale);

        mCover.setAlpha(percent);
        mCover.setVisibility((percent <= 0.05f) ? View.GONE : View.VISIBLE);
    }
}

@Override
public String getFragmentTitle() {
    return null;
}
}
saulpower
fonte
0

Minha solução: configurei quase todas as visualizações como static. Agora meu aplicativo interage perfeitamente. Poder chamar os métodos estáticos de qualquer lugar talvez não seja um bom estilo, mas por que brincar com um código que não funciona? Li muitas perguntas e suas respostas aqui no SO e nenhuma solução trouxe sucesso (para mim).

Eu sei que ele pode vazar a memória e desperdiçar pilha, e meu código não caberá em outros projetos, mas não tenho medo disso - testei o aplicativo em diferentes dispositivos e condições, sem nenhum problema, o Android A plataforma parece ser capaz de lidar com isso. A IU é atualizada a cada segundo e mesmo em um dispositivo S2 ICS (4.0.3), o aplicativo é capaz de lidar com milhares de marcadores geográficos.

Martin Pfeffer
fonte
0

Eu enfrentei o mesmo problema, mas meu ViewPager estava dentro de um TopFragment que criou e configurou um adaptador usando setAdapter(new FragmentPagerAdapter(getChildFragmentManager())).

onAttachFragment(Fragment childFragment)Corrigi esse problema substituindo no TopFragment desta forma:

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

    super.onAttachFragment(childFragment);
}

Como já se sabe (veja as respostas acima), quando o childFragmentManager se recria, ele também cria os fragmentos que estavam dentro do viewPager.
O importante é que depois disso, ele chama onAttachFragment e agora temos uma referência ao novo fragmento recriado!

Espero que isso ajude alguém a conseguir esse Q antigo como eu :)

Shirane85
fonte
0

Resolvi o problema salvando os fragmentos no 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
fonte
0

Só para você saber ...

Adicionando à ladainha de desgraças com essas classes, há um bug bastante interessante que vale a pena compartilhar.

Estou usando um ViewPager para navegar em uma árvore de itens (selecione um item e o pager de visualização anima a rolagem para a direita e o próximo ramo aparece, navegue de volta, e o ViewPager rola na direção oposta para retornar ao nó anterior) .

O problema surge quando empurro e retiro fragmentos do final do FragmentStatePagerAdapter. É inteligente o suficiente para notar que os itens mudam e inteligente o suficiente para criar e substituir um fragmento quando o item é alterado. Mas não é inteligente o suficiente para descartar o estado do fragmento, ou inteligente o suficiente para cortar os estados do fragmento salvos internamente quando o tamanho do adaptador muda. Portanto, quando você abre um item e empurra um novo para o final, o fragmento do novo item obtém o estado salvo do fragmento do item antigo, o que causou estragos absolutos em meu código. Meus fragmentos carregam dados que podem exigir muito trabalho para recuperá-los da Internet, então não salvar o estado realmente não era uma opção.

Não tenho uma solução alternativa limpa. Eu usei algo assim:

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

Uma solução imperfeita porque a nova instância do fragmento ainda obtém um SavedState Bundle, mas pelo menos não carrega dados obsoletos.

Robin Davies
fonte
0

Como as pessoas não costumam ler comentários, aqui está uma resposta que em grande parte duplica o que escrevi aqui :

a causa raiz do problema é o fato de que o sistema Android não chama getItempara obter fragmentos que são realmente exibidos, mas instantiateItem. Este método primeiro tenta pesquisar e reutilizar uma instância de fragmento para uma determinada guia em FragmentManager. Somente se essa pesquisa falhar (o que acontece apenas na primeira vez, quando FragmentManageré criado), getItemé chamado. É por motivos óbvios não recriar fragmentos (que podem ser pesados), por exemplo, cada vez que um usuário gira seu dispositivo.
Para resolver isso, em vez de criar fragmentos com Fragment.instantiateem sua atividade, você deve fazê-lo com pagerAdapter.instantiateIteme todas essas chamadas devem ser cercadas por startUpdate/finishUpdatechamadas de método que iniciam / confirmam transações de fragmentos respectivamente.getItem deve ser o local onde os fragmentos são realmente criados usando seus respectivos construtores.

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
fonte