Фрагмент Android отображает загрузку при рендеринге макета

Допустим, у меня есть Activity только с FrameLayout (я также пробовал с новым FragmentContainerView), поэтому я буду загружать на него Fragments. Предположим, у меня есть два фрагмента: fragA и fragB. Сначала я загружу fragA и с помощью кнопки, присутствующей в этом фрагменте, < em>замените на fragB.

Я определил функцию для своего Activity, чтобы я мог загрузить новый Fragment в контейнер:

public void loadFragment(Fragment fragment) {
    FragmentTransaction fragTrans = getSupportFragmentManager()
            .beginTransaction()
            .replace(R.id.container, fragment)
            .addToBackStack(null);

    fragTrans.commit();
}

И я вызову это из fragA, чтобы перейти к fragB. Пока все хорошо, все работает. На самом деле проблема, с которой я сталкиваюсь, связана не с работой, а с тем, что мне не нравится время загрузки fragB, оно занимает около 2 секунд, и я хотел бы отобразить загрузку для пользователя, но я не могу этого добиться.

Первое, что я попробовал, это добавить gone ProgressBar в макет действия, где находится контейнер. Затем моя функция loadFragment сначала сделает видимым этот progressBar, а затем выполнит транзакцию фрагмента, и каждый фрагмент скроет (уйдет) это представление перед выходом из метода onCreateView. Но проблема в том, что пользователь никогда не видит этот ProgressBar, это похоже на то, что основной пользовательский интерфейс замораживается при рендеринге макета fragB и, следовательно, не обновляет пользовательский интерфейс, делая этот progressBar бесполезным. Я даже вижу некоторые пропущенные кадры в LogCat около 50 кадров.

В обоих фрагментах нет никакой логики, только onCreateView реализовано там, где макет раздут. Я могу заметить, что если я изменяю макет fragB на более простой, он загружается мгновенно, поэтому я предполагаю, что проблема связана со временем, которое требуется для рендеринга макета. Этот тяжелый макет представляет собой действительно большую форму с большим количеством TextInputLayout с TextInputEditText и MaterialSpinner (из GitHub).

Я знаю, что могу упростить макет, но я хотел бы знать, как я могу отображать загрузку во время рендеринга. Например, я видел, как некоторые приложения загружали какое-то фиктивное представление во время загрузки, а затем заменяли его реальными данными.

Я попробовал загрузить фиктивный макет с ProgressBar посередине и в методе onCreateView опубликовать Handler, чтобы раздуть настоящий макет в том же контейнере в фон примерно такой:

public View onCreateView(final LayoutInflater inflater, final ViewGroup container, Bundle savedInstanceState) {
    View rootView = inflater.inflate(R.layout.fragment_dummy, container, false);

    Handler mHandler = new Handler();
    mHandler.post(new Runnable() {
        @Override
        public void run() {
            container.removeAllViews();
            View realView = inflater.inflate(R.layout.fragment_real, container);
        }
    });
}

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

И еще одна вещь, которую я не пробовал, потому что, возможно, это чрезмерное убийство, это иметь промежуточный или загружающий фрагмент и всегда загружать его перед реальным фрагментом, и я пропущу Intent Extra, чтобы я мог сказать, какой настоящий фрагмент я хочу отобразить. Этот промежуточный фрагмент не будет добавлен в стопку.

Как вы решаете такого рода проблемы?


person sebasira    schedule 04.04.2020    source источник
comment
Я помню, что они называют представление Skeleton фиктивным представлением, на которое я ссылался в своем посте. И это тоже будет похоже на Shimmer, но так как проблема в том, что пользовательский интерфейс завис, я не могу обновить пользовательский интерфейс.   -  person sebasira    schedule 04.04.2020
comment
Добавьте объект статического диалога в класс приложения (в котором вы можете показать прогресс), используя действие или контекст приложения. Показать перед загрузкой и скрыть после загрузки.   -  person Nilesh B    schedule 04.04.2020
comment
@NileshB спасибо за ваше предложение. Я пробовал это безуспешно, то же самое, что и с progressBar, о котором я говорю, он зависает и ничего не показывает. Я думал, что статика может помочь, но нет. Прямо сейчас единственное рабочее решение, которое я нашел, - это решение с Handler и удалением всех представлений в методе onDestroyView. Но мне кажется некрасивым   -  person sebasira    schedule 04.04.2020


Ответы (2)


Я думаю, вам нужно использовать AsycTask при загрузке нового фрагмента.

1- Загрузите новый фрагмент

2 - Запустить асинхронную задачу

3 - Инициализируйте Progressdialog в конструкторе AsyncTask, куда вы хотите загрузить данные

4 - Показать Progressdialog в onPreExecute() в вашей AsyncTask с помощью dialog.show()

5 - Закройте диалог Progressdialog в onPostExecute() в вашей AsyncTask с помощью dialog.dismiss()

6- Обновите пользовательский интерфейс / установите новые данные для вашего адаптера и т. д.

Отметьте здесь Показать счетчик загрузки во время загрузки фрагмента

person Okan Serdaroğlu    schedule 04.04.2020
comment
Спасибо! Я бы предпочел не использовать AsyncTask, так как они объявлены (или будут) устаревшими: developer. android.com/reference/android/os/AsyncTask - person sebasira; 04.04.2020
comment
Ты прав. Я имею в виду, что вы должны использовать фонового рабочего при открытии второго фрагмента. Rx Java может быть хорошей альтернативой. Или вы можете использовать Coroutines, если вы используете Kotlin. techyourchance.com/asynctask-deprecated - person Okan Serdaroğlu; 04.04.2020
comment
К сожалению, я не увлекаюсь Kotlin (по крайней мере, пока), а что касается фонового рабочего, то я думаю о Handler, потому что это простота. Значит, вы тоже думаете, что фоновый работник — это то, что вам нужно? Странно, что нет готовых решений. Я читал о AsyncLayoutInflater (developer.android.com/reference/androidx/ asynclayoutinflater/), но я не знаю, поможет ли это мне - person sebasira; 05.04.2020
comment
Ну, обработчик, кажется, не работает. Я тестировал его с помощью простого макета с TextView, который говорит о загрузке, и он работал, но когда я меняю его на ProgressBar, я вижу только пустой экран. Опять же, пользовательский интерфейс заморожен, поэтому кажется, что индикатор выполнения не анимирован. И я предполагаю, что то же самое произойдет с любым фоновым рабочим, потому что инфляция использует поток пользовательского интерфейса, верно? - person sebasira; 05.04.2020
comment
Я только что прочитал вашу ссылку. Спасибо, мне тоже пригодится. Я вижу из вашей ссылки, что AsyncLayoutInflater асинхронно раздувает ваши представления. Это может быть верно для вашего случая, однако использование RxJava также может быть правдой. Я думаю, вы пробовали оба из них. - person Okan Serdaroğlu; 05.04.2020
comment
инфляция использует пользовательский интерфейс. По этой причине вы должны использовать фоновый поток для анимации вашего прогресса. Решительно попробуйте RxJava - person Okan Serdaroğlu; 05.04.2020
comment
Но как я могу сделать любую анимацию с замороженным пользовательским интерфейсом? это меня смущает - person sebasira; 05.04.2020
comment
Я думаю, что они будут в разных темах. Так что это не проблема. Однако в вашем целевом фрагменте вы также должны проверить свои взгляды. Почему они мерзнут при надувании. Возможно, вам нужно сделать оптимизацию в вашем макете - person Okan Serdaroğlu; 05.04.2020
comment
Ну, насколько мне известно, на Android есть один и только один поток main/UI... Я тестирую с AsyncLayoutInflater, пока все хорошо. Отображается ProgressBar и происходит обратный вызов, когда представление завершается. Теперь мне нужно посмотреть, как применить/отобразить завышенное представление, потому что я все время вижу только ProgressBar - person sebasira; 05.04.2020

Что ж, кажется, AsyncLayoutInflater - это то, что нужно!

Вот как я его использую:

private ViewGroup fragmentContainer;

public View onCreateView(LayoutInflater inflater, ViewGroup container,
                         Bundle savedInstanceState) {
    View dummyView = inflater.inflate(R.layout.fragment_dummy_loading, container, false);

    fragmentContainer = container;
    AsyncLayoutInflater asyncLayoutInflater = new AsyncLayoutInflater(getContext());


    asyncLayoutInflater.inflate(R.layout.fragment_real_layout, container, new AsyncLayoutInflater.OnInflateFinishedListener() {
        @Override
        public void onInflateFinished(@NonNull View view, int resid, @Nullable ViewGroup parent) {
            fragmentContainer.removeAllViews();
            fragmentContainer.addView(view);
        }
    });


    return dummyView;
}


@Override
public void onStop() {
    super.onStop();
    fragmentContainer.removeAllViews();
}

Там вверху fragment_dummy_loading — это макет с ProgressBar, а fragment_real_layout — это очень тяжелый макет.

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

Согласно документам:

Если макет, который пытается раздуть, не может быть создан асинхронно по какой-либо причине, AsyncLayoutInflater автоматически вернется к раздуванию в потоке пользовательского интерфейса.

И я проверяю источник AsyncLayoutInflater и могу сказать, что метод onInflateFinished вызывается независимо от того, была ли инфляция в фоновом режиме или в основном потоке.

person sebasira    schedule 04.04.2020
comment
Когда я реализую эту ошибку, представления, добавленные в представление фрагментов, должны быть связаны с фрагментом. просмотр androidx.constraintlayout.widget.constraintlayout У вас есть решение? - person Ahmad; 02.03.2021
comment
Извините, @Ahmad, я никогда не сталкивался с этой ошибкой. Может быть, вы можете создать новый вопрос, опубликовать более подробную информацию и отправить мне ссылку, чтобы я мог ее посмотреть. - person sebasira; 02.03.2021