Reputation: 101
I have a TabLayout is set up with viewpager. The viewpager is set up with adapter. There are 11 fragments were added to adapter and each fragment has a recyclerview. When I run the app swiping between fragments goes slow. Can anyone tell me the reason of this issue?
my MainActivity:
myTabLayout.setTabMode(TabLayout.MODE_SCROLLABLE);
mainViewPager = (ViewPager) findViewById(R.id.mainViewPager);
tabLayoutAdapter = new TabLayoutAdapter(getSupportFragmentManager());
tabLayoutAdapter.addFragment(new HomeFragment());
tabLayoutAdapter.addFragment(new ClipFragment());
tabLayoutAdapter.addFragment(new MovieFragment());
tabLayoutAdapter.addFragment(new SerialFragment());
tabLayoutAdapter.addFragment(new MusicFragment());
tabLayoutAdapter.addFragment(new BookFragment());
tabLayoutAdapter.addFragment(new PixFragment());
tabLayoutAdapter.addFragment(new GameFragment());
tabLayoutAdapter.addFragment(new NewsFragment());
tabLayoutAdapter.addFragment(new LiveFragment());
tabLayoutAdapter.addFragment(new AdvancedSearchFragment());
mainViewPager.setAdapter(tabLayoutAdapter);
myTabLayout.setupWithViewPager(mainViewPager);
my adapter:
public class TabLayoutAdapter extends FragmentStatePagerAdapter {
List<Fragment> tabPages = new ArrayList<>();
public TabLayoutAdapter(FragmentManager fm) {
super(fm);
}
@Override
public Fragment getItem(int position) {
return tabPages.get(position);
}
@Override
public int getCount() {
return tabPages.size();
}
public void addFragment(Fragment page){
tabPages.add(page);
}
}
sample fragment:
public class MovieFragment extends Fragment {
RecyclerView movieRecyclerView;
List<Movie> movieList;
MovieAdapter movieAdapter;
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Log.d("Fragment", "thread = " + Thread.currentThread().getName());
Log.i("Fragment", "thread = " + Thread.currentThread().getName());
}
@Nullable
@Override
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
View movieView = inflater.inflate(R.layout.tab_movie, container, false);
return movieView;
}
@Override
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
movieRecyclerView = (RecyclerView) view.findViewById(R.id.movieRecyclerView);
movieList = new ArrayList<Movie>();
movieAdapter = new MovieAdapter(view.getContext(), movieList);
RecyclerView.LayoutManager mLayoutManager = new GridLayoutManager(view.getContext(), 2);
movieRecyclerView.setLayoutManager(mLayoutManager);
movieRecyclerView.addItemDecoration(new GridSpacingItemDecoration(2, dpToPx(10), false));
movieRecyclerView.setItemAnimator(new DefaultItemAnimator());
movieRecyclerView.setAdapter(movieAdapter);
prepareMovies();
}
public class GridSpacingItemDecoration extends RecyclerView.ItemDecoration {
private int spanCount;
private int spacing;
private boolean includeEdge;
public GridSpacingItemDecoration(int spanCount, int spacing, boolean includeEdge) {
this.spanCount = spanCount;
this.spacing = spacing;
this.includeEdge = includeEdge;
}
@Override
public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {
int position = parent.getChildAdapterPosition(view); // item position
int column = position % spanCount; // item column
if (includeEdge) {
outRect.left = spacing - column * spacing / spanCount; // spacing - column * ((1f / spanCount) * spacing)
outRect.right = (column + 1) * spacing / spanCount; // (column + 1) * ((1f / spanCount) * spacing)
if (position < spanCount) { // top edge
outRect.top = spacing;
}
outRect.bottom = spacing; // item bottom
} else {
outRect.left = column * spacing / spanCount; // column * ((1f / spanCount) * spacing)
outRect.right = spacing - (column + 1) * spacing / spanCount; // spacing - (column + 1) * ((1f / spanCount) * spacing)
if (position >= spanCount) {
outRect.top = spacing; // item top
}
}
}
}
private void prepareMovies() {
int[] covers = new int[]{
R.drawable.bossbaby,
R.drawable.abeautifulmind,
R.drawable.despicableme,
R.drawable.harrypotter,
R.drawable.thegodfather,
R.drawable.piratesofcaribbean,
};
Movie movie = new Movie("Boss Baby",10000,4.5,20000,16000, covers[0]);
movieList.add(movie);
movie = new Movie("A Beautiful mind",35100,4.9,40000,39000, covers[1]);
movieList.add(movie);
movie = new Movie("Despicable me",20000,4.7,33000,31000, covers[2]);
movieList.add(movie);
movie = new Movie("Harry Potter",90000,5.0,110000,105000, covers[3]);
movieList.add(movie);
movie = new Movie("The God Father",70000,4.9,90000,86700, covers[4]);
movieList.add(movie);
movie = new Movie("Pirates of caribbean",50000,4.6,70000,64000, covers[5]);
movieList.add(movie);
movieAdapter.notifyDataSetChanged();
}
private int dpToPx(int dp) {
Resources r = getResources();
return Math.round(TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dp, r.getDisplayMetrics()));
}
}
Upvotes: 5
Views: 2861
Reputation: 204
I've the same problem and could solve it by delaying the execution of "notifyDataSetChanged" by a 200-300 ms. This gives the UI thread enough time to finish when swiping from one fragment to the next. Although, I'm not sure if there is another better solution.
new Handler().postDelayed(new Runnable() {
public void run() {
movieAdapter.notifyDataSetChanged();
}
}, 200);
Upvotes: 0
Reputation: 2727
Try following things to make navigation smoother :
1) on each fragment move code from onViewCreated
to onActivityCreated
, because it is methods gets called when fragment appeared completely :
@Override
public void onActivityCreated(@Nullable Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
// put code of setting data to views etc.
}
2) implement following methods in FragmentStateAdapter
class :
@Override
public void destroyItem(ViewGroup container, int position, Object object) {
// super.destroyItem(container, position, object);
}
// Force a refresh of the page when a different fragment is displayed
@Override
public int getItemPosition(Object object) {
return POSITION_NONE;
}
3) Use some low resolution images in your fragment(if you are using them from resource folder). Dont use large resoultion like - 1024*960, try to compress them and then add.
4) Avoid using wrap_content
in layout files of fragments. because then it takes time to calculate the width height , If you can provide match_parent or any specific value in dp
, it will work much faster.
try these points may be these will help you.
Upvotes: 1
Reputation: 3729
Along with @Khemraj answer, try the following also because when we come back to same recyclerview
containing class then there are chances of issues regarding viewpager
adapter
tabLayoutAdapter = new TabLayoutAdapter(getChildFragmentManager());
instead of
tabLayoutAdapter = new TabLayoutAdapter(getSupportFragmentManager());
Upvotes: 0
Reputation: 58944
This question raises many times. And the solution is same.
Your Ui is hanging, that simply mean you are not managing UI/Main thread and Worker thread in a good way. You should understand that use UI thread only for setting UI.
For example. You call a webservice. It gives you array of items. You want set in List.
A bad way
Call webservice by Volley/Retrofit. Get response, parse it. Then set it in List. In this process you did manage use any Thread. Just Volly/Retrofit did it's work perfectly. But after that all the things happen in Main Thread.
A good way
Call webservice by Volley/Retrofit. Get response, parse it in worker thread. Then set it in List. In this process used worker thread to parse. And only used Main thread to set UI.
If you ask me why didn't you face this in earlier days?
Because you were doing work in Single Activity/ Fragment, where UI thread handled your parsing in non-considerable time. But you faced this issue because now you have 11 Fragments with 11 List.
Now when you have showed your code.
RecyclerView
is empty, set List when it is empty.Check this code, in this I used old list
public class MovieFragment extends Fragment {
RecyclerView movieRecyclerView;
List<Movie> movieList;
MovieAdapter movieAdapter;
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Log.i("Fragment", "thread = " + Thread.currentThread().getName());
}
@Nullable
@Override
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
View movieView = inflater.inflate(R.layout.tab_movie, container, false);
return movieView;
}
private void setAdapter() {
movieRecyclerView = (RecyclerView) view.findViewById(R.id.movieRecyclerView);
movieAdapter = new MovieAdapter(view.getContext(), movieList);
RecyclerView.LayoutManager mLayoutManager = new GridLayoutManager(view.getContext(), 2);
movieRecyclerView.setLayoutManager(mLayoutManager);
movieRecyclerView.addItemDecoration(new GridSpacingItemDecoration(2, dpToPx(10), false));
movieRecyclerView.setItemAnimator(new DefaultItemAnimator());
movieRecyclerView.setAdapter(movieAdapter);
}
@Override
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
setAdapter();
if (movieList == null || movieList.size() == 0) {
prepareMovies();
}
movieAdapter.notifyDataSetChanged();
}
public class GridSpacingItemDecoration extends RecyclerView.ItemDecoration {
private int spanCount;
private int spacing;
private boolean includeEdge;
public GridSpacingItemDecoration(int spanCount, int spacing, boolean includeEdge) {
this.spanCount = spanCount;
this.spacing = spacing;
this.includeEdge = includeEdge;
}
@Override
public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {
int position = parent.getChildAdapterPosition(view); // item position
int column = position % spanCount; // item column
if (includeEdge) {
outRect.left = spacing - column * spacing / spanCount; // spacing - column * ((1f / spanCount) * spacing)
outRect.right = (column + 1) * spacing / spanCount; // (column + 1) * ((1f / spanCount) * spacing)
if (position < spanCount) { // top edge
outRect.top = spacing;
}
outRect.bottom = spacing; // item bottom
} else {
outRect.left = column * spacing / spanCount; // column * ((1f / spanCount) * spacing)
outRect.right = spacing - (column + 1) * spacing / spanCount; // spacing - (column + 1) * ((1f / spanCount) * spacing)
if (position >= spanCount) {
outRect.top = spacing; // item top
}
}
}
}
private void prepareMovies() {
movieList = new ArrayList<>();
int[] covers = new int[]{
R.drawable.bossbaby,
R.drawable.abeautifulmind,
R.drawable.despicableme,
R.drawable.harrypotter,
R.drawable.thegodfather,
R.drawable.piratesofcaribbean,
};
movieList.add(new Movie("Boss Baby", 10000, 4.5, 20000, 16000, covers[0]));
movieList.add(new Movie("A Beautiful mind", 35100, 4.9, 40000, 39000, covers[1]));
movieList.add(new Movie("Despicable me", 20000, 4.7, 33000, 31000, covers[2]));
movieList.add(new Movie("Harry Potter", 90000, 5.0, 110000, 105000, covers[3]));
movieList.add(new Movie("The God Father", 70000, 4.9, 90000, 86700, covers[4]));
movieList.add(new Movie("Pirates of caribbean", 50000, 4.6, 70000, 64000, covers[5]));
}
private int dpToPx(int dp) {
Resources r = getResources();
return Math.round(TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dp, r.getDisplayMetrics()));
}
}
Upvotes: 0
Reputation: 1138
When you have many fragments and want to have optimization in its animation and interaction, then you can use viewpager.setOffscreenPageLimit(int numberOfPages)
;
setOffscreenPageLimit()
will have default value 1
i.e viewpager will load only one page(fragment) on its either side and other pages will be recreated from the adapter when scrolled and hence it lags based on operations that page will do. So when you know the number of pages then using this method will help in smoothness of paging animations and interaction.
From Doc setOffscreenPageLimit()
Set the number of pages that should be retained to either side of the current page in the view hierarchy in an idle state. Pages beyond this limit will be recreated from the adapter when needed.
This is offered as an optimization. If you know in advance the number of pages you will need to support or have lazy-loading mechanisms in place on your pages, tweaking this setting can have benefits in perceived smoothness of paging animations and interaction. If you have a small number of pages (3-4) that you can keep active all at once, less time will be spent in layout for newly created view subtrees as the user pages back and forth
Upvotes: 9