J'utilise la nouvelle fragment imbriqué API que Android inclut dans la bibliothèque de support.
Le problème auquel je suis confronté avec les fragments imbriqués est que, si un fragment imbriqué (c'est-à-dire un fragment qui a été ajouté à un autre fragment via FragmentManager
returned by getChildFragmentManager()
) appelle startActivityForResult()
, la méthode onActivityResult()
du fragment imbriqué n'est pas appelée. Cependant, la fonction onActivityResult()
et la fonction onActivityResult()
du fragment parent sont appelées.
Je ne sais pas si je manque quelque chose sur les fragments imbriqués, mais je ne m'attendais pas au comportement décrit. Voici le code qui reproduit ce problème. J'apprécierais beaucoup que quelqu'un puisse m'orienter dans la bonne direction et m'expliquer ce que je fais 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 est:
<?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>
Oui, la onActivityResult()
dans le fragment imbriqué ne sera pas invoquée de cette façon.
La séquence d'appel de onActivityResult (dans Android) est
Activity.dispatchActivityResult()
.FragmentActivity.onActivityResult()
.Fragment.onActivityResult()
.Dans la 3ème étape, le fragment se trouve dans le FragmentMananger
du parent Activity
. Donc, dans votre exemple, c'est le fragment de conteneur qui est trouvé pour distribuer onActivityResult()
, le fragment imbriqué n'a jamais pu recevoir l'événement.
Je pense que vous devez implémenter votre propre dispatch dans ContainerFragment.onActivityResult()
, trouver le fragment imbriqué et invoquer lui passer le résultat et les données.
J'ai résolu ce problème avec le code suivant (la bibliothèque de support est utilisée):
Dans le fragment de conteneur, remplacez onActivityResult de cette manière:
@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);
}
}
}
Désormais, le fragment imbriqué recevra un appel à la méthode onActivityResult.
En outre, comme notéEric Brynsvold dans une question similaire, le fragment imbriqué doit démarrer l'activité en utilisant son fragment parent et non le simple appel startActivityForResult (). Ainsi, dans un fragment imbriqué, commencez l'activité avec:
getParentFragment().startActivityForResult(intent, requestCode);
Voici comment je l'ai résolu.
En activité:
@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);
}
}
}
J'ai recherché la source FragmentActivity. Je trouve ces faits.
regardez le code dans 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));
}
lorsque le résultat est renvoyé.FragmentActivity remplace la fonction onActivityResult et vérifie si le 16 bits supérieur du requestCode n'est pas égal à 0, puis transmet onActivityResult à son fragment enfant.
@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);
}
c'est ainsi que FragmentActivity envoie l'événement onActivityResult.
lorsque vous avez une fragmentActivity, elle contient fragment1 et fragment1 contient fragment2. OnActivityResult ne peut donc passer qu'à fragment1.
Et que je trouve un moyen de résoudre ce problème. Créez d'abord un fragment NestedFragment extend Remplacez la fonction 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 de créer MyFragment étendre le remplacement de fragment sur la fonctionActivityResult.
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);
}
}
}
}
C'est tout! Usage:
Waring !!!!!!!! Le requestCode doit être inférieur à 512 (8 bits), car nous avons renversé le requestCode en 3 parties
16bit | 8bit | 8 bits
le 16 bits supérieur Android déjà utilisé, le 8 bits du milieu est destiné à aider fragment1 à trouver le fragment2 dans son tableau fragmentManager. le 8 bits le plus bas est le Origin requestCode.
Réponse mise à jour le 2 septembre 2019
Ce problème est de retour dans Androidx, lorsque vous utilisez une seule activité et que vous avez un fragment imbriqué dans la NavHostFragment
, onActivityResult()
n'est pas appelé pour le fragment enfant de NavHostFragment
.
Pour résoudre ce problème, vous devez router manuellement l'appel vers la onActivityResult()
des fragments enfants de onActivityResult()
de l'activité hôte.
Voici comment le faire en utilisant le code Kotlin. Cela va à l'intérieur de la onActivityResult()
de votre activité principale qui héberge la NavHostFragment
:
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
val navHostFragment = supportFragmentManager.findFragmentById(R.id.your_nav_Host_fragment)
val childFragments = navHostFragment?.childFragmentManager?.fragments
childFragments?.forEach { it.onActivityResult(requestCode, resultCode, data) }
}
Cela garantira que la onActivityResult()
des fragments enfants sera appelée normalement.
Ancienne réponse
Ce problème a été corrigé dans Android Support Library 23.2 en avant. Nous n'avons plus rien à faire de plus avec Fragment
dans Android Support Library 23.2+. onActivityResult()
est maintenant appelé dans le Fragment
comme prévu.
Je viens de le tester en utilisant la bibliothèque de support Android 23.2.1 et cela fonctionne. Enfin, je peux avoir un code plus propre!
Donc, la solution consiste à commencer à utiliser la dernière Android Support Library.
Pour l'activité principale, vous écrivez OnActivityForResult avec Super class comme as
@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());
} }
Pour l'activité Ecrire l'intention sans getActivity () comme as
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 pour le fragment
@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
if (requestCode == 101 && resultCode == getActivity().RESULT_OK && null != data) {
}}