在回收者视图中加载大量项目
Loading large number of items in recycler view
我在片段中有一个回收站视图,基本上我试图在回收站视图中加载歌曲列表。回收站视图的每一行都包含一个 imageview(用于专辑封面)和 textview(歌曲名称)。当数据集的大小很大时,我遇到了麻烦,即当歌曲太多时,回收器视图滞后,应用程序最终给出 ANR.I 我正在使用 Glide 在每一行的图像视图中加载专辑封面。
google 音乐播放器如何能够毫无延迟地播放如此多的歌曲?
编辑:
这是我的 SongsFragment
public class SongsFragment extends Fragment {
static {
AppCompatDelegate.setCompatVectorFromResourcesEnabled(true);
}
ProgressBar progressBar; // progress bar to show after every 30 items
NestedScrollView nestedScrollView; //for smooth scrolling of recyclerview as well as to detect the end of recyclerview
RecyclerView recyclerView;
ArrayList<Song> songMainList = new ArrayList<>(); //partial list in which items are added
ArrayList<Song> songAllList = new ArrayList<>(); //Complete List of songs
SongAdapter songsAdapter;
private LinearLayoutManager layoutManager;
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View rootView = inflater.inflate(R.layout.fragment_songs, container, false);
nestedScrollView = (NestedScrollView) rootView.findViewById(R.id.nestedScrollView);
progressBar = (ProgressBar) rootView.findViewById(R.id.progressBar);
String songJson = getActivity().getIntent().getStringExtra("songList");
songAllList = new Gson().fromJson(songJson, new TypeToken<ArrayList<Song>>() {
}.getType());
//Getting list of all songs in songAllList
if (songAllList.size() > 30) {
songMainList = new ArrayList<>(songAllList.subList(0,30));
} else {
songMainList = songAllList;
}
//if size of fetched songAllList>30 then add only 30 rows to songMainList
recyclerView = (RecyclerView) rootView.findViewById(R.id.songs);
int spanCount = 1; // 2 columns
int spacing = 4; // 50px
recyclerView.addItemDecoration(new GridItemDecoration(spanCount, spacing, true));
recyclerView.setHasFixedSize(true);
recyclerView.setNestedScrollingEnabled(false);
recyclerView.setLayoutManager(new LinearLayoutManager(getActivity()));
songsAdapter = new SongAdapter(getActivity(), songMainList, recyclerView);
nestedScrollView.getViewTreeObserver().addOnScrollChangedListener(new ViewTreeObserver.OnScrollChangedListener() {
@Override
public void onScrollChanged() {
View view = (View) nestedScrollView.getChildAt(nestedScrollView.getChildCount() - 1);
int diff = (view.getBottom() - (nestedScrollView.getHeight() + nestedScrollView
.getScrollY()));
if (diff == 0) { //NestedScrollView scrolled to bottom
progressBar.setVisibility(View.VISIBLE); //show progressbar
new Handler().postDelayed(new Runnable() {
@Override
public void run() {
if (songMainList.size() < songAllList.size()) {
int x = 0, y = 0;
if ((songAllList.size() - songMainList.size()) >= 30) {
x = songMainList.size();
y = x + 30;
} else {
x = songMainList.size();
y = x + songAllList.size() - songMainList.size();
}
for (int i = x; i < y; i++) {
songMainList.add(songAllList.get(i)); //Adding new items from songAllList to songMainList one by one
songsAdapter.notifyDataSetChanged();
}
}
progressBar.setVisibility(View.GONE);
}
}, 1500);
}
}
});
recyclerView.setAdapter(songsAdapter);
return rootView;
}
}
这是我的 RecyclerViewAdapter 和 viewholder
public class SongAdapter extends RecyclerView.Adapter {
private List<Song> songsList;
private Context c;
private RecyclerView.ViewHolder holder;
public SongAdapter(Context context) {
mainActivityContext = context;
}
public SongAdapter(Context context, List<Song> songs, RecyclerView recyclerView) {
songsList = songs;
LinearLayoutManager linearLayoutManager = (LinearLayoutManager) recyclerView.getLayoutManager();
c = context;
}
public SongAdapter getInstance() {
return SongAdapter.this;
}
@Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.song_list_row, parent, false);
return new SongViewHolder(view,c);
}
@Override
public void onBindViewHolder(final RecyclerView.ViewHolder holder, int position) {
if (holder instanceof SongViewHolder) {
Song song = songsList.get(position);
this.holder = holder;
String name = song.getName();
String artist = song.getArtist();
String imagepath = song.getImagepath();
((SongViewHolder) holder).name.setText(name);
((SongViewHolder) holder).artist.setText(artist);
if (!imagepath.equalsIgnoreCase("no_image")) //if the album art has valid imagepath for this song
Glide.with(c).load(imagepath)
.centerCrop()
.into(((SongViewHolder) holder).iv);
else
((SongViewHolder) holder).iv.setImageResource(R.drawable.empty);
((SongViewHolder) holder).song = song;
}
}
@Override
public int getItemCount() {
return songsList.size();
}
static class SongViewHolder extends RecyclerView.ViewHolder{
ImageView iv;
TextView name, artist;
CardView songListCard;
private Context ctx;
private OnLongPressListener mListener;
SongViewHolder(View v, Context context) {
super(v);
this.ctx = context;
iv= (ImageView) v.findViewById(R.id.album_art);
name= (TextView) v.findViewById(R.id.name);
artist= (TextView) v.findViewById(R.id.artist_mini);
songListCard = (CardView) v.findViewById(R.id.song_list_card);
}
}
当只有 150-200 个项目时,recyclerview 工作正常,但当达到 600-700 个项目时,整个应用程序变慢。这可能是因为我在 onBindViewHolder?
中使用 glide 的方式
你们是一次性加载所有数据吗? RecycleView 应该没有问题,我认为如果你有太多数据,处理本身会花费太多时间。您应该分块加载数据并检查用户的滚动状态并加载下一批等。有点像 Instagram 或 Facebook 的做法。
通过删除 recyclerview 上的 NestedScrollView 解决了问题。
nestedscrollview 不允许调用 recyclerview.addOnScrollListener() ,因此我在加载更多项目时出现滞后。
这是我如何为 RecyclerView 实现 loadOnScroll-
recyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
@Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
super.onScrolled(recyclerView, dx, dy);
if (!recyclerView.canScrollVertically(1))
onScrolledToBottom();
}
});
private void onScrolledToBottom() {
if (songMainList.size() < songAllList.size()) {
int x, y;
if ((songAllList.size() - songMainList.size()) >= 50) {
x = songMainList.size();
y = x + 50;
} else {
x = songMainList.size();
y = x + songAllList.size() - songMainList.size();
}
for (int i = x; i < y; i++) {
songMainList.add(songAllList.get(i));
}
songsAdapter.notifyDataSetChanged();
}
}
排序答案:
LinearLayoutManager(context).apply { isAutoMeasureEnabled = false }
// or in Java
layoutManager.setAutoMeasureEnabled(false)
更新 2020.08.14
已弃用 RecyclerView.LayoutManager#setAutoMeasureEnabled
此方法已在 API 级别 27.1.0 中弃用。
LayoutManager 的实现者应该通过覆盖 isAutoMeasureEnabled()
来定义它是否使用 AutoMeasure
从 RecyclerView.LayoutManager#setAutoMeasureEnabled() 的文档我们知道:
This method is usually called by the LayoutManager with value {@code true} if it wants to support WRAP_CONTENT
It works by calling {@link LayoutManager#onLayoutChildren(Recycler, State)} during an {@link RecyclerView#onMeasure(int, int)} call, then calculating desired dimensions based on children's positions.
如果我们设置 mAutoMeasure = true
,它将在 RecyclerView#onMeasure(int, int)
调用期间调用 LayoutManager#onLayoutChildren(Recycler, State)
。每个子视图的onMeasure()
方法都会被调用,这太费时间了。
我们来看LinearLayoutManager的构造函数
public LinearLayoutManager(Context context, int orientation, boolean reverseLayout) {
setOrientation(orientation);
setReverseLayout(reverseLayout);
setAutoMeasureEnabled(true);
}
所以,我们设置mAutoMeasure = false
后,一切就OK了
您可以使用 AndroidX Room & Paging 实现此目的
- 将您的数据从网络缓存到本地房间数据库
- 使用 Paging 从房间数据库加载数据
我在片段中有一个回收站视图,基本上我试图在回收站视图中加载歌曲列表。回收站视图的每一行都包含一个 imageview(用于专辑封面)和 textview(歌曲名称)。当数据集的大小很大时,我遇到了麻烦,即当歌曲太多时,回收器视图滞后,应用程序最终给出 ANR.I 我正在使用 Glide 在每一行的图像视图中加载专辑封面。 google 音乐播放器如何能够毫无延迟地播放如此多的歌曲?
编辑: 这是我的 SongsFragment
public class SongsFragment extends Fragment {
static {
AppCompatDelegate.setCompatVectorFromResourcesEnabled(true);
}
ProgressBar progressBar; // progress bar to show after every 30 items
NestedScrollView nestedScrollView; //for smooth scrolling of recyclerview as well as to detect the end of recyclerview
RecyclerView recyclerView;
ArrayList<Song> songMainList = new ArrayList<>(); //partial list in which items are added
ArrayList<Song> songAllList = new ArrayList<>(); //Complete List of songs
SongAdapter songsAdapter;
private LinearLayoutManager layoutManager;
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View rootView = inflater.inflate(R.layout.fragment_songs, container, false);
nestedScrollView = (NestedScrollView) rootView.findViewById(R.id.nestedScrollView);
progressBar = (ProgressBar) rootView.findViewById(R.id.progressBar);
String songJson = getActivity().getIntent().getStringExtra("songList");
songAllList = new Gson().fromJson(songJson, new TypeToken<ArrayList<Song>>() {
}.getType());
//Getting list of all songs in songAllList
if (songAllList.size() > 30) {
songMainList = new ArrayList<>(songAllList.subList(0,30));
} else {
songMainList = songAllList;
}
//if size of fetched songAllList>30 then add only 30 rows to songMainList
recyclerView = (RecyclerView) rootView.findViewById(R.id.songs);
int spanCount = 1; // 2 columns
int spacing = 4; // 50px
recyclerView.addItemDecoration(new GridItemDecoration(spanCount, spacing, true));
recyclerView.setHasFixedSize(true);
recyclerView.setNestedScrollingEnabled(false);
recyclerView.setLayoutManager(new LinearLayoutManager(getActivity()));
songsAdapter = new SongAdapter(getActivity(), songMainList, recyclerView);
nestedScrollView.getViewTreeObserver().addOnScrollChangedListener(new ViewTreeObserver.OnScrollChangedListener() {
@Override
public void onScrollChanged() {
View view = (View) nestedScrollView.getChildAt(nestedScrollView.getChildCount() - 1);
int diff = (view.getBottom() - (nestedScrollView.getHeight() + nestedScrollView
.getScrollY()));
if (diff == 0) { //NestedScrollView scrolled to bottom
progressBar.setVisibility(View.VISIBLE); //show progressbar
new Handler().postDelayed(new Runnable() {
@Override
public void run() {
if (songMainList.size() < songAllList.size()) {
int x = 0, y = 0;
if ((songAllList.size() - songMainList.size()) >= 30) {
x = songMainList.size();
y = x + 30;
} else {
x = songMainList.size();
y = x + songAllList.size() - songMainList.size();
}
for (int i = x; i < y; i++) {
songMainList.add(songAllList.get(i)); //Adding new items from songAllList to songMainList one by one
songsAdapter.notifyDataSetChanged();
}
}
progressBar.setVisibility(View.GONE);
}
}, 1500);
}
}
});
recyclerView.setAdapter(songsAdapter);
return rootView;
}
}
这是我的 RecyclerViewAdapter 和 viewholder
public class SongAdapter extends RecyclerView.Adapter {
private List<Song> songsList;
private Context c;
private RecyclerView.ViewHolder holder;
public SongAdapter(Context context) {
mainActivityContext = context;
}
public SongAdapter(Context context, List<Song> songs, RecyclerView recyclerView) {
songsList = songs;
LinearLayoutManager linearLayoutManager = (LinearLayoutManager) recyclerView.getLayoutManager();
c = context;
}
public SongAdapter getInstance() {
return SongAdapter.this;
}
@Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.song_list_row, parent, false);
return new SongViewHolder(view,c);
}
@Override
public void onBindViewHolder(final RecyclerView.ViewHolder holder, int position) {
if (holder instanceof SongViewHolder) {
Song song = songsList.get(position);
this.holder = holder;
String name = song.getName();
String artist = song.getArtist();
String imagepath = song.getImagepath();
((SongViewHolder) holder).name.setText(name);
((SongViewHolder) holder).artist.setText(artist);
if (!imagepath.equalsIgnoreCase("no_image")) //if the album art has valid imagepath for this song
Glide.with(c).load(imagepath)
.centerCrop()
.into(((SongViewHolder) holder).iv);
else
((SongViewHolder) holder).iv.setImageResource(R.drawable.empty);
((SongViewHolder) holder).song = song;
}
}
@Override
public int getItemCount() {
return songsList.size();
}
static class SongViewHolder extends RecyclerView.ViewHolder{
ImageView iv;
TextView name, artist;
CardView songListCard;
private Context ctx;
private OnLongPressListener mListener;
SongViewHolder(View v, Context context) {
super(v);
this.ctx = context;
iv= (ImageView) v.findViewById(R.id.album_art);
name= (TextView) v.findViewById(R.id.name);
artist= (TextView) v.findViewById(R.id.artist_mini);
songListCard = (CardView) v.findViewById(R.id.song_list_card);
}
}
当只有 150-200 个项目时,recyclerview 工作正常,但当达到 600-700 个项目时,整个应用程序变慢。这可能是因为我在 onBindViewHolder?
中使用 glide 的方式你们是一次性加载所有数据吗? RecycleView 应该没有问题,我认为如果你有太多数据,处理本身会花费太多时间。您应该分块加载数据并检查用户的滚动状态并加载下一批等。有点像 Instagram 或 Facebook 的做法。
通过删除 recyclerview 上的 NestedScrollView 解决了问题。 nestedscrollview 不允许调用 recyclerview.addOnScrollListener() ,因此我在加载更多项目时出现滞后。 这是我如何为 RecyclerView 实现 loadOnScroll-
recyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
@Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
super.onScrolled(recyclerView, dx, dy);
if (!recyclerView.canScrollVertically(1))
onScrolledToBottom();
}
});
private void onScrolledToBottom() {
if (songMainList.size() < songAllList.size()) {
int x, y;
if ((songAllList.size() - songMainList.size()) >= 50) {
x = songMainList.size();
y = x + 50;
} else {
x = songMainList.size();
y = x + songAllList.size() - songMainList.size();
}
for (int i = x; i < y; i++) {
songMainList.add(songAllList.get(i));
}
songsAdapter.notifyDataSetChanged();
}
}
排序答案:
LinearLayoutManager(context).apply { isAutoMeasureEnabled = false }
// or in Java
layoutManager.setAutoMeasureEnabled(false)
更新 2020.08.14
已弃用 RecyclerView.LayoutManager#setAutoMeasureEnabled
此方法已在 API 级别 27.1.0 中弃用。 LayoutManager 的实现者应该通过覆盖 isAutoMeasureEnabled()
来定义它是否使用 AutoMeasure从 RecyclerView.LayoutManager#setAutoMeasureEnabled() 的文档我们知道:
This method is usually called by the LayoutManager with value {@code true} if it wants to support WRAP_CONTENT
It works by calling {@link LayoutManager#onLayoutChildren(Recycler, State)} during an {@link RecyclerView#onMeasure(int, int)} call, then calculating desired dimensions based on children's positions.
如果我们设置 mAutoMeasure = true
,它将在 RecyclerView#onMeasure(int, int)
调用期间调用 LayoutManager#onLayoutChildren(Recycler, State)
。每个子视图的onMeasure()
方法都会被调用,这太费时间了。
我们来看LinearLayoutManager的构造函数
public LinearLayoutManager(Context context, int orientation, boolean reverseLayout) {
setOrientation(orientation);
setReverseLayout(reverseLayout);
setAutoMeasureEnabled(true);
}
所以,我们设置mAutoMeasure = false
后,一切就OK了
您可以使用 AndroidX Room & Paging 实现此目的
- 将您的数据从网络缓存到本地房间数据库
- 使用 Paging 从房间数据库加载数据