tutorial studio inside fragments example change activity android android-fragments android-nested-fragment

android - studio - onActivityResult() no llamado en la nueva API de fragmento anidado



fragment example android (7)

He estado usando la nueva API de fragmento anidado que Android incluye en la biblioteca de soporte.

El problema al que me enfrento con fragmentos anidados es que, si un fragmento anidado (es decir, un fragmento que se ha agregado a otro fragmento mediante FragmentManager devuelto por getChildFragmentManager() ) llama a startActivityForResult() , el método onActivityResult() del fragmento anidado no es llamado. Sin embargo, tanto el fragmento padre onActivityResult() como el activity onActivityResult() se llaman.

No sé si me falta algo sobre los fragmentos anidados, pero no esperaba el comportamiento descrito. A continuación se muestra el código que reproduce este problema. Agradecería mucho que alguien me señale la dirección correcta y me explique lo que estoy haciendo mal:

package com.example.nestedfragmentactivityresult; import android.media.RingtoneManager; import android.os.Bundle; import android.content.Intent; import android.support.v4.app.Fragment; import android.support.v4.app.FragmentActivity; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.Button; import android.widget.Toast; public class MainActivity extends FragmentActivity { public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); this.getSupportFragmentManager() .beginTransaction() .add(android.R.id.content, new ContainerFragment()) .commit(); } public void onActivityResult(int requestCode, int resultCode, Intent data) { super.onActivityResult(requestCode, resultCode, data); // This is called Toast.makeText(getApplication(), "Consumed by activity", Toast.LENGTH_SHORT).show(); } public static class ContainerFragment extends Fragment { public final View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { View result = inflater.inflate(R.layout.test_nested_fragment_container, container, false); return result; } public void onActivityCreated(Bundle savedInstanceState) { super.onActivityCreated(savedInstanceState); getChildFragmentManager().beginTransaction() .add(R.id.content, new NestedFragment()) .commit(); } public void onActivityResult(int requestCode, int resultCode, Intent data) { super.onActivityResult(requestCode, resultCode, data); // This is called Toast.makeText(getActivity(), "Consumed by parent fragment", Toast.LENGTH_SHORT).show(); } } public static class NestedFragment extends Fragment { public final View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { Button button = new Button(getActivity()); button.setText("Click me!"); button.setOnClickListener(new View.OnClickListener() { public void onClick(View v) { Intent intent = new Intent(RingtoneManager.ACTION_RINGTONE_PICKER); startActivityForResult(intent, 0); } }); return button; } public void onActivityResult(int requestCode, int resultCode, Intent data) { super.onActivityResult(requestCode, resultCode, data); // This is NOT called Toast.makeText(getActivity(), "Consumed by nested fragment", Toast.LENGTH_SHORT).show(); } } }

test_nested_fragment_container.xml es:

<?xml version="1.0" encoding="utf-8"?> <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/content" android:layout_width="match_parent" android:layout_height="match_parent" > </FrameLayout>


¡Me enfrenté al mismo problema! Lo resolví usando ChildFragment estático en ParentFragment , así:

private static ChildFragment mChildFragment; protected void onActivityResult(int requestCode, int resultCode, Intent data) { mChildFragment.onActivityResult(int requestCode, int resultCode, Intent data); }


Así es como lo resolví.

En actividad:

@Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { super.onActivityResult(requestCode, resultCode, data); List<Fragment> frags = getSupportFragmentManager().getFragments(); if (frags != null) { for (Fragment f : frags) { if (f != null) handleResult(f, requestCode, resultCode, data); } } } private void handleResult(Fragment frag, int requestCode, int resultCode, Intent data) { if (frag instanceof IHandleActivityResult) { // custom interface with no signitures frag.onActivityResult(requestCode, resultCode, data); } List<Fragment> frags = frag.getChildFragmentManager().getFragments(); if (frags != null) { for (Fragment f : frags) { if (f != null) handleResult(f, requestCode, resultCode, data); } } }


Este problema se ha solucionado en Android Support Library 23.2 en adelante. Ya no tenemos que hacer nada extra con Fragment en Android Support Library 23.2+. onActivityResult() ahora se llama en el Fragment anidado como se esperaba.

Acabo de probarlo usando la biblioteca de soporte de Android 23.2.1 y funciona. ¡Finalmente puedo tener un código más limpio!

Entonces, la solución es comenzar a usar la última biblioteca de soporte de Android.


He buscado la fuente FragmentActivity. Encontré estos hechos.

  1. cuando el fragmento llama a startActivityForResult (), en realidad llama a su padre FragmentActivity''s startAcitivityFromFragment ().
  2. cuando la actividad de inicio del fragmento para el resultado, el requestCode debe ser inferior a 65536 (16 bits).
  3. en StartActivityFromFragment () de FragmentActivity, se llama Activity.startActivityForResult (), esta función también necesita un requestCode, pero este requestCode no es igual al requestCode de origen.
  4. En realidad, requestCode en FragmentActivity tiene dos partes. el valor más alto de 16 bits es (el índice del fragmento en FragmentManager) + 1.el 16 bit inferior es igual al código de solicitud de origen. es por eso que requestCode debe estar debajo de 65536.

mira el código en FragmentActivity.

public void startActivityFromFragment(Fragment fragment, Intent intent, int requestCode) { if (requestCode == -1) { super.startActivityForResult(intent, -1); return; } if ((requestCode&0xffff0000) != 0) { throw new IllegalArgumentException("Can only use lower 16 bits for requestCode"); } super.startActivityForResult(intent, ((fragment.mIndex+1)<<16) + (requestCode&0xffff)); }

cuando result back.FragmentActivity anula la función onActivityResult, y verifica si el 16bit superior de requestCode no es 0, pasa el test onActivityResult a su fragmento secundario.

@Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { mFragments.noteStateNotSaved(); int index = requestCode>>16; if (index != 0) { index--; final int activeFragmentsCount = mFragments.getActiveFragmentsCount(); if (activeFragmentsCount == 0 || index < 0 || index >= activeFragmentsCount) { Log.w(TAG, "Activity result fragment index out of range: 0x" + Integer.toHexString(requestCode)); return; } final List<Fragment> activeFragments = mFragments.getActiveFragments(new ArrayList<Fragment>(activeFragmentsCount)); Fragment frag = activeFragments.get(index); if (frag == null) { Log.w(TAG, "Activity result no fragment exists for index: 0x" + Integer.toHexString(requestCode)); } else { frag.onActivityResult(requestCode&0xffff, resultCode, data); } return; } super.onActivityResult(requestCode, resultCode, data); }

así es como FragmentActivity distribuye el eventoActivityResult.

cuando tienes una actividad fragmentActivity, contiene fragment1 y fragment1 contiene fragment2. Entonces onActivityResult solo puede pasar a fragment1.

Y que encuentro una manera de resolver este problema. Primero cree un fragmento de extensión NestedFragment que anule la función startActivityForResult.

public abstract class NestedFragment extends Fragment { @Override public void startActivityForResult(Intent intent, int requestCode) { List<Fragment> fragments = getFragmentManager().getFragments(); int index = fragments.indexOf(this); if (index >= 0) { if (getParentFragment() != null) { requestCode = ((index + 1) << 8) + requestCode; getParentFragment().startActivityForResult(intent, requestCode); } else { super.startActivityForResult(intent, requestCode); } } }

}

que crear MyFragment extender Anulación de Fragmento en la función de Respuesta de Actividad.

public abstract class MyFragment extends Fragment { @Override public void onActivityResult(int requestCode, int resultCode, Intent data) { int index = requestCode >> 8; if (index > 0) { //from nested fragment index--; List<Fragment> fragments = getChildFragmentManager().getFragments(); if (fragments != null && fragments.size() > index) { fragments.get(index).onActivityResult(requestCode & 0x00ff, resultCode, data); } } }

}

¡Eso es todo! Uso:

  1. fragment1 extiende MyFragment.
  2. fragment2 extiende NestedFragment.
  3. No más diferentes. Solo llame a startActivityForResult () en fragment2, y anule la función onActivityResult ().

Waring !!!!!!!! El requestCode debe estar debajo de 512 (8bit), porque derramamos el requestCode en 3 partes

16bit | 8bit | 8 bits

cuanto mayor sea el Android de 16 bits ya utilizado, el 8bit medio es para ayudar a fragmentar1 a encontrar el fragmento2 en su matriz fragmentManager. El 8bit más bajo es el código de solicitud de origen.


Para la Actividad principal escribes OnActivityForResult con Super clase como

@Override public void onActivityResult(int requestCode, int resultCode, Intent result) { super.onActivityResult(requestCode, resultCode, result); if (requestCode == Crop.REQUEST_PICK && resultCode == RESULT_OK) { beginCrop(result.getData()); } }

Para la actividad Write intent sin getActivity () como

Intent pickContactIntent = new Intent(Intent.ACTION_PICK, Uri.parse("content://contacts")); pickContactIntent.setType(ContactsContract.CommonDataKinds.Phone.CONTENT_TYPE); // Show user only contacts w/ phone numbers startActivityForResult(pickContactIntent, 103);

OnActivityResult for fragment

@Override public void onActivityResult(int requestCode, int resultCode, Intent data) { if (requestCode == 101 && resultCode == getActivity().RESULT_OK && null != data) {

}}


Resolví este problema con el siguiente código (se usa la biblioteca de soporte):

En fragmento de contenedor, anule onActivityResult de esta manera:

@Override public void onActivityResult(int requestCode, int resultCode, Intent data) { super.onActivityResult(requestCode, resultCode, data); List<Fragment> fragments = getChildFragmentManager().getFragments(); if (fragments != null) { for (Fragment fragment : fragments) { fragment.onActivityResult(requestCode, resultCode, data); } } }

Ahora el fragmento anidado recibirá una llamada al método onActivityResult.

Además, como noted Eric Brynsvold en una pregunta similar, el fragmento anidado debería comenzar la actividad utilizando su fragmento padre y no la simple llamada startActivityForResult (). Entonces, en la actividad de inicio de fragmento anidado con:

getParentFragment().startActivityForResult(intent, requestCode);


Sí, el onActivityResult() en fragmento anidado no se invocará de esta manera.

La secuencia de llamada de onActivityResult (en la biblioteca de soporte de Android) es

  1. Activity.dispatchActivityResult() .
  2. FragmentActivity.onActivityResult() .
  3. Fragment.onActivityResult() .

En el 3er paso, el fragmento se encuentra en el FragmentMananger de Parent Activity . Entonces, en su ejemplo, es el fragmento del contenedor que se encuentra que se distribuye en el onActivityResult() , el fragmento anidado nunca podría recibir el evento.

Creo que debe implementar su propio despacho en ContainerFragment.onActivityResult() , buscar el fragmento anidado e invocar para pasarle el resultado y los datos.