【问题标题】:Swiping between fragments in TabLayout goes slow在 TabLayout 中的片段之间滑动变慢
【发布时间】: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。
  • 我添加了一些代码

标签: android android-fragments android-viewpager android-tablayout


【解决方案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);
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多