问题
Let's say I've got an Activity
with only a FrameLayout
(I've also try with the new FragmentContainerView
) so I will be loading Fragments
on it. Let's assume I've got two Fragments: fragA and fragB, I will first load fragA and with a button, present in that fragment, replace it with fragB.
I've define a function on my Activity
so I can load a new Fragment
on the container:
public void loadFragment(Fragment fragment) {
FragmentTransaction fragTrans = getSupportFragmentManager()
.beginTransaction()
.replace(R.id.container, fragment)
.addToBackStack(null);
fragTrans.commit();
}
And I will call this from fragA to go to fragB. This far so good, everything works fine. Actually the problem I'm facing is not about functioning, is that I don't like the loading time of fragB, it takes about 2sec, and I'd like to display a loading to the user, but I'm not able to achieve it.
The first thing I've try is adding a gone ProgressBar
to the activity layout, where the container is. Then my loadFragment
function will first set this progressBar visible and then perform the fragment transaction and each fragment will hide (gone) this View before exiting the onCreateView
method. But the problem is that the user never sees this ProgressBar
, it's like the main UI is freezed while rendering the layout of the fragB and therefore does not update the UI making this progressBar usesless. I even see some frame skipped in the LogCat about 50 frames.
Both fragments have no logic just the onCreateView
implemented where the layout is inflated. I can notice that if I change the layout of fragB to a simpler one it loads instantly, so I'm guessing the issue is related to the time it takes to render the layout. This heavy layout is a really big form with lots of TextInputLayout
with TextInputEditText
and MaterialSpinner
(from GitHub).
I know I maybe can simplify the layout, but I'd like to know how can I display a loading while rendering. For example, I've seen some apps load some kind of dummy-view while loading and then replace it with real data.
One thing I've try is to load a dummy layout with a ProgressBar
in the middle and in the onCreateView
method post a Handler
to inflate a real layout on the same container in the background, like this:
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);
}
});
}
This kinda work, as the viewing experience is nice, but when navigating back, the layout of fragB remains visible as background of the other fragments. I have not test it but maybe I can call container.removeAllViews();
before exiting the fragment and it will work, but still seems like a workaround rather than a solution, to me.
And other thing I've not try because maybe is an over-kill is to have an intermediate or loading fragment and load it always before the real fragment, and I will pass an Intent Extra to it so I can tell what's the real fragment I want to display. This intermediate fragment will not be added to the backstack.
How do you solve this kind of problems?
回答1:
I think you have to use AsycTask when your new fragment loading
1- Load the new fragment
2 -Start Async task
3 - Initialize Progressdialog in constructor of AsyncTask where you want to load the data
4 - Show Progressdialog in onPreExecute() in your AsyncTask with dialog.show()
5 - Dismiss the Progressdialog in onPostExecute() in your AsyncTask with dialog.dismiss()
6- Update the UI / set the new data to your adapter etc
Check here Show a loading spinner while a fragment is loading
回答2:
Well it seems that AsyncLayoutInflater
is the way to go!
Here's how I use it:
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();
}
Up there, fragment_dummy_loading is a layout with a ProgressBar
and fragment_real_layout is the real heavy layout.
There's just one thing I don't get... and that's how can I bind Objects to the XML widgets (without using Android Data Binding, if possible) when the AsyncLayoutInflater
fallsback to infalte in the UI thread.
According to the docs:
If the layout that is trying to be inflated cannot be constructed asynchronously for whatever reason, AsyncLayoutInflater will automatically fall back to inflating on the UI thread.
And I check the source of AsyncLayoutInflater
and I can tell that the method onInflateFinished is called no matter if the inflation was in the background or in the main thread
来源:https://stackoverflow.com/questions/61034263/android-fragment-display-a-loading-while-rendering-layout