Prática recomendada para fragmentos aninhados no Android 4.0, 4.1 (<4.2) sem usar a biblioteca de suporte

115

Estou escrevendo um aplicativo para tablets 4.0 e 4.1, para o qual não quero usar as bibliotecas de suporte (se não for necessário), mas apenas a api 4.x.

Portanto, minha plataforma de destino é muito bem definida como:> = 4.0 e <= 4.1

O aplicativo possui um layout com vários painéis (dois fragmentos, um pequeno à esquerda, um fragmento de conteúdo à direita) e uma barra de ação com guias.

Semelhante a este:

insira a descrição da imagem aqui

Clicar em uma guia na barra de ação altera o fragmento "externo", e o fragmento interno é um fragmento com dois fragmentos aninhados (1. pequeno fragmento de lista à esquerda, 2. fragmento de conteúdo amplo).

Agora estou me perguntando qual é a prática recomendada para substituir fragmentos e, especialmente, fragmentos aninhados. O ViewPager faz parte da biblioteca de suporte, não há alternativa 4.x nativa para esta classe. Parece estar "obsoleto" no meu sentido. - http://developer.android.com/reference/android/support/v4/view/ViewPager.html

Então eu li as notas de lançamento do Android 4.2, a respeito ChildFragmentManager, que seria um bom ajuste, mas estou visando 4.0 e 4.1, então isso também não pode ser usado.

ChildFragmentManager só está disponível em 4.2

Infelizmente, quase não existem bons exemplos por aí que mostram as melhores práticas para uso de fragmentos sem a biblioteca de suporte, mesmo em todos os guias do desenvolvedor Android; e especialmente nada sobre fragmentos aninhados.

Então, eu me pergunto: simplesmente não é possível escrever aplicativos 4.1 com fragmentos aninhados sem usar a biblioteca de suporte e tudo o que vem com ela? (precisa usar FragmentActivity em vez de Fragment, etc.?) Ou qual seria a prática recomendada?


O problema que estou tendo atualmente no desenvolvimento é exatamente esta declaração:

A Android Support Library também oferece suporte a fragmentos aninhados, para que você possa implementar designs de fragmentos aninhados no Android 1.6 e superior.

Nota: Você não pode aumentar um layout em um fragmento quando esse layout inclui um <fragment>. Fragmentos aninhados são suportados apenas quando adicionados a um fragmento dinamicamente.

Porque eu defini os fragmentos aninhados em XML, o que aparentemente causa um erro como:

Caused by: java.lang.IllegalArgumentException: Binary XML file line #15: Duplicate id 0x7f090009, tag frgCustomerList, or parent id 0x7f090008 with another fragment for de.xyz.is.android.fragment.CustomerListFragment_

No momento, concluo por mim mesmo: mesmo no 4.1, quando eu nem quero ter como alvo a plataforma 2.x, os fragmentos aninhados como mostrado na imagem não são possíveis sem a biblioteca de suporte.

(Isso pode realmente ser mais uma entrada de wiki do que uma pergunta, mas talvez outra pessoa tenha gerenciado isso antes).

Atualizar:

Uma resposta útil está em: Fragment Inside Fragment

Mathias Conradt
fonte
22
Você tem três opções: 1. Almeje apenas 4.2 com fragmentos aninhados nativos. 2. Alvo 4.x com fragmentos aninhados da biblioteca de suporte 3. Não use fragmentos aninhados para nenhum outro cenário de destino da plataforma. Isso deve responder à sua pergunta. Além disso, você não pode usar fragmentos aninhados embutidos no layout xml, todos eles devem ser adicionados no código. dificilmente existem bons exemplos por aí que mostram as melhores práticas para uso de fragmentos sem a biblioteca de suporte - a estrutura do fragmento de suporte replica o nativo, portanto, qualquer exemplo deve funcionar de qualquer maneira.
Luksprog,
@Luksprog Obrigado por seus comentários. Eu prefiro sua solução 2, e framents funcionam bem na biblioteca de suporte, mas as guias na ActionBar não - afaik, eu precisaria usar ActionBarSherlock, mas as guias não seriam integradas na ActionBar, mas apenas abaixo (que não é t necessário para 4.x). E ActionBar.TabListener oferece suporte apenas a fragmentos de android.app.Fragment, não da biblioteca de suporte.
Mathias Conradt
2
Não estou familiarizado com o aplicativo Contatos na guia Galaxy, mas lembre-se de que você sempre pode encontrar uma implementação personalizada do ActionBar(desenvolvido internamente pela Samsung). Dê uma olhada mais de perto no ActionBarSherlock, ele tem as guias na ActionBar se houver espaço.
Luksprog
4
@Luksprog Eu acredito que você já forneceu a única resposta que há para dar, você faria a gentileza de colocá-la como uma resposta adequada.
Warpzit de
1
@Pork Meu principal motivo para a pergunta é: existem soluções alternativas para fragmentos aninhados sem ter que usar a biblioteca de suporte e todos os seus outros elementos de visualização. Ou seja, se eu mudar para a biblioteca de suporte, usarei FragmentActivity em vez de Fragment. Mas eu quero usar Fragment, tudo que eu quero é uma substituição para Fragmentos Aninhados , mas não todos os componentes da v4. Ou seja, por meio de outras bibliotecas de código aberto, etc. por aí. Por exemplo, a captura de tela acima é executada no 4.0, e estou me perguntando se eles estão usando o ABS, SupportLib ou qualquer outra coisa.
Mathias Conradt

Respostas:

60

Limitações

Portanto, o aninhamento de fragmentos dentro de outro fragmento não é possível com xml, independentemente de qual versão FragmentManagervocê usa.

Portanto, você tem que adicionar fragmentos via código, isso pode parecer um problema, mas no longo prazo torna seus layouts superflexíveis.

Então, aninhando sem usar getChildFragmentManger? A essência childFragmentManageré que ele adia o carregamento até que a transação do fragmento anterior seja concluída. E é claro que era apenas suportado naturalmente no 4.2 ou na biblioteca de suporte.

Nesting without ChildManager - Solução

Solução, claro! Eu tenho feito isso há muito tempo (desde que ViewPagerfoi anunciado).

Ver abaixo; Este é um Fragmentque adia o carregamento, então Fragments podem ser carregados dentro dele.

É muito simples, Handleré uma classe realmente muito útil, efetivamente o manipulador espera por um espaço para executar no thread principal após a transação do fragmento atual terminar de confirmar (já que os fragmentos interferem na IU que eles executam no thread principal).

// Remember this is an example, you will need to modify to work with your code
private final Handler handler = new Handler();
private Runnable runPager;

@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)
    return inflater.inflate(R.layout.frag_layout, container, false);
}

@Override
public void onActivityCreated(Bundle savedInstanceState)
{
    super.onActivityCreated(savedInstanceState);
    runPager = new Runnable() {

        @Override
        public void run()
        {
          getFragmentManager().beginTransaction().addFragment(R.id.frag_container, MyFragment.newInstance()).commit();
        }
    };
    handler.post(runPager);
}

/**
 * @see android.support.v4.app.Fragment#onPause()
 */
@Override
public void onPause()
{
    super.onPause();
    handler.removeCallbacks(runPager);
}

Eu não consideraria isso uma 'prática recomendada', mas tenho aplicativos ativos usando esse hack e ainda não tive nenhum problema com ele.

Eu também uso este método para incorporar pagers de visualização - https://gist.github.com/chrisjenx/3405429

Chris.Jenkins
fonte
Como você lida com fragmentos aninhados de layout?
pablisco
A única maneira de ver isso funcionando é usando um CustomLayoutInflater, conforme você se depara com o fragmentelemento, substitui a superimplementação e tenta analisá-la / aumentá-la sozinho. Mas isso vai exigir muito esforço, bem fora do escopo de uma questão StackOverflow.
Chris.Jenkins
Oi, alguém pode me ajudar nessa questão ?? Estou realmente preso .. stackoverflow.com/questions/32240138/…
Nicks
2

A melhor maneira de fazer isso no pré-API 17 é não fazer nada. Tentar implementar esse comportamento vai causar problemas. No entanto, isso não quer dizer que não possa ser falsificado de forma convincente usando a API 14. atual. O que fiz foi o seguinte:

1 - veja a comunicação entre fragmentos http://developer.android.com/training/basics/fragments/communicating.html

2 - mova seu layout xml FrameLayout de seu Fragment existente para o layout Activity e oculte-o dando uma altura de 0:

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
          xmlns:tools="http://schemas.android.com/tools"
          android:layout_width="fill_parent"
          android:layout_height="fill_parent">
<FrameLayout android:id="@+id/content"
          android:layout_width="300dp"
          android:layout_height="match_parent" />


<FrameLayout android:id="@+id/lstResults"
             android:layout_width="300dp"
             android:layout_height="0dp"
             android:layout_below="@+id/content"
             tools:layout="@layout/treeview_list_content"/>


<FrameLayout android:id="@+id/anomalies_fragment"
             android:layout_width="match_parent"
             android:layout_height="match_parent"
        android:layout_toRightOf="@+id/content" />

3 - Implementar a interface no fragmento pai

    OnListener mCallback;

// Container Activity must implement this interface
public interface OnListener 
{
    public void onDoSomethingToInitChildFrame(/*parameters*/);
    public void showResults();
    public void hideResults();
}

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

    // This makes sure that the container activity has implemented
    // the callback interface. If not, it throws an exception
    try {
        mCallback = (OnFilterAppliedListener) activity;
    } catch (ClassCastException e) {
        throw new ClassCastException(activity.toString()
                + " must implement OnListener");
    }
}

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

    mCallback.showResults();
}

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

    mCallback.hideResults();
}

public void onClickButton(View view)
{
    // do click action here

    mCallback.onDoSomethingToInitChildFrame(/*parameters*/);
}

4 - Implementar a interface na atividade pai

public class YourActivity extends Activity implementa yourParentFragment.OnListener {

public void onDoSomethingToInitChildFrame(/*parameters*/)
{
    FragmentTransaction ft = getFragmentManager().beginTransaction();
    Fragment childFragment = getFragmentManager().findFragmentByTag("Results");
    if(childFragment == null)
    {
        childFragment = new yourChildFragment(/*parameters*/);
        ft.add(R.id.lstResults, childFragment, "Results");
    }
    else
    {
        ft.detach(childFragment);

        ((yourChildFragment)childFragment).ResetContent(/*parameters*/);

        ft.attach(childFragment);
    }
    ft.commit();

    showResultsPane();
}

public void showResults()
{
    FragmentTransaction ft = getFragmentManager().beginTransaction();
    Fragment childFragment = getFragmentManager().findFragmentByTag("Results");
    if(childFragment != null)
        ft.attach(childFragment);
    ft.commit();

    showResultsPane();
}

public void showResultsPane()
{
    //resize the elements to show the results pane
    findViewById(R.id.content).getLayoutParams().height = ViewGroup.LayoutParams.WRAP_CONTENT;
    findViewById(R.id.lstResults).getLayoutParams().height = ViewGroup.LayoutParams.WRAP_CONTENT;
}

public void hideResults()
{
    //resize the elements to hide the results pane
    findViewById(R.id.content).getLayoutParams().height = ViewGroup.LayoutParams.MATCH_PARENT;
    findViewById(R.id.lstResults).getLayoutParams().height = 0;

    FragmentTransaction ft = getFragmentManager().beginTransaction();
    Fragment childFragment = getFragmentManager().findFragmentByTag("Results");
    if(childFragment != null)
        ft.detach(childFragment);
    ft.commit();
}

}

5 - Aproveite, com este método você obtém a mesma funcionalidade de fluido que com a função getChildFragmentManager () em um ambiente pré-API 17. Como você deve ter notado, o fragmento filho não é mais filho do fragmento pai, mas agora filho da atividade, isso realmente não pode ser evitado.

Alex
fonte
1

Tive que lidar com esse problema exato devido a uma combinação de NavigationDrawer, TabHost e ViewPager, que teve complicações com o uso da biblioteca de suporte por causa do TabHost. E então eu também tive que suportar min API do JellyBean 4.1, então usar fragmentos aninhados com getChildFragmentManager não era uma opção.

Então, meu problema pode ser destilado para ...

TabHost (para nível superior)
+ ViewPager (para apenas um dos fragmentos com guias de nível superior)
= necessidade de fragmentos aninhados (que o JellyBean 4.1 não suporta)

Minha solução foi criar a ilusão de fragmentos aninhados sem realmente aninhar fragmentos. Eu fiz isso fazendo com que a atividade principal usasse TabHost E ViewPager para gerenciar duas Visualizações irmãs, cuja visibilidade é gerenciada alternando layout_weight entre 0 e 1.

//Hide the fragment used by TabHost by setting height and weight to 0
LinearLayout.LayoutParams lp = new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, 0, 0);
mTabHostedView.setLayoutParams(lp);
//Show the fragment used by ViewPager by setting height to 0 but weight to 1
lp = new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, 0, 1);
mPagedView.setLayoutParams(lp);

Isso efetivamente permitiu que meu falso "Fragmento Aninhado" funcionasse como uma visão independente, desde que eu gerenciei manualmente os pesos de layout relevantes.

Aqui está meu activity_main.xml:

<android.support.v4.widget.DrawerLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/drawer_layout"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context="com.ringofblades.stackoverflow.app.MainActivity">

    <TabHost
        android:id="@android:id/tabhost"
        android:layout_width="match_parent"
        android:layout_height="match_parent">
        <LinearLayout android:orientation="vertical"
            android:layout_width="match_parent"
            android:layout_height="match_parent">
            <FrameLayout android:id="@android:id/tabcontent"
                android:background="@drawable/background_image"
                android:layout_width="match_parent"
                android:layout_weight="0.5"
                android:layout_height="0dp"/>
            <android.support.v4.view.ViewPager
                xmlns:tools="http://schemas.android.com/tools"
                android:id="@+id/pager"
                android:background="@drawable/background_image"
                android:layout_width="match_parent"
                android:layout_weight="0.5"
                android:layout_height="0dp"
                tools:context="com.ringofblades.stackoverflow.app.MainActivity">
                <FrameLayout
                    android:id="@+id/container"
                    android:layout_width="match_parent"
                    android:layout_height="match_parent" />
            </android.support.v4.view.ViewPager>
            <TabWidget android:id="@android:id/tabs"
                android:layout_width="match_parent"
                android:layout_height="wrap_content" />
        </LinearLayout>
    </TabHost>

    <fragment android:id="@+id/navigation_drawer"
        android:layout_width="@dimen/navigation_drawer_width"
        android:layout_height="match_parent"
        android:layout_gravity="start"
        android:name="com.ringofblades.stackoverflow.app.NavigationDrawerFragment"
        tools:layout="@layout/fragment_navigation_drawer" />
</android.support.v4.widget.DrawerLayout>

Observe que "@ + id / pager" e "@ + id / container" são irmãos com 'android: layout_weight = "0,5"' e 'android: layout_height = "0dp"'. Isso é para que eu possa ver no visualizador para qualquer tamanho de tela. Seus pesos serão manipulados no código durante a execução, de qualquer maneira.

Kain Shin
fonte
Olá, estou curioso para saber por que você escolheu usar o TabHost em vez do ActionBar com guias? Eu mesmo mudei de TabHost para apenas ActionBar, e meu código ficou mais limpo e compacto ...
IgorGanapolsky
Pelo que me lembro, uma desvantagem de usar guias no ActionBar é que ele decide mostrá-los automaticamente como um menu suspenso giratório (no caso de uma tela pequena) e isso não era bom para mim. Mas não tenho 100% de certeza.
WindRider
@Igor, li em algum lugar aqui SOque usar ActionBarabas com um Navigation Drawernão é bom porque as colocará automaticamente sobre a visualização da sua gaveta. Não tenho o link para fazer o backup.
Azurespot
1
@NoniA. blog.xamarin.com/android-tips-hello-toolbar-goodbye-action-bar Você acompanha o desenvolvimento do Android?
IgorGanapolsky de
1
Nossa, obrigado pelo link @Igor! Vou verificar isso com certeza. Ainda sou um iniciante, então tenho um milhão de outras coisas para aprender com o Android, mas este parece uma joia! Obrigado novamente.
Azurespot
1

Com base na resposta de @Christ.Jenkins, esta é a solução que tem funcionado bem para mim, para remover fragmento (s) durante os eventos do ciclo de vida (que têm a tendência de lançar IllegalStateExceptions). Isso usa uma combinação da abordagem Handler e uma verificação Activity.isFinishing () (caso contrário, ele lançará um erro para "Não é possível executar esta ação após onSaveInstanceState).

import android.app.Activity;
import android.os.Handler;
import android.support.annotation.Nullable;
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentManager;
import android.support.v4.app.FragmentTransaction;

public abstract class BaseFragment extends Fragment {
    private final Handler handler = new Handler();

    /**
     * Removes the {@link Fragment} using {@link #getFragmentManager()}, wrapped in a {@link Handler} to
     * compensate for illegal states.
     *
     * @param fragment The {@link Fragment} to schedule for removal.
     */
    protected void removeFragment(@Nullable final Fragment fragment) {
        if (fragment == null) return;

        final Activity activity = getActivity();
        handler.post(new Runnable() {
            @Override
            public void run() {
                if (activity != null && !activity.isFinishing()) {
                    getFragmentManager().beginTransaction()
                            .remove(fragment)
                            .commitAllowingStateLoss();
                }
            }
        });
    }

    /**
     * Removes each {@link Fragment} using {@link #getFragmentManager()}, wrapped in a {@link Handler} to
     * compensate for illegal states.
     *
     * @param fragments The {@link Fragment}s to schedule for removal.
     */
    protected void removeFragments(final Fragment... fragments) {
        final FragmentManager fragmentManager = getFragmentManager();
        final FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();

        for (Fragment fragment : fragments) {
            if (fragment != null) {
                fragmentTransaction.remove(fragment);
            }
        }

        final Activity activity = getActivity();
        handler.post(new Runnable() {
            @Override
            public void run() {
                if (activity != null && !activity.isFinishing()) {
                    fragmentTransaction.commitAllowingStateLoss();
                }
            }
        });
    }
}

Uso:

class MyFragment extends Fragment {
    @Override
    public void onDestroyView() {
        removeFragments(mFragment1, mFragment2, mFragment3);
        super.onDestroyView();
    }
}
HelloImKevo
fonte
1

Embora o OP possa ter circunstâncias especiais que o impeçam de usar a Biblioteca de Suporte, a maioria das pessoas deve usá-la. A documentação do Android recomenda isso e tornará seu aplicativo disponível para o maior público possível.

Em minha resposta mais completa aqui , fiz um exemplo demonstrando como usar fragmentos aninhados com a biblioteca de suporte.

insira a descrição da imagem aqui

Suragch
fonte
Eu era o OP. A razão para não usar a biblioteca de suporte foi porque era um aplicativo interno da empresa, onde o hardware usado foi claramente definido como> = 4.0 e <= 4.1. Não houve necessidade de atingir um público amplo, era uma equipe interna e nenhuma intenção de usar o aplicativo fora da empresa. A única razão da biblioteca de suporte é ser compatível com versões anteriores - mas seria de se esperar que tudo o que você pode fazer com a biblioteca de suporte seja capaz de conseguir "nativamente" sem ela. Por que uma versão superior "nativa" teria menos recursos do que uma biblioteca de suporte cujo único propósito é ser compatível com versões anteriores?
Mathias Conradt
1
No entanto, é claro que você poderia usar a biblioteca de suporte e eu também. Só não entendi por que o Google oferece recursos SOMENTE na biblioteca de suporte, mas não fora, ou por que eles chamariam de biblioteca de suporte e não a tornariam o padrão geral, se é a melhor prática de qualquer maneira. Aqui está um bom artigo sobre a biblioteca de suporte: martiancraft.com/blog/2015/06/android-support-library
Mathias Conradt
@Mathias, bom artigo.
Suragch