Alternando entre a imagem da Gaveta de Navegação Android e o cursor para cima ao usar fragmentos

177

Ao usar a Gaveta de Navegação, os desenvolvedores do Android recomendam que na ActionBar "apenas as telas representadas na Gaveta de Navegação tenham realmente a imagem da Gaveta de Navegação" e que "todas as outras telas tenham o tradicional quilate".

Veja aqui para detalhes: http://youtu.be/F5COhlbpIbY

Estou usando uma atividade para controlar vários níveis de fragmentos e posso fazer com que a imagem da Gaveta de Navegação seja exibida e funcione em todos os níveis.

Ao criar fragmentos de nível inferior, posso chamar o ActionBarDrawerToggle setDrawerIndicatorEnabled(false)para ocultar a imagem da Gaveta de Navegação e exibir o cursor para cima

LowerLevelFragment lowFrag = new LowerLevelFragment();

//disable the toggle menu and show up carat
theDrawerToggle.setDrawerIndicatorEnabled(false);
getSupportFragmentManager().beginTransaction().replace(R.id.frag_layout, 
lowFrag, "lowerFrag").addToBackStack(null).commit();

O problema que estou tendo é quando navego de volta aos fragmentos de nível superior que o quilate Up ainda mostra em vez da imagem original da gaveta de navegação. Alguma sugestão sobre como "atualizar" a ActionBar nos fragmentos de nível superior para exibir novamente a imagem da Gaveta de Navegação?


Solução

A sugestão de Tom funcionou para mim. Aqui está o que eu fiz:

Atividade principal

Esta atividade controla todos os fragmentos no aplicativo.

Ao preparar novos fragmentos para substituir outros, defino o DrawerToggle setDrawerIndicatorEnabled(false)assim:

LowerLevelFragment lowFrag = new LowerLevelFragment();

//disable the toggle menu and show up carat
theDrawerToggle.setDrawerIndicatorEnabled(false);
getSupportFragmentManager().beginTransaction().replace(R.id.frag_layout,   
lowFrag).addToBackStack(null).commit();

Em seguida, em uma substituição de onBackPressed, eu reverti o acima, definindo o DrawerToggle da setDrawerIndicatorEnabled(true)seguinte maneira:

@Override
public void onBackPressed() {
    super.onBackPressed();
    // turn on the Navigation Drawer image; 
    // this is called in the LowerLevelFragments
    setDrawerIndicatorEnabled(true)
}

Nos LowerLevelFragments

Nos fragmentos eu modifiquei onCreatee onOptionsItemSelectedassim:

Na onCreateadicionado setHasOptionsMenu(true)para permitir configurar o menu de opções. Defina também setDisplayHomeAsUpEnabled(true)para ativar o < na barra de ação:

@Override
public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    // needed to indicate that the fragment would 
    // like to add items to the Options Menu        
    setHasOptionsMenu(true);    
    // update the actionbar to show the up carat/affordance 
    getActivity().getActionBar().setDisplayHomeAsUpEnabled(true);
}

Em seguida, onOptionsItemSelectedsempre que o < é pressionado, ele chama o onBackPressed()da atividade para subir um nível na hierarquia e exibir a Imagem da gaveta de navegação:

@Override
public boolean onOptionsItemSelected(MenuItem item) {   
    // Get item selected and deal with it
    switch (item.getItemId()) {
        case android.R.id.home:
            //called when the up affordance/carat in actionbar is pressed
            getActivity().onBackPressed();
            return true;
         
    }
EvilAsh
fonte
2
Também no seu método onBackPressed (), você pode verificar quantas entradas estão na pilha traseira com o método getFragmentManager (). GetBackStackEntryCount () e ativar o indicador de gaveta apenas se o resultado for 0. Nesse caso, não é necessário ativar o homeAsUpIndicator em cada LowerLevelFragments.
precisa saber é o seguinte
2
Isso é muito útil! Você deve mover a parte "solução" da sua postagem e torná-la uma "resposta" real. Você vai conseguir mais pontos para upvotes e é uma resposta depois de tudo
Oleksiy
Por que você está substituindo o fragmento aqui: .replace(R.id.frag_layout. Se este é mais um nível de hierarquia, eu esperaria que você .addo recuasse.
JJD
5
Bro, como você faz referência ao theDrawerToggle.setDrawerIndicatorEnabled(false);interior do fragmento? Eu acho que é declarado no arquivo de classe da atividade principal. Não consigo encontrar uma maneira de fazer referência a isso. Alguma dica?
Skynet
1
Ao usar uma barra de ferramentas, tive que mudar as opções de exibição para não usar a casa enquanto isso. Caso contrário, o setDisplayOptions()método no ToolbarWidgetWrapper(do pacote android.support.v7.internal.widget interno) não recriaria o ícone ao inserir o mesmo fragmento pela segunda vez. Apenas deixando isso aqui para quando outros tropeçarem nesse problema também.
Wolfram Rittmeyer

Respostas:

29

Você escreveu que, para implementar fragmentos de nível inferior, você está substituindo o fragmento existente, em vez de implementar o fragmento de nível inferior em uma nova atividade.

Eu pensaria que você teria que implementar a funcionalidade back manualmente: quando o usuário pressionou, você tem um código que abre a pilha (por exemplo, Activity::onBackPressedsobrescreva). Então, onde quer que você faça isso, você pode reverter o setDrawerIndicatorEnabled.

Tom
fonte
Obrigado Tom, isso funcionou! Atualizei a postagem original com a solução que usei.
EvilAsh
6
Como referenciar o theDrawerToggle em um fragmento de nível inferior? Foi definido na atividade principal, não consigo entender isso!
Skynet
1
Você pode obter a atividade do fragmento. Portanto, basta adicionar um getter em sua atividade principal para acessar a alternância da gaveta.
Raphael Royer-Rivard
83

É fácil como 1-2-3.

Se você deseja alcançar:

1) Indicador da gaveta - quando não há fragmentos na pilha traseira ou a gaveta é aberta

2) Seta - quando alguns fragmentos estão na pilha de trás

private FragmentManager.OnBackStackChangedListener
        mOnBackStackChangedListener = new FragmentManager.OnBackStackChangedListener() {
    @Override
    public void onBackStackChanged() {
        syncActionBarArrowState();
    }
};

@Override
protected void onCreate(Bundle savedInstanceState) {
    getSupportActionBar().setDisplayShowHomeEnabled(true);
    getSupportActionBar().setDisplayHomeAsUpEnabled(true);
    mDrawerToggle = new ActionBarDrawerToggle(
            this,             
            mDrawerLayout,  
            R.drawable.ic_navigation_drawer, 
            0, 
            0  
    ) {

        public void onDrawerClosed(View view) {
            syncActionBarArrowState();
        }

        public void onDrawerOpened(View drawerView) {
            mDrawerToggle.setDrawerIndicatorEnabled(true);
        }
    };

    mDrawerLayout.setDrawerListener(mDrawerToggle);
    getSupportFragmentManager().addOnBackStackChangedListener(mOnBackStackChangedListener);
}

@Override
protected void onDestroy() {
    getSupportFragmentManager().removeOnBackStackChangedListener(mOnBackStackChangedListener);
    super.onDestroy();
}

private void syncActionBarArrowState() {
    int backStackEntryCount = 
        getSupportFragmentManager().getBackStackEntryCount();
    mDrawerToggle.setDrawerIndicatorEnabled(backStackEntryCount == 0);
}

3) Ambos os indicadores para agir de acordo com sua forma

@Override
public boolean onOptionsItemSelected(MenuItem item) {
    if (mDrawerToggle.isDrawerIndicatorEnabled() && 
        mDrawerToggle.onOptionsItemSelected(item)) {
        return true;
    } else if (item.getItemId() == android.R.id.home && 
               getSupportFragmentManager().popBackStackImmediate()) {
        return true;
    } else {
        return super.onOptionsItemSelected(item);
    }
}

PS Consulte Criando uma gaveta de navegação no Android Developers para obter outras dicas sobre o comportamento do indicador de três linhas.

riwnodennyk
fonte
3
Acabei com algo semelhante ao seu. Eu acho que essa solução (usando o BackStackChangedListener para alternar entre o que é mostrado) é a mais elegante. No entanto, tenho duas alterações: 1) Não chamo / altero o indicador da gaveta nas chamadas onDrawerClosed / onDrawerOpened - não é necessário e 2) deixei que os próprios fragmentos lidassem com a navegação para cima, criando um AbstractFragment que todos eles herdam. implementa onOptionsItemSelected (..) e que sempre chama setHasOptionsMenu (true);
Espen Riskedal
2
O Yout também deve bloquear o gesto de deslizar da gaveta de navegação no modo de seta: mDrawerLayout.setDrawerLockMode (DrawerLayout.LOCK_MODE_LOCKED_CLOSED); e ativá-lo novamente no modo indicador de gaveta
artkoenig
5
Acabei fazendo tudo isso no meu fragmento NavigationDrawer. Funciona como um encanto btw, obrigado.
frostymarvelous
1
setActionBarArrowDependingOnFragmentsBackStack()... que nome longo: P
S.Thiongane
2
Isso não mostra o botão para cima quando passo do fragmento A para B e adiciono A ao backstack. :(
aandis
14

Eu usei a próxima coisa:

getSupportFragmentManager().addOnBackStackChangedListener(new FragmentManager.OnBackStackChangedListener() {
            @Override
            public void onBackStackChanged() {
                if(getSupportFragmentManager().getBackStackEntryCount() > 0){
                    mDrawerToggle.setDrawerIndicatorEnabled(false);
                    getSupportActionBar().setDisplayHomeAsUpEnabled(true);
                }
                else {
                    getSupportActionBar().setDisplayHomeAsUpEnabled(false);
                    mDrawerToggle.setDrawerIndicatorEnabled(true);
                }
            }
        });
Yuriy Sych
fonte
1
MUITO OBRIGADO, este é o único que realmente funcionou. Depois de adicionar isso, drawerToggle.setToolbarNavigationClickListener(esse ouvinte é chamado quando se clica na seta
EpicPandaForce
Obrigado @Yuriy, isso me ajudou a resolver meu problema
Muhammad Shoaib Murtaza
12

Se o botão da barra de ação para cima não funcionar, não se esqueça de adicionar o ouvinte:

// Navigation back icon listener
mDrawerToggle.setToolbarNavigationClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            onBackPressed();
        }
});

Estou com problemas para implementar uma navegação na gaveta com um botão home, tudo funcionou, exceto o botão de ação.

Burrich
fonte
10

Tente manipular a seleção do item Home na MainActivity, dependendo do estado do DrawerToggle. Dessa forma, você não precisa adicionar o mesmo código a cada fragmento.

@Override
public boolean onOptionsItemSelected(MenuItem item) {
    // Only handle with DrawerToggle if the drawer indicator is enabled.
    if (mDrawerToggle.isDrawerIndicatorEnabled() &&
            mDrawerToggle.onOptionsItemSelected(item)) {
        return true;
    }
    // Handle action buttons
    switch (item.getItemId()) {
        // Handle home button in non-drawer mode
        case android.R.id.home:
            onBackPressed();
            return true;

        default:
            return super.onOptionsItemSelected(item);
    }
}
dzeikei
fonte
+1 solução pura. Para que sua resposta seja totalmente utilizável, adicione um cheque no backstack. Quando estiver vazio, defina automaticamente o indicador da gaveta para verdadeiro.
HpTerm
@ HPTerm Lido com o backstack onBackPressed()porque queria o mesmo comportamento para ambos.
dzeikei
6

ACOMPANHAMENTO

A solução dada por @dzeikei é pura, mas pode ser estendida, ao usar fragmentos, para lidar automaticamente com a reposição do indicador da gaveta quando a barra de apoio está vazia.

@Override
public boolean onOptionsItemSelected(MenuItem item) {
    // Only handle with DrawerToggle if the drawer indicator is enabled.
    if (mDrawerToggle.isDrawerIndicatorEnabled() &&
            mDrawerToggle.onOptionsItemSelected(item)) {
        return true;
    }
    // Handle action buttons
    switch (item.getItemId()) {
        // Handle home button in non-drawer mode
        case android.R.id.home:
            // Use getSupportFragmentManager() to support older devices
            FragmentManager fragmentManager = getFragmentManager();
            fragmentManager.popBackStack();
            // Make sure transactions are finished before reading backstack count
            fragmentManager.executePendingTransactions();
            if (fragmentManager.getBackStackEntryCount() < 1){
                mDrawerToggle.setDrawerIndicatorEnabled(true);  
            }
            return true;

        default:
            return super.onOptionsItemSelected(item);
    }
}

EDITAR

Para a pergunta de @JJD.

Os fragmentos são mantidos / gerenciados em uma atividade. O código acima é gravado uma vez nessa atividade, mas apenas manipula o cursor para o onOptionsItemSelected.

Em um dos meus aplicativos, eu também precisava lidar com o comportamento do cursor quando o botão Voltar foi pressionado. Isso pode ser resolvido sobrescrevendo onBackPressed.

@Override
public void onBackPressed() {
    // Use getSupportFragmentManager() to support older devices
    FragmentManager fragmentManager = getFragmentManager();
    fragmentManager.executePendingTransactions();
    if (fragmentManager.getBackStackEntryCount() < 1){
        super.onBackPressed();
    } else {
        fragmentManager.executePendingTransactions();
        fragmentManager.popBackStack();
        fragmentManager.executePendingTransactions();
        if (fragmentManager.getBackStackEntryCount() < 1){
            mDrawerToggle.setDrawerIndicatorEnabled(true);
        }
    }
};

Observe a duplicação de código entre onOptionsItemSelectede onBackPressedque pode ser evitada criando um método e chamando esse método nos dois locais.

Observe também que eu adiciono mais duas vezes, o executePendingTransactionsque, no meu caso, era necessário ou, às vezes, eu tinha comportamentos estranhos com o sinal de intercalação.

HpTerm
fonte
É este o código completo que preciso adicionar para implementar o comportamento de sinal de intercalação Up ou você está se referindo a uma das outras postagens? Por favor, esclareça isso.
JJD
Obrigado pela edição. Na verdade eu mantenho mDrawerToggledentro de uma NavigationDrawerFragmentclasse. Para fazê-lo funcionar eu preciso também alternar o estado do botão home / indicador - veja: NavigationDrawerFragment#toggleDrawerIndicator. Além disso, não tenho certeza de que você precise do check-in inicial onOptionsItemSelected: descomentei-o. - Exemplo simplificado
JJD
Implementação revisada : você está certo sobre o check-in inicial onOptionsItemSelected. Isso garante que a gaveta de navegação ainda seja aberta na hierarquia de nível superior. No entanto, mudei o código para o arquivo NavigationDrawerFragment#onOptionsItemSelected. Isso me ajuda a não expor mDrawerToggleao MainActivity.
JJD
@JJD Lembrei-me de lutar um pouco por ter tudo funcionando para o sinal de alerta para cada nível da hierarquia. Contanto que funcione para você, tudo bem. Obviamente, como você diz, você pode mover o código para outro lugar para não expô-lo.
HpTerm
2

Criei uma interface para a atividade de hospedagem para atualizar o estado de exibição do menu de hambúrguer. Para fragmentos de nível superior, defino a alternância para truee para os fragmentos para os quais quero exibir a seta para cima <Defino a alternância para false.

public class SomeFragment extends Fragment {

    public interface OnFragmentInteractionListener {
        public void showDrawerToggle(boolean showDrawerToggle);
    }

    private OnFragmentInteractionListener mListener;

    @Override
    public void onAttach(Activity activity) {
        super.onAttach(activity);
        try {
            this.mListener = (OnFragmentInteractionListener) activity;
        } catch (ClassCastException e) {
            throw new ClassCastException(activity.toString() + " must implement OnFragmentInteractionListener");
        }
    }

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

Então na minha atividade ...

public class MainActivity extends Activity implements SomeFragment.OnFragmentInteractionListener {

    private ActionBarDrawerToggle mDrawerToggle;

    public void showDrawerToggle(boolean showDrawerIndicator) {
        mDrawerToggle.setDrawerIndicatorEnabled(showDrawerIndicator);
    }

}
Bill Mote
fonte
2

Esta resposta estava funcionando, mas havia um pequeno problema. Não getSupportActionBar().setDisplayHomeAsUpEnabled(false)foi chamado explicitamente e estava ocultando o ícone da gaveta, mesmo quando não havia itens no backstack, portanto, alterar o setActionBarArrowDependingOnFragmentsBackStack()método funcionou para mim.

private void setActionBarArrowDependingOnFragmentsBackStack() {
        int backStackEntryCount = getSupportFragmentManager()
                .getBackStackEntryCount();
        // If there are no items in the back stack
        if (backStackEntryCount == 0) {
            // Please make sure that UP CARAT is Hidden otherwise Drawer icon
            // wont display
            getSupportActionBar().setDisplayHomeAsUpEnabled(false);
            // Display the Drawer Icon
            mDrawerToggle.setDrawerIndicatorEnabled(true);
        } else {
            // Show the Up carat
            getSupportActionBar().setDisplayHomeAsUpEnabled(true);
            // Hide the Drawer Icon
            mDrawerToggle.setDrawerIndicatorEnabled(false);
        }

    }
Sheraz Ahmad Khilji
fonte
no meu caso, a solução certa estava usando apenas actionBarDrawerToggle.setDrawerIndicatorEnabled (getSupportFragmentManager (). getBackStackEntryCount () <0);
Gorets
1

A lógica é clara. Mostrar o botão Voltar se a pilha traseira do fragmento estiver limpa. Mostrar animação de retorno de hambúrguer de material se a pilha de fragmentos não estiver clara.

getSupportFragmentManager().addOnBackStackChangedListener(
    new FragmentManager.OnBackStackChangedListener() {
        @Override
        public void onBackStackChanged() {
            syncActionBarArrowState();
        }
    }
);


private void syncActionBarArrowState() {
    int backStackEntryCount = getSupportFragmentManager().getBackStackEntryCount();
    mNavigationDrawerFragment.setDrawerIndicatorEnabled(backStackEntryCount == 0);
}

//add these in Your NavigationDrawer fragment class

public void setDrawerIndicatorEnabled(boolean flag){
    ActionBar actionBar = getActionBar();
    if (!flag) {
        mDrawerToggle.setDrawerIndicatorEnabled(false);
        actionBar.setDisplayHomeAsUpEnabled(true);
        mDrawerToggle.setHomeAsUpIndicator(getColoredArrow());
    } else {
        mDrawerToggle.setDrawerIndicatorEnabled(true);
    }
    mDrawerToggle.syncState();
    getActivity().supportInvalidateOptionsMenu();
}

//download back button from this(https://www.google.com/design/icons/) website and add to your project

private Drawable getColoredArrow() {
    Drawable arrowDrawable = ContextCompat.getDrawable(getActivity(), R.drawable.ic_arrow_back_black_24dp);
    Drawable wrapped = DrawableCompat.wrap(arrowDrawable);

    if (arrowDrawable != null && wrapped != null) {
        // This should avoid tinting all the arrows
        arrowDrawable.mutate();
        DrawableCompat.setTint(wrapped, Color.GRAY);
    }
    return wrapped;
}
kml_ckr
fonte
1

Se você der uma olhada no aplicativo GMAIL e vier aqui, procure o ícone de custo / benefício.

Eu pediria que você fizesse isso, nenhuma das respostas acima foi clara. Consegui modificar a resposta aceita.

  • NavigationDrawer -> Listview contém subfragmentos.


  • subfragmentos serão listados assim

  • firstFragment == posição 0 ---> isso terá subfragmentos -> fragmento

  • secondFragment
  • thirdFragment e assim por diante ....

No firstFragment você tem outro fragmento.

Chame isso em DrawerActivity

getFragmentManager().addOnBackStackChangedListener(new FragmentManager.OnBackStackChangedListener() {
        @Override
        public void onBackStackChanged() {
            if (getFragmentManager().getBackStackEntryCount() > 0) {
                mDrawerToggle.setDrawerIndicatorEnabled(false);
            } else {
                mDrawerToggle.setDrawerIndicatorEnabled(true);
            }
        }
    });

e em fragmento

    setHasOptionsMenu(true);    

@Override
public boolean onOptionsItemSelected(MenuItem item) {
    // Get item selected and deal with it
    switch (item.getItemId()) {
        case android.R.id.home:
            //called when the up affordance/carat in actionbar is pressed
            activity.onBackPressed();
            return true;
    }
    return false;
}

No método de atividade OnBackPressed Drawer, defina a alternância da gaveta para true para ativar o ícone da lista de navegação novamente.

Obrigado, Pusp

EngineSense
fonte
0

O IMO, usando onNavigateUp () (como mostrado aqui ) na solução de riwnodennyk ou Tom, é mais limpo e parece funcionar melhor. Apenas substitua o código onOptionsItemSelected por este:

@Override
public boolean onSupportNavigateUp() {
    if (getSupportFragmentManager().getBackStackEntryCount() > 0) {
        // handle up navigation
        return true;
    } else {
        return super.onSupportNavigateUp();
    }
}
0101100101
fonte