在 TabLayout 中的片段之间滑动变慢

Swiping between fragments in TabLayout goes slow

我有一个 TabLayout 是用 viewpager 设置的。 viewpager 是用适配器设置的。适配器中添加了 11 个片段,每个片段都有一个 recyclerview。当我 运行 时,在片段之间滑动的应用程序变慢了。谁能告诉我这个问题的原因?

我的主要活动:

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

我的适配器:

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

}

示例片段:

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

}

这个问题提了很多次。解决方法是一样的。

您的 Ui 挂起,这仅仅意味着您没有以良好的方式管理 UI/Main 线程和工作线程。您应该明白使用 UI 线程仅用于设置 UI.

例如。你调用一个网络服务。它为您提供了一系列项目。您要在列表中设置。

一个糟糕的方式

通过 Volley/Retrofit 调用网络服务。获取响应,解析它。然后在列表中设置它。在此过程中,您确实设法使用了任何线程。只是 Volly/Retrofit 就完美地工作了。但是在那之后所有的事情都发生在主线程中。

好方法

通过 Volley/Retrofit 调用网络服务。获取响应,在工作线程中解析它。然后在列表中设置它。在这个过程中使用了工作线程来解析。并且只使用主线程来设置 UI.

要点

  • 使用AsyncTask, WorkerThread进行非UI操作。如获取数据、解析数据、根据需要操作数据(如迭代列表)。
  • 在代码中的 UI 线程上识别 long 运行 任务 将它们移动工作线程
  • 在列表中使用无限滚动,而不是一次设置所有数据。 (在 ViewPager Fragment 的列表中很重要
  • 使用大尺寸的图像也会挂起。确保图片大小符合您的需要。

如果你问我为什么不早点面对这个?

因为您在单个 Activity/ 片段中工作,其中 UI 线程在相当长的时间内处理了您的解析。但是您遇到了这个问题,因为现在您有 11 个片段和 11 个列表。

编辑

现在,当您展示您的代码时。

  • 你的图片好像很大。检查图像有小尺寸。 (50-150kb 就足够了)。您可以使用 Tiny Image 来批量压缩图像。
  • 其次,您没有在本地保存您的 ArrayList 以供进一步使用。可以检查RecyclerView是否为空,为空时设置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()));
    }
}

当你的片段较多,想对其动画和交互进行优化时,可以使用viewpager.setOffscreenPageLimit(int numberOfPages)

setOffscreenPageLimit() 将具有默认值 1 即 viewpager 将仅在其任一侧加载一个页面(片段),并且其他页面将在滚动时从适配器重新创建,因此它滞后于该页面将执行的操作。所以当你知道页面数量时,使用这种方法将有助于页面动画和交互的流畅性。

来自医生 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.

这是作为优化提供的。如果你事先知道号码 您需要支持或具有延迟加载机制的页面 放置在您的页面上,调整此设置可以带来好处 分页动画和交互的感知流畅度。如果你有 您可以同时保持活动状态的少量页面 (3-4), 新创建的视图子树的布局将花费更少的时间,因为 用户来回翻页

连同@Khemraj 的回答,请尝试以下操作,因为当我们返回包含 class 的相同 recyclerview 时,可能会出现有关 viewpager 适配器[=14= 的问题]

tabLayoutAdapter = new TabLayoutAdapter(getChildFragmentManager());

而不是

tabLayoutAdapter = new TabLayoutAdapter(getSupportFragmentManager());

尝试以下操作以使导航更顺畅:

1) 在每个片段上将代码从 onViewCreated 移动到 onActivityCreated,因为它是在片段完全出现时调用的方法:

 @Override
    public void onActivityCreated(@Nullable Bundle savedInstanceState) {
        super.onActivityCreated(savedInstanceState);

        // put code of setting data to views etc.
    }

2) 在 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) 在您的片段中使用一些低分辨率图像(如果您从资源文件夹中使用它们)。不要用1024*960之类的大分辨率,尽量压缩后再添加。

4) 避免在片段的布局文件中使用wrap_content。因为计算宽高需要时间,如果你可以提供 match_parent 或 dp 中的任何特定值,它会工作得更快。

尝试这些点可能会对你有所帮助。

我遇到了同样的问题,可以通过将“notifyDataSetChanged”的执行延迟 200-300 毫秒来解决。当从一个片段滑动到下一个片段时,这为 UI 线程提供了足够的时间来完成。虽然,我不确定是否有其他更好的解决方案。

new Handler().postDelayed(new Runnable() {
   public void run() {
      movieAdapter.notifyDataSetChanged();
   }
}, 200);