Como determinar quando o fragmento se torna visível no ViewPager

754

Problema: Fragmento onResume() em ViewPageré acionado antes do fragmento se torna realmente visível.

Por exemplo, eu tenho 2 fragmentos com ViewPagereFragmentPagerAdapter . O segundo fragmento está disponível apenas para usuários autorizados e preciso solicitar que o usuário efetue login quando o fragmento se tornar visível (usando uma caixa de diálogo de alerta).

Mas o ViewPager cria o segundo fragmento quando o primeiro estiver visível para armazenar em cache o segundo fragmento e o torna visível quando o usuário começa a passar o dedo.

Então o onResume() evento é disparado no segundo fragmento muito antes de se tornar visível. É por isso que estou tentando encontrar um evento que é acionado quando o segundo fragmento se torna visível para mostrar um diálogo no momento apropriado.

Como isso pode ser feito?

4ntoine
fonte
18
"eu tenho 2 fragmentos com o ViewPager e o FragmentPagerAdapter. O segundo fragmento pode estar disponível apenas para usuários autorizados e devo pedir para o login quando o fragmento se tornar visível (caixa de diálogo de alerta)." - IMHO, isso é horrível UX. Abrir uma caixa de diálogo porque o usuário deslizou horizontalmente faria com que eu desse uma classificação de uma estrela à Play Store.
CommonsWare
é melhor apenas exibir informações no TextView com o botão "Login"? Qual é a sua solução para esse caso?
4ntoine
5
"Não há necessidade de carregar dados se eles não serão exibidos." - então você não deve colocá-lo em um ViewPager. Em um pager de duas páginas, as duas páginas serão carregadas imediatamente, quer você goste ou não. ViewPagerSupõe-se que a experiência do usuário seja de que o conteúdo esteja lá imediatamente após a passagem, e não algum tempo depois. É por isso que ViewPagerinicializa uma página antes do que é visível, para ajudar a garantir a experiência do usuário.
CommonsWare
2
Parece que o ViewPager não é flexível o suficiente e não permite desativar o armazenamento em cache, já que setOffscreenPageLimit mínimo é 1: stackoverflow.com/questions/10073214/… . Não vejo nenhuma razão para isso e o comportamento esperado (no caso de armazenamento em cache obrigatório) é criar o fragmento, MAS acionar o onResume () do fragmento quando o fragmento se tornar visível.
4ntoine
1
Um pouco tarde, mas para quem enfrenta o mesmo problema, você pode tentar a biblioteca FragmentViewPager (eu sou o autor), que lida com esse problema e fornece alguns recursos extras. Para uma amostra, verifique a página do GitHub do projeto ou esta resposta do stackoverflow .
S. Brukhanda

Respostas:

582

Como determinar quando o fragmento se torna visível no ViewPager

Você pode fazer o seguinte substituindo o setUserVisibleHintseu Fragment:

public class MyFragment extends Fragment {
    @Override
    public void setUserVisibleHint(boolean isVisibleToUser) {
        super.setUserVisibleHint(isVisibleToUser);
        if (isVisibleToUser) {
        }
        else {
        }
    }
}
gorn
fonte
7
Graças à atualização atual da Biblioteca de suporte Android (rev 11), o problema de dica visível do usuário foi finalmente corrigido. Agora é seguro usar a dica visível do usuário para o ViewPager.
Oasis Feng
58
Descobri que o método setUserVisibleHint é chamado ANTES que o onCreateView seja chamado e isso dificulta o rastreamento de qualquer inicialização.
precisa saber é o seguinte
11
@AndroidDev Se você deseja executar algum código quando a dica é verdadeira, mas precisa da árvore de visualização já inicializada, basta agrupar esse bloco de código isResumed()para evitar um NPE. Está funcionando bem para mim.
Ravi Thapliyal
13
É simplesmente ridículo que haja tantos hacks diferentes para algo que o SDK deve fornecer por padrão.
Mike6679
15
setUserVisibleHintagora está obsoleto
SR
525

ATUALIZAÇÃO : a Biblioteca de suporte do Android (rev 11) finalmente corrigiu o problema de dica visível do usuário . Agora, se você usar a biblioteca de suporte para fragmentos, poderá usar getUserVisibleHint()ou substituir setUserVisibleHint()com segurança para capturar as alterações, conforme descrito pela resposta de gorn.

ATUALIZAÇÃO 1 Aqui está um pequeno problema com getUserVisibleHint(). Este valor é por padrão true.

// Hint provided by the app that this fragment is currently visible to the user.
boolean mUserVisibleHint = true;

Portanto, pode haver um problema ao tentar usá-lo antes de setUserVisibleHint()ser chamado. Como solução alternativa, você pode definir um valor no onCreatemétodo como este.

public void onCreate(@Nullable Bundle savedInstanceState) {
    setUserVisibleHint(false);

A resposta desatualizada:

Na maioria dos casos de uso, ViewPagermostram apenas uma página de cada vez, mas os fragmentos pré-armazenados em cache também são colocados para o estado "visível" (na verdade invisível) se você estiver usando FragmentStatePagerAdapterno Android Support Library pre-r11.

Eu substituo:

public class MyFragment extends Fragment {
    @Override
    public void setMenuVisibility(final boolean visible) {
        super.setMenuVisibility(visible);
        if (visible) {
            // ...
        }
    }
   // ...
}

Para capturar o estado de foco do fragmento, que eu acho que é o estado mais adequado para a "visibilidade" que você quer dizer, uma vez que apenas um fragmento no ViewPager pode realmente colocar seus itens de menu junto com os itens da atividade pai.

Oasis Feng
fonte
73
Tenha cuidado ao usar getActivity (), na primeira inicialização ele será nulo.
vovkab
10
Observe que setUserVisibleHint () sempre funcionou corretamente no FragmentPagerAdapter.
louielouie
8
Esta solução (e também a de Gorn) está um pouco atrasada. Nos casos em que o viewpager é deslocado rapidamente e várias vezes, esse método será chamado com um atraso (quando o fragmento não estiver mais visível / invisível).
AsafK
3
Estou recebendo truegetUserVisibleHint () quando onCreateOptionsMenué chamado e quando setUserVisibleHinté chamado, o menu ainda não foi criado, parece. Por fim, recebo duas opções Menu adicionado quando quero apenas o menu do fragmento visível. Alguma sugestão a esse respeito?
C4rixmorten
13
setUserVisibleHintagora está obsoleto
SR
143

Isso parece restaurar o onResume()comportamento normal que você esperaria. Ele funciona bem ao pressionar a tecla home para sair do aplicativo e depois entrar novamente no aplicativo. onResume()não é chamado duas vezes seguidas.

@Override
public void setUserVisibleHint(boolean visible)
{
    super.setUserVisibleHint(visible);
    if (visible && isResumed())
    {
        //Only manually call onResume if fragment is already visible
        //Otherwise allow natural fragment lifecycle to call onResume
        onResume();
    }
}

@Override
public void onResume()
{
    super.onResume();
    if (!getUserVisibleHint())
    {
        return;
    }

    //INSERT CUSTOM CODE HERE
}
craigrs84
fonte
4
Exatamente o que eu estava procurando. Resolve o problema de setUserVisibleHintser chamado antes onCreateViewe isso setUserVisibleHintnão é chamado se o aplicativo entrar em segundo plano e depois em primeiro plano. Impressionante! Obrigado!
Alex
11
Eu acho que chamar Resume você mesmo é uma péssima idéia, mas, caso contrário, tudo o que você precisa para responder à pergunta desta postagem está na função setUserVisibleHint!
Quentin G.
4
Prefiro implementar um onVisibleToUser()método simples e ter que chamar de onResume()e de em setUserVisibleHint(boolean)vez de onResume()me chamar e interferir nos retornos de chamada do ciclo de vida. Caso contrário, acho que essa abordagem funciona bem, obrigado!
21719 Stephan Stephan Henningsen
1
Sim, eu concordo com os comentários acima. Em geral, tente evitar chamar métodos públicos de métodos públicos em sua classe. Crie um método privado e chame-o de ambos os métodos públicos.
Johan Franzén
4
setUserVisibleHint descontinuado!
Hamid Reza
70

Aqui está outra maneira de usar onPageChangeListener:

  ViewPager pager = (ViewPager) findByViewId(R.id.viewpager);
  FragmentPagerAdapter adapter = new FragmentPageAdapter(getFragmentManager);
  pager.setAdapter(adapter);
  pager.setOnPageChangeListener(new OnPageChangeListener() {

  public void onPageSelected(int pageNumber) {
    // Just define a callback method in your fragment and call it like this! 
    adapter.getItem(pageNumber).imVisible();

  }

  public void onPageScrolled(int arg0, float arg1, int arg2) {
    // TODO Auto-generated method stub

  }

  public void onPageScrollStateChanged(int arg0) {
    // TODO Auto-generated method stub

  }
});
Ostkontentitan
fonte
1
Esta solução funciona bem, obrigado. Esta deve ser a solução recomendada para aqueles edifício para plataformas mais antigas que utilizam os (v4) pacotes de suporte fragmento
Frank Yin
7
Vale a pena notar que isso não pode fornecer o resultado esperado se você estiver usando um FragmentStatePagerAdapter e instanciando uma nova instância de Fragment em getItem (), porque você definiria apenas o estado no novo fragmento e não o que o viewpager obteve
amigos estão dizendo sobre ben Pearson
1
Essa solução é boa se você deseja executar algo quando o fragmento se tornar visível. Ele não mantém o estado visível de um fragmento (ou seja, não sabe quando o fragmento está invisível).
AsafK
Eu sou o mesmo tipo de problema. por favor me ajude a resolver o problema. Meu Post: stackoverflow.com/questions/23115283/...
Jeeten Parmar
isso está dando exceção de ponteiro nulo para o adaptador que eu estou usando no método iamVisible do fragmento. Estou tentando definir dados recebidos da rede somente quando o fragmento está visível.
precisa saber é o seguinte
58

setUserVisibleHint()é chamado algumas vezes antes onCreateView() e outras depois, o que causa problemas.

Para superar isso, você precisa verificar isResumed()também o setUserVisibleHint()método interno . Mas, nesse caso, percebi que setUserVisibleHint() é chamado se o Fragmento for retomado e visível, NÃO quando criado.

Portanto, se você deseja atualizar algo quando o Fragment estiver visible, coloque sua função de atualização em onCreate()e setUserVisibleHint():

@Override
public View onCreateView(...){
    ...
    myUIUpdate();
    ...        
}
  ....
@Override
public void setUserVisibleHint(boolean visible){
    super.setUserVisibleHint(visible);
    if (visible && isResumed()){
        myUIUpdate();
    }
}

ATUALIZAÇÃO: Ainda assim, eu percebi que myUIUpdate()é chamado duas vezes às vezes, o motivo é que, se você tiver 3 guias e esse código estiver na 2ª guia, quando você abrir a 1ª guia, a 2ª guia também será criada, mesmo que não seja visível e myUIUpdate()seja chamada. Então, quando você desliza para a 2ª guia, myUIUpdate()from if (visible && isResumed())é chamado e, como resultado, myUIUpdate()pode ser chamado duas vezes em um segundo.

O outro problema é !visibleem setUserVisibleHinté chamado tanto 1) quando você sai da tela fragmento e 2) antes de ser criado, quando você alternar para a tela fragmento primeira vez.

Solução:

private boolean fragmentResume=false;
private boolean fragmentVisible=false;
private boolean fragmentOnCreated=false;
...

@Override
public View onCreateView(...){
    ...
    //Initialize variables
    if (!fragmentResume && fragmentVisible){   //only when first time fragment is created
        myUIUpdate();
    }
    ...        
}

@Override
public void setUserVisibleHint(boolean visible){
    super.setUserVisibleHint(visible);
    if (visible && isResumed()){   // only at fragment screen is resumed
        fragmentResume=true;
        fragmentVisible=false;
        fragmentOnCreated=true;
        myUIUpdate();
    }else  if (visible){        // only at fragment onCreated
        fragmentResume=false;
        fragmentVisible=true;
        fragmentOnCreated=true;
    }
    else if(!visible && fragmentOnCreated){// only when you go out of fragment screen
        fragmentVisible=false;
        fragmentResume=false;
    }
}

Explicação:

fragmentResume, fragmentVisible: Garante myUIUpdate()em onCreateView()é chamado somente quando fragmento é criado e visível, não no currículo. Também resolve o problema quando você está na 1ª guia, a segunda guia é criada mesmo que não esteja visível. Isso resolve e verifica se a tela do fragmento está visível quando onCreate.

fragmentOnCreated: Garante que o fragmento não esteja visível e não seja chamado quando você criar um fragmento pela primeira vez. Então agora essa cláusula if só é chamada quando você desliza para fora do fragmento.

Atualização Você pode colocar todo esse código em um BaseFragmentcódigo como este e substituir o método.

Jemshit Iskenderov
fonte
1
Sua solução funciona perfeitamente, exceto em um cenário, quando o fragmento é aberto normalmente e, em seguida, você clica em algum botão para substituí-lo por outro fragmento e, em seguida, clica em voltar para exibir o novo fragmento no backstack; nesse caso setUserVisibleHint, não foi chamado. ! e dentro do onCreateViewmétodo fragmentVisiblefoi false!? então o fragmento apareceu vazio ..! Alguma ideia.?
Alaa AbuZarifa 19/04/19
28

Para detectar Fragmentno ViewPagervisível, tenho certeza de que apenas o uso setUserVisibleHint não é suficiente.
Aqui está minha solução para verificar se um fragmento é visível ou invisível. Primeiro, ao iniciar o viewpager, alterne entre as páginas, vá para outra atividade / fragmento / fundo / primeiro plano`

public class BaseFragmentHelpLoadDataWhenVisible extends Fragment {
    protected boolean mIsVisibleToUser; // you can see this variable may absolutely <=> getUserVisibleHint() but it not. Currently, after many test I find that

    /**
     * This method will be called when viewpager creates fragment and when we go to this fragment background or another activity or fragment
     * NOT called when we switch between each page in ViewPager
     */
    @Override
    public void onStart() {
        super.onStart();
        if (mIsVisibleToUser) {
            onVisible();
        }
    }

    @Override
    public void onStop() {
        super.onStop();
        if (mIsVisibleToUser) {
            onInVisible();
        }
    }

    /**
     * This method will called at first time viewpager created and when we switch between each page
     * NOT called when we go to background or another activity (fragment) when we go back
     */
    @Override
    public void setUserVisibleHint(boolean isVisibleToUser) {
        super.setUserVisibleHint(isVisibleToUser);
        mIsVisibleToUser = isVisibleToUser;
        if (isResumed()) { // fragment have created
            if (mIsVisibleToUser) {
                onVisible();
            } else {
                onInVisible();
            }
        }
    }

    public void onVisible() {
        Toast.makeText(getActivity(), TAG + "visible", Toast.LENGTH_SHORT).show();
    }

    public void onInVisible() {
        Toast.makeText(getActivity(), TAG + "invisible", Toast.LENGTH_SHORT).show();
    }
}

EXPLICAÇÃO Você pode verificar o logcat abaixo com cuidado, então acho que você pode saber por que essa solução funcionará

Primeiro lançamento

Fragment1: setUserVisibleHint: isVisibleToUser=false isResumed=false
Fragment2: setUserVisibleHint: isVisibleToUser=false isResumed=false
Fragment3: setUserVisibleHint: isVisibleToUser=false isResumed=false
Fragment1: setUserVisibleHint: isVisibleToUser=true isResumed=false // AT THIS TIME isVisibleToUser=true but fragment still not created. If you do something with View here, you will receive exception
Fragment1: onCreateView
Fragment1: onStart mIsVisibleToUser=true
Fragment2: onCreateView
Fragment3: onCreateView
Fragment2: onStart mIsVisibleToUser=false
Fragment3: onStart mIsVisibleToUser=false

Ir para a página2

Fragment1: setUserVisibleHint: isVisibleToUser=false isResumed=true
Fragment2: setUserVisibleHint: isVisibleToUser=true isResumed=true

Ir para a página3

Fragment2: setUserVisibleHint: isVisibleToUser=false isResumed=true
Fragment3: setUserVisibleHint: isVisibleToUser=true isResumed=true

Vá para segundo plano:

Fragment1: onStop mIsVisibleToUser=false
Fragment2: onStop mIsVisibleToUser=false
Fragment3: onStop mIsVisibleToUser=true

Ir para o primeiro plano

Fragment1: onStart mIsVisibleToUser=false
Fragment2: onStart mIsVisibleToUser=false
Fragment3: onStart mIsVisibleToUser=true

Projeto DEMO aqui

Espero que ajude

Phan Van Linh
fonte
1
Não funciona quando um fragmento possui outro pager de visualização, que também consiste em fragmento.
Shihab Uddin
desculpe, não posso postar a resposta no momento porque não tenho tempo suficiente, se possível, consulte o meu git sobre seu problema aqui github.com/PhanVanLinh/AndroidViewPagerSkeleton/tree/master . SubChildContainerFragmenté usado para detectar a fragment has another view pager which also consists fragment. Você pode misturar SubChildContainerFragmente ChildContainerFragmentpara 1 aula. Espero que ajude. Postarei a resposta completa mais tarde #
Phan Van Linh
26
package com.example.com.ui.fragment;


import android.os.Bundle;
import android.support.annotation.Nullable;
import android.support.v4.app.Fragment;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;

import com.example.com.R;

public class SubscribeFragment extends Fragment {

    @Override
    public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
        View view = inflater.inflate(R.layout.fragment_subscribe, container, false);
        return view;
    }

    @Override
    public void setUserVisibleHint(boolean isVisibleToUser) {
        super.setUserVisibleHint(isVisibleToUser);

        if (isVisibleToUser) {
            // called here
        }
    }

    @Override
    public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
        super.onViewCreated(view, savedInstanceState);
    }
}
w3hacker
fonte
2
Essa deve ser a resposta aceita. Também funciona com android.app.Fragment, que é um bônus enorme.
RajV
setUserVisibleHintObsoleta
ekashking
23

Na versão ViewPager2e ViewPagerda versão, androidx.fragment:fragment:1.1.0você pode apenas usar onPausee onResumeretornos de chamada para determinar qual fragmento está atualmente visível para o usuário. onResumeo retorno de chamada é chamado quando o fragmento se onPausetorna visível e quando para de ficar visível.

No caso do ViewPager2, é um comportamento padrão, mas o mesmo comportamento pode ser ativado para o bem antigo ViewPagerfacilmente.

Para habilitar esse comportamento no primeiro ViewPager, você deve passar o FragmentPagerAdapter.BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENTparâmetro como segundo argumento do FragmentPagerAdapterconstrutor.

FragmentPagerAdapter(fragmentManager, BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT)

Nota: o setUserVisibleHint()método e o FragmentPagerAdapterconstrutor com um parâmetro agora estão obsoletos na nova versão do Fragment do android jetpack.

lukjar
fonte
1
Obrigado pela boa solução. Eu estava procurando por isso há muito tempo. Grande trabalho
Anurag Srivastava
1
Para o ViewPager2, a opção BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT não é o comportamento padrão, você precisa configurá-lo manualmente. Ao definir esta opção, sua solução funcionou para mim. Obrigado.
driAn 28/02
1
A partir de abril de 2020, esta é a solução atualizada e funciona como um encanto.
Prakash
1
@ 4ntoine, considere aceitar esta resposta como a resposta correta, pois a partir de agora ela está correta e a maioria das respostas está usando o método setUserVisibleHint descontinuado
Jorn Rigter
16

Substituir setPrimaryItem()na FragmentPagerAdaptersubclasse. Eu uso esse método e funciona bem.

@Override
public void setPrimaryItem(ViewGroup container, int position, Object object) {
    // This is what calls setMenuVisibility() on the fragments
    super.setPrimaryItem(container, position, object);

    if (object instanceof MyWhizBangFragment) {
        MyWhizBangFragment fragment = (MyWhizBangFragment) object;
        fragment.doTheThingYouNeedToDoOnBecomingVisible();
    }
}
kris larson
fonte
1
Isso quase funcionou, mas teve problemas com o NPE e foi chamado várias vezes. Postado uma alternativa abaixo!
Gober
@Gober salvando o objeto Object na primeira vez e verifique e ignore a chamada de acompanhamento com o mesmo objeto Object, como FragmentStateAdapter faz no método setPrimaryItem ()
wanglugao
12

Substituir Fragment.onHiddenChanged()por isso.

public void onHiddenChanged(boolean hidden)

Chamado quando o estado oculto (retornado por isHidden() ) do fragmento foi alterado. Fragmentos começam não ocultos; isso será chamado sempre que o fragmento mudar de estado a partir disso.

Parâmetros
hidden- boolean: True se o fragmento está agora oculto, false se não estiver visível.

dan
fonte
3
este método está obsoleto por agora e não pode ser mais utilizado
bluewhile
4
@bluewhile onde você vê que está obsoleto? developer.android.com/reference/android/app/…
Cel
1
Acabei de usar isso com sucesso, e a documentação não parece indicar que está obsoleta.
Trevor
1
@bluewhile, em 4.1 (api 16) ainda não está obsoleto.
dan
8
Estou usando o nível 19 da API e posso confirmar que, embora isso não seja preterido, ele não funciona como anunciado. onHiddenChanged não é chamado quando o fragmento está oculto por outro fragmento, por exemplo.
4

Eu descobri isso onCreateOptionsMenue onPrepareOptionsMenumétodos chamados apenas no caso do fragmento realmente visível. Não encontrei nenhum método que se comporte assim, tentei, OnPageChangeListenermas não funcionou para as situações, por exemplo, preciso de uma variável inicializada no onCreatemétodo.

Portanto, esses dois métodos podem ser usados ​​para esse problema como uma solução alternativa, especificamente para trabalhos pequenos e curtos.

Penso que esta é a melhor solução, mas não a melhor. Vou usar isso, mas aguardo uma solução melhor ao mesmo tempo.

Saudações.

ismailarilik
fonte
2

Outra solução postada aqui, substituindo setPrimaryItem no pageradapter por kris larson, quase funcionou para mim. Mas esse método é chamado várias vezes para cada instalação. Também recebi o NPE de visualizações, etc. no fragmento, pois isso não está pronto nas primeiras vezes em que esse método é chamado. Com as seguintes alterações, isso funcionou para mim:

private int mCurrentPosition = -1;

@Override
public void setPrimaryItem(ViewGroup container, int position, Object object) {
    super.setPrimaryItem(container, position, object);

    if (position == mCurrentPosition) {
        return;
    }

    if (object instanceof MyWhizBangFragment) {
        MyWhizBangFragment fragment = (MyWhizBangFragment) object;

        if (fragment.isResumed()) {
            mCurrentPosition = position;
            fragment.doTheThingYouNeedToDoOnBecomingVisible();
        }
    }
}
Gober
fonte
2

Adicione o seguinte código dentro do fragmento

@Override
public void setMenuVisibility(final boolean visible) 
 {
    super.setMenuVisibility(visible);
    if (visible && isResumed()) 
     {

     }
}
Afzaal Iftikhar
fonte
Isso funciona apenas para ViewPagerFragments. Não para fragmentos em atividade normal.
AndroidGuy
2

Encontrei o mesmo problema ao trabalhar com FragmentStatePagerAdapterse 3 guias. Eu tive que mostrar um Dilaog sempre que a 1ª guia foi clicada e ocultá-la clicando em outras guias.

A substituição setUserVisibleHint()sozinha não ajudou a encontrar o fragmento visível atual.

Ao clicar na 3ª guia -----> 1ª guia. Foi acionado duas vezes para o 2º fragmento e para o 1º fragmento. Eu combinei com o método isResumed ().

    @Override
public void setUserVisibleHint(boolean isVisibleToUser) {
    super.setUserVisibleHint(isVisibleToUser);
    isVisible = isVisibleToUser;

    // Make sure that fragment is currently visible
    if (!isVisible && isResumed()) {
        // Call code when Fragment not visible
    } else if (isVisible && isResumed()) {
       // Call code when Fragment becomes visible.
    }

}
Aman Arora
fonte
2

Temos um caso especial com o MVP, em que o fragmento precisa notificar o apresentador de que a exibição se tornou visível, e o apresentador é injetado por Dagger fragment.onAttach().

setUserVisibleHint()não é suficiente, detectamos três casos diferentes que precisavam ser abordados ( onAttach()é mencionado para que você saiba quando o apresentador está disponível):

  1. Fragmento acaba de ser criado. O sistema faz as seguintes chamadas:

    setUserVisibleHint() // before fragment's lifecycle calls, so presenter is null
    onAttach()
    ...
    onResume()
  2. Fragmento já criado e o botão home é pressionado. Ao restaurar o aplicativo em primeiro plano, isso é chamado:

    onResume()
  3. Mudança de orientação:

    onAttach() // presenter available
    onResume()
    setUserVisibleHint()

Queremos que a dica de visibilidade chegue ao apresentador apenas uma vez, e é assim que fazemos:

@Nullable
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
    View root = inflater.inflate(R.layout.fragment_list, container, false);
    setHasOptionsMenu(true);

    if (savedInstanceState != null) {
        lastOrientation = savedInstanceState.getInt(STATE_LAST_ORIENTATION,
              getResources().getConfiguration().orientation);
    } else {
        lastOrientation = getResources().getConfiguration().orientation;
    }

    return root;
}

@Override
public void onResume() {
    super.onResume();
    presenter.onResume();

    int orientation = getResources().getConfiguration().orientation;
    if (orientation == lastOrientation) {
        if (getUserVisibleHint()) {
            presenter.onViewBecomesVisible();
        }
    }
    lastOrientation = orientation;
}

@Override
public void setUserVisibleHint(boolean isVisibleToUser) {
    super.setUserVisibleHint(isVisibleToUser);
    if (presenter != null && isResumed() && isVisibleToUser) {
        presenter.onViewBecomesVisible();
    }
}

@Override public void onSaveInstanceState(Bundle outState) {
    super.onSaveInstanceState(outState);
    outState.putInt(STATE_LAST_ORIENTATION, lastOrientation);
}
PIN
fonte
2

Detectando por focused view!

Isso funciona para mim

public static boolean isFragmentVisible(Fragment fragment) {
    Activity activity = fragment.getActivity();
    View focusedView = fragment.getView().findFocus();
    return activity != null
            && focusedView != null
            && focusedView == activity.getWindow().getDecorView().findFocus();
}
Mohammad Reza Norouzi
fonte
1

Encontrei esse problema quando estava tentando acionar um timer quando o fragmento no visor estava na tela para o usuário ver.

O cronômetro sempre iniciava antes do fragmento ser visto pelo usuário. Isso ocorre porque o onResume()método no fragmento é chamado antes que possamos ver o fragmento.

Minha solução foi fazer uma verificação no onResume()método. Eu queria chamar um determinado método de 'foo ()' quando o fragmento 8 era o fragmento atual dos pagers de exibição.

@Override
public void onResume() {
    super.onResume();
    if(viewPager.getCurrentItem() == 8){
        foo();
        //Your code here. Executed when fragment is seen by user.
    }
}

Espero que isto ajude. Eu já vi esse problema aparecer muito. Esta parece ser a solução mais simples que já vi. Muitos outros não são compatíveis com APIs mais baixas etc.

Malone
fonte
1

Eu tive o mesmo problema. ViewPagerexecuta outros eventos do ciclo de vida do fragmento e não pude mudar esse comportamento. Escrevi um pager simples usando fragmentos e animações disponíveis. SimplePager

Rukmal Dias
fonte
1

Eu usei isso e funcionou!

mContext.getWindow().getDecorView().isShown() //boolean
Ali Akram
fonte
1

Eu apóio o SectionsPagerAdapter com fragmentos filhos. Depois de muita dor de cabeça, finalmente consegui a versão de trabalho com base nas soluções deste tópico:

public abstract class BaseFragment extends Fragment {

    private boolean visible;
    private boolean visibilityHintChanged;

    /**
     * Called when the visibility of the fragment changed
     */
    protected void onVisibilityChanged(View view, boolean visible) {

    }

    private void triggerVisibilityChangedIfNeeded(boolean visible) {
        if (this.visible == visible || getActivity() == null || getView() == null) {
            return;
        }
        this.visible = visible;
        onVisibilityChanged(getView(), visible);
    }

    @Override
    public void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        if (!visibilityHintChanged) {
            setUserVisibleHint(false);
        }
    }

    @Override
    public void onResume() {
        super.onResume();
        if (getUserVisibleHint() && !isHidden()) {
            triggerVisibilityChangedIfNeeded(true);
        }
    }

    @Override
    public void onHiddenChanged(boolean hidden) {
        super.onHiddenChanged(hidden);
        triggerVisibilityChangedIfNeeded(!hidden);
    }

    @Override
    public void setUserVisibleHint(boolean isVisibleToUser) {
        super.setUserVisibleHint(isVisibleToUser);
        visibilityHintChanged = true;
        if (isVisibleToUser && isResumed() && !isHidden()) {
            triggerVisibilityChangedIfNeeded(true);
        } else if (!isVisibleToUser) {
            triggerVisibilityChangedIfNeeded(false);
        }
    }

    @Override
    public void onPause() {
        super.onPause();
        triggerVisibilityChangedIfNeeded(false);
    }

    @Override
    public void onStop() {
        super.onStop();
        triggerVisibilityChangedIfNeeded(false);
    }

    protected boolean isReallyVisible() {
        return visible;
    }
}
Andoctorey
fonte
Esqueci de mencionar, depois de adicionar fragmento filho ao conjunto de fragmentos pai fragment.setUserVisibleHint (true);
Andoctorey 10/10/19
E não se esqueça de usar getChildFragmentManager () em vez de getFragmentManager () para adicionar fragmentos filhos.
Andoctorey 15/05/19
0

Observe que setUserVisibleHint(false)não é chamado na parada de atividade / fragmento. Você ainda precisará verificar iniciar / parar adequadamente register/unregisterqualquer ouvinte / etc.

Além disso, você verá setUserVisibleHint(false)se o seu fragmento inicia em um estado não visível; você não quer ir para unregisterlá, porque nunca se registrou antes nesse caso.

@Override
public void onStart() {
    super.onStart();

    if (getUserVisibleHint()) {
        // register
    }
}

@Override
public void onStop() {
    if (getUserVisibleHint()) {
        // unregister
    }

    super.onStop();
}

@Override
public void setUserVisibleHint(boolean isVisibleToUser) {
    super.setUserVisibleHint(isVisibleToUser);

    if (isVisibleToUser && isResumed()) {
        // register

        if (!mHasBeenVisible) {
            mHasBeenVisible = true;
        }
    } else if (mHasBeenVisible){
        // unregister
    }
}
onze e cinco
fonte
0

Uma maneira simples de implementar é verificar se o usuário está conectado antes de ir para o fragmento.

Em seu MainActivity, você pode fazer algo assim dentro do método onNavigationItemSelected .

 case R.id.nav_profile_side:


                if (User_is_logged_in) {

                    fragmentManager.beginTransaction()
                            .replace(R.id.content_frame
                                    , new FragmentProfile())
                            .commit();
                }else {

                    ShowLoginOrRegisterDialog(fragmentManager);

                }

                break;

No entanto, se você estiver usando a gaveta de navegação, a seleção na gaveta será alterada para Perfil, embora não tenhamos ido para o ProfileFragment.

Para redefinir a seleção para a seleção atual, execute o código abaixo

        navigationView.getMenu().getItem(0).setChecked(true);
Martin Mbae
fonte
0

setUserVisibleHint (boolean visible) agora está obsoleto. Portanto, esta é a solução correta

FragmentPagerAdapter(fragmentManager, BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT)

No ViewPager2 e no ViewPager da versão, androidx.fragment:fragment:1.1.0você pode apenas usar onPause()e onResume()determinar qual fragmento está atualmente visível para o usuário. onResume()é chamado quando o fragmento se tornou visível e onPausequando para de ficar visível.

Para habilitar esse comportamento no primeiro ViewPager, você deve passar o FragmentPagerAdapter.BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENTparâmetro como o segundo argumento do FragmentPagerAdapterconstrutor.

Rahul
fonte
-4

Substituí o método Count do FragmentStatePagerAdapter associado e ele retornou a contagem total menos o número de páginas a serem ocultadas:

 public class MyAdapter : Android.Support.V13.App.FragmentStatePagerAdapter
 {   
     private List<Fragment> _fragments;

     public int TrimmedPages { get; set; }

     public MyAdapter(Android.App.FragmentManager fm) : base(fm) { }

     public MyAdapter(Android.App.FragmentManager fm, List<Android.App.Fragment> fragments) : base(fm)
     {
         _fragments = fragments;

         TrimmedPages = 0;
     }

     public override int Count
     {
         //get { return _fragments.Count; }
         get { return _fragments.Count - TrimmedPages; }
     }
 }

Portanto, se houver 3 fragmentos adicionados inicialmente ao ViewPager e apenas os 2 primeiros devem ser mostrados até que alguma condição seja atendida, substitua a contagem de páginas configurando TrimmedPages como 1 e ele deve mostrar apenas as duas primeiras páginas.

Isso funciona bem para as páginas no final, mas realmente não ajuda no começo ou no meio (embora haja várias maneiras de fazer isso).

Sam é
fonte