在 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。
关键点
使用AsyncTask、WorkerThread进行非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(仿今日头条顶部导航)