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

Posted

技术标签:

【中文标题】在 TabLayout 中的片段之间滑动变慢【英文标题】:Swiping between fragments in TabLayout goes slow 【发布时间】:2019-01-27 07:11:26 【问题描述】:

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

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

我的适配器:

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

【问题讨论】:

你能贴一些代码吗? 在 Viewpager 采用 + recyclerview 的位置发布 XML。 我添加了一些代码 【参考方案1】:

如果你有很多片段,想对其动画和交互进行优化,那么你可以使用viewpager.setOffscreenPageLimit(int numberOfPages)

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

来自文档setOffscreenPageLimit()

设置在空闲状态下视图层次结构中当前页面任一侧应保留的页面数。超出页面 此限制将在需要时从适配器重新创建。

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

【讨论】:

【参考方案2】:

尝试以下方法使导航更流畅:

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

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

        // put code of setting data to views etc.
    

2) 在FragmentStateAdapter 类中实现以下方法:

    @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。因为那时计算宽度高度需要时间,如果您可以在dp中提供match_parent或任何特定值,它会工作得更快。

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

【讨论】:

这不是在onActivityCreated中充气找视图的原则。他们有特殊的初始化方法。 这里inflating的意思是inflating其他视图、布局,可以在运行时添加【参考方案3】:

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

您的 Ui 挂起,这仅仅意味着您没有很好地管理 UI/主线程和工作线程。你应该明白,使用 UI 线程只是为了设置 UI。

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

不好的方式

通过 Volley/Retrofit 调用 web 服务。得到响应,解析它。然后将其设置在列表中。在此过程中,您确实管理使用任何线程。只是 Volly/Retrofit 完美地完成了它的工作。但在那之后所有的事情都发生在主线程中。

好办法

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

关键点

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

如果你问我为什么不早些面对这种情况?

因为您在 Single Activity/Fragment 中工作,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()));
    

【讨论】:

我还没有在这段代码中应用从服务器获取数据的操作。 she :)) 这些碎片的数量有这么多吗? 你的单张图片尺寸是多少? 我的图片尺寸很小。最大为 100 kb 我应用了此更改,但在示例片段中向下滚动它仍然滞后。【参考方案4】:

除了@Khemraj 的回答外,还请尝试以下操作,因为当我们返回相同的 recyclerview 包含类时,可能会出现关于 viewpager 适配器的问题

tabLayoutAdapter = new TabLayoutAdapter(getChildFragmentManager());

而不是

tabLayoutAdapter = new TabLayoutAdapter(getSupportFragmentManager());

【讨论】:

【参考方案5】:

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

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

【讨论】:

以上是关于在 TabLayout 中的片段之间滑动变慢的主要内容,如果未能解决你的问题,请参考以下文章

getPageTitle() 没有被片段内的 Tablayout 调用

在tablayout viewpager中运行调整选项卡片段

tab头部滑动切换,TabLayout和ViewPager实现

自适应 Tab 宽度可以滑动文字逐渐变色的 TabLayout(仿今日头条顶部导航)

包含recyclerview的TabLayout在另一个标签上滑动时丢失了信息

Android:使用Tab检测单个片段viewpager