Swiping between fragments in TabLayout goes slow
Asked Answered
R

5

5

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

}

Robey answered 21/8, 2018 at 6:33 Comment(3)
Can you post some code?Any
post XML where Viewpager has taken + recyclerview taken.Uhland
I Add some codeRobey
C
9

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

Coed answered 21/8, 2018 at 7:16 Comment(0)
U
1

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.

Uhland answered 21/8, 2018 at 10:35 Comment(2)
This is not principled to inflate and find view in onActivityCreated. They have special method for initializing.Robey
here inflating means inflating other view, layout, can be added at runtimeUhland
M
0

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.

Keypoints

  • Use AsyncTask, WorkerThread to do non-UI operations. Like fetch data, parse data, manipulating data for your need (like iterating a list).
  • Identify long running task on UI thread in your code. Move them to worker thread.
  • Use endless scrolling in List instead of setting up all data at once. (Important in ViewPager Fragment's List)
  • Using huge size of image will also hangs. Make sure images are in appropriate size that you need.

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.

Edit

Now when you have showed your code.

  • It seems like your images size is huge. Check images are having small size. (50-150kb will be enough). You can use Tiny Image to compress image in bulk.
  • Second thing is you don't save your ArrayList locally for further use. You can check the 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()));
    }
}
Molech answered 21/8, 2018 at 6:46 Comment(6)
I haven't applied data fetch operation from server yet in this code.Robey
she :)) are the number of these fragments so much?Robey
What is your single image size?Molech
my images size is small. maximum is 100 kbRobey
I apply this change but about scrolling down in sample fragment it still lags.Robey
Also apply setOffscreenPageLimit to load only a few Fragments once.Molech
E
0

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());
Erato answered 21/8, 2018 at 7:43 Comment(0)
C
0

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);
Cholent answered 16/11, 2021 at 10:26 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.