Начать операцию из фрагмента с использованием Transition (поддержка API 21)

Я пытаюсь перенести приложение Android в новую библиотеку поддержки (support-v4: 21.0.0), и у меня возникли проблемы с запуском Activity from Fragments с переходом.

В моих действиях я делал что-то вроде:

Bundle options = ActivityOptionsCompat.makeSceneTransitionAnimation(this).toBundle();
ActivityCompat.startActivityForResult(this, intent, REQUEST_SOMETHING, options);

который отлично подходит для деятельности. Однако, если я попытаюсь сделать что-то подобное с фрагментами, например:

Activity activity = getActivity();
Bundle options = ActivityOptionsCompat.makeSceneTransitionAnimation(activity).toBundle();
ActivityCompat.startActivityForResult(activity, intent, REQUEST_SOMETHING, options);

получается, что onActivityResult() не вызывается для Фрагмента, а только входящая активность. Я не нашел ничего в библиотеке поддержки, чтобы передать параметры Bundle в качестве параметра startActivityForResult() на фактическом фрагменте и вернуть его к onActivityResult() в этом фрагменте. Возможно ли это?

Простейшим решением было бы обрабатывать все вызовы onActivityResult() в самой Деятельности, но я бы предпочел не делать этого, потому что у меня есть тонна возможных фрагментов, которые могут получать этот обратный вызов.

Помощь приветствуется. Спасибо!

Ответы

Ответ 1

К сожалению, ActivityCompat.startActivityForResult() работает не совсем правильно в Fragments (см. ответ Алекс Локвуд). Несколько недель я удивлялся тому, как Google никогда не предоставлял нам метод ActivityCompat, эквивалентный реализации фрагмента startActivityForResult(). О чем они думали?! Но тогда у меня возникла идея: давайте взглянем на то, как метод действительно реализован.

На самом деле startActivityForResult() в фрагменте отличается от того, что находится в Activity (см. здесь):

public void startActivityForResult(Intent intent, int requestCode) {
    if (mActivity == null) {
        throw new IllegalStateException("Fragment " + this + " not attached to Activity");
    }
    mActivity.startActivityFromFragment(this, intent, requestCode);
}

Теперь startActivityFromFragment() выглядит следующим образом (см. здесь):

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));
}

Google использует некоторое нечетное смещение байта в коде запроса, чтобы впоследствии вызывать только вызывающий фрагмент onActivityResult(). Теперь, поскольку ActivityCompat не предоставляет никаких startActivityFromFragment(), единственный вариант - реализовать его самостоятельно. Для доступа к частному полю пакета mIndex требуется отражение.

public static void startActivityForResult(Fragment fragment, Intent intent,
                                          int requestCode, Bundle options) {
    if (Build.VERSION.SDK_INT >= 16) {
        if ((requestCode & 0xffff0000) != 0) {
            throw new IllegalArgumentException("Can only use lower 16 bits" +
                                               " for requestCode");
        }
        if (requestCode != -1) {
            try {
                Field mIndex = Fragment.class.getDeclaredField("mIndex");
                mIndex.setAccessible(true);
                requestCode = ((mIndex.getInt(this) + 1) << 16) + (requestCode & 0xffff);
            } catch (NoSuchFieldException | IllegalAccessException e) {
                throw new RuntimeException(e);
            }
        }
        ActivityCompat.startActivityForResult(fragment.getActivity(), intent,
                                              requestCode, options);
    } else {
        fragment.getActivity().startActivityFromFragment(this, intent, requestCode);
    }
}

Скопируйте этот метод в любом месте и используйте его из вашего фрагмента. Его onActivityResult() будет вызываться как следует.

UPDATE: Была выпущена библиотека поддержки v23.2, и кажется, что startActivityFromFragment(Fragment fragment, Intent intent, int requestCode, Bundle options) выполняет эту работу:)

Ответ 2

Метод ActivityCompat#startActivityForResult() является просто прокси для метода активности startActivityForResult(Intent, Bundle). Вызов метода изнутри класса фрагмента не означает, что Fragment onActivityResult() в конечном итоге будет вызван, поскольку я уверен, что вы это узнали. Структура знает способ узнать, из какого класса возник этот вызов... единственным правильным поведением было бы вызвать метод Activity onActivityResult() в этом случае.

Похоже, что лучшим вариантом было бы обработать все в методе onActivityResult(), как вы предложили в своем сообщении.

Ответ 3

Вы можете создать интерфейс прослушивателя или просто публичную функцию в своем фрагменте и передать аргументы, из которых вы получаете в onActivityResult() действия, к слушателю или общедоступному методу фрагмента и выполнять свою работу там.

Ответ 4

Простой способ:

в фрагменте:

 ActivityOptionsCompat options = ActivityOptionsCompat.makeSceneTransitionAnimation();

 this.startActivityFromFragment(this, intent, requestCode, options);