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

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

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

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

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

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

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

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

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


person Damian Wieczorek    schedule 18.11.2014    source источник


Ответы (4)


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

На самом деле startActivityForResult() во Фрагменте отличается от startActivityForResult() в Активности (см. здесь):

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() будет называться как надо.

ОБНОВЛЕНИЕ: Выпущена библиотека поддержки v23.2, и кажется, что startActivityFromFragment(Fragment fragment, Intent intent, int requestCode, Bundle options) теперь справляется со своей задачей :)

person 0101100101    schedule 26.02.2015
comment
запуск действия для получения результата с использованием последнего предоставленного вами метода сработал для меня - большое спасибо за решение, хотя я не считаю его полностью чистым, поэтому будем надеяться, что Google ничего не изменит в отношении странного смещения байтов. - person user1071762; 26.03.2015
comment
Я предполагаю, что было бы чище сделать вызов из Activity, а затем передать результат из Activity во Fragment, но наличие множества фрагментов, прикрепленных к моей активности (из-за viewpager), делает это довольно сложным. - person user1071762; 26.03.2015
comment
@user2302510 user2302510 Точно, становится очень сложно справиться с этим самостоятельно. Рад, что это помогло! - person 0101100101; 26.03.2015

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

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

person Alex Lockwood    schedule 18.11.2014
comment
Я понимаю, почему это происходит, но от этого не становится менее неудобно. Это то, чего нет только в библиотеке поддержки, или это нельзя сделать и с нативными фрагментами? Я надеюсь, что они добавят эту функцию в будущем. - person Damian Wieczorek; 20.11.2014
comment
Вы пытались просто вызвать метод Fragment startActivityForResult(Intent, int, Bundle)? - person Alex Lockwood; 20.11.2014

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

person Nitesh    schedule 18.11.2014

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

во Фрагменте:

 ActivityOptionsCompat options = ActivityOptionsCompat.makeSceneTransitionAnimation();

 this.startActivityFromFragment(this, intent, requestCode, options);
person Jabin    schedule 21.07.2016