具有水平 RecycleView 的 ListView 获得锁定
ListView with horizontal RecycleView get lock
我有一个大约有 5-6 行的 ListView,每一行都是一个显示水平片段的 RecycleView。卡片(例如 Netflix、HBO 应用)
注意事项:
- 每个 RecycleView 大约有 30-50 个项目。
- 每个 ViewHolder 都有一个 "big" 图片作为背景(大约 300x260dp,取决于设备屏幕尺寸,宽度约为设备屏幕的 90%)
- 图片是使用Glide加载的(代码如下)
我在ListView Adapter中为RecycleViews创建了一个缓存(SparseArray)来保持每个RecycleView的状态,否则每次它离开屏幕时它都会重新启动到位置0。
通过这样做,只需将每个 RecycleView 滚动到末尾,就会使其中一些冻结。
尝试了几种解决方法:
- 审查了 MAT 并重构了一些代码以避免由于静态上下文造成的泄漏
- MAT 显然因为图像显示了大量内存
- 减小了图像的大小
- 隐藏图片
- 创建 ViewHolder 的缓存(没用)
- Glide 下载图像后,为图像创建内存缓存
- 将 ViewHolders setIsRecyclable 设置为 false
但是,此选项的 none 有效,保证应用程序不会冻结的唯一方法是避免 ListView 缓存(如果移动视图,则丢失您访问的最后一个位置出屏幕)
Glide 代码(我在这里尝试了几个选项:避免 Bitmap,避免内存缓存,将磁盘策略更新为 ALL)
Glide.with(context).load(url)
.asBitmap()
.skipMemoryCache(true)
.thumbnail(0.5f)
.centerCrop()
.error(placeHolderId)
.diskCacheStrategy(DiskCacheStrategy.RESULT)
//.priority(Priority.NORMAL)
.dontAnimate()
.into(new SimpleTarget<Bitmap>() {
@Override
public void onResourceReady(Bitmap bitmap, GlideAnimation anim) {
if (bitmap != null) {
imgView.setImageBitmap(bitmap);
//cache.put(url, bitmap);
} else {
imgView.setBackground(null);
}
}
});
有 advice/suggestions 吗?
也许还有另一种选择可以在没有缓存的情况下保持视图的状态?
ListView 适配器代码:
@Override
public View getView(int position, View convertView, ViewGroup parent) {
case TYPE_VENUE_FILTER: {
View v;
// Create a new view into the list.
LayoutInflater inflater = (LayoutInflater) context
.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
if (inflater != null) {
v = inflater.inflate(R.layout.fragment_venue_filter, parent, false);
}
// We need to get the exact filter to be displayed
int index = position - FIRST_VENUE_FILTER_ROW;
if (index < this.filters.size()) {
VenueFilter filter = this.filters.get(index);
// Recalculate the distance order
filter.sortVenuesForLocation(this.lastLocation);
// Set data into the view.
VenueFilterFragment.loadFragment(this.context, v, filter, this.fm);
}
return v;
}
}
更新 11/04
终于明白了
- 按照建议在 Adapter 上重新使用 converView
- 在 RecycleView 中包含一个 ScrollListener 并在 bean 中存储最后的滚动位置,以便在重新访问时恢复它。
此代码在适配器中,用于填充特定行 (getView)
// We should reset or recover the scroll state
LinearLayoutManager llm = (LinearLayoutManager) filterContent.getLayoutManager();
if (filter.offset > 0) {
if (llm != null) llm.scrollToPosition(filter.offset + 1);
} else {
if (llm != null) llm.scrollToPosition(0);
}
// We need to clear every time. Because we are reusing the view
recycleView.clearOnScrollListeners();
// We need to create a new specific scroll listener
recycleView.addOnScrollListener(new RecyclerView.OnScrollListener() {
@Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
super.onScrolled(recyclerView, dx, dy);
// Keeping the offset for a future load
LinearLayoutManager llm = (LinearLayoutManager) recyclerView.getLayoutManager();
if (llm != null) filter.offset = llm.findFirstVisibleItemPosition();
}
});
这是一个非常广泛和复杂的问题。您的布局很复杂,因此需要一些努力才能使其高效。您可以做些什么来提高性能:
- 所有
RecyclerView
都应尽可能使用视图持有者。如果需要,定义多个视图类型。 parent RecyclerView
可能只有一种视图类型,即带有 child RecyclerView
的项目。 Child RecyclerView
s 可能 也可以通过 RecycledViewPool
共享视图持有者。更多信息在这里:https://developer.android.com/reference/android/support/v7/widget/RecyclerView.RecycledViewPool.html
- 确保所有布局尽可能平坦。您拥有的视图类型越多,这一点就越重要。
- Glide是图片加载的不错选择。确保尽可能使用缓存。下载后缓存图像以减少对网络的影响。下载图像时使用占位符或进度条。重新使用视图时重置每个
ImageView
状态以避免闪烁。
- 取消加载您不再需要加载的项目的图像。这也应该可以帮助您减少网络连接 activity.
- 你也可以恢复滚动位置,这里应该没有问题。不过这是一个单独的主题,您可以参考此主题以了解更多详细信息:
- 并关注 MAT,是的。确保您没有使用太多资源很重要,在这种情况下这很容易。
希望对您有所帮助。祝你好运!
我有一个大约有 5-6 行的 ListView,每一行都是一个显示水平片段的 RecycleView。卡片(例如 Netflix、HBO 应用)
注意事项:
- 每个 RecycleView 大约有 30-50 个项目。
- 每个 ViewHolder 都有一个 "big" 图片作为背景(大约 300x260dp,取决于设备屏幕尺寸,宽度约为设备屏幕的 90%)
- 图片是使用Glide加载的(代码如下)
我在ListView Adapter中为RecycleViews创建了一个缓存(SparseArray)来保持每个RecycleView的状态,否则每次它离开屏幕时它都会重新启动到位置0。
通过这样做,只需将每个 RecycleView 滚动到末尾,就会使其中一些冻结。
尝试了几种解决方法:
- 审查了 MAT 并重构了一些代码以避免由于静态上下文造成的泄漏
- MAT 显然因为图像显示了大量内存
- 减小了图像的大小
- 隐藏图片
- 创建 ViewHolder 的缓存(没用)
- Glide 下载图像后,为图像创建内存缓存
- 将 ViewHolders setIsRecyclable 设置为 false
但是,此选项的 none 有效,保证应用程序不会冻结的唯一方法是避免 ListView 缓存(如果移动视图,则丢失您访问的最后一个位置出屏幕)
Glide 代码(我在这里尝试了几个选项:避免 Bitmap,避免内存缓存,将磁盘策略更新为 ALL)
Glide.with(context).load(url)
.asBitmap()
.skipMemoryCache(true)
.thumbnail(0.5f)
.centerCrop()
.error(placeHolderId)
.diskCacheStrategy(DiskCacheStrategy.RESULT)
//.priority(Priority.NORMAL)
.dontAnimate()
.into(new SimpleTarget<Bitmap>() {
@Override
public void onResourceReady(Bitmap bitmap, GlideAnimation anim) {
if (bitmap != null) {
imgView.setImageBitmap(bitmap);
//cache.put(url, bitmap);
} else {
imgView.setBackground(null);
}
}
});
有 advice/suggestions 吗? 也许还有另一种选择可以在没有缓存的情况下保持视图的状态?
ListView 适配器代码:
@Override
public View getView(int position, View convertView, ViewGroup parent) {
case TYPE_VENUE_FILTER: {
View v;
// Create a new view into the list.
LayoutInflater inflater = (LayoutInflater) context
.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
if (inflater != null) {
v = inflater.inflate(R.layout.fragment_venue_filter, parent, false);
}
// We need to get the exact filter to be displayed
int index = position - FIRST_VENUE_FILTER_ROW;
if (index < this.filters.size()) {
VenueFilter filter = this.filters.get(index);
// Recalculate the distance order
filter.sortVenuesForLocation(this.lastLocation);
// Set data into the view.
VenueFilterFragment.loadFragment(this.context, v, filter, this.fm);
}
return v;
}
}
更新 11/04
终于明白了
- 按照建议在 Adapter 上重新使用 converView
- 在 RecycleView 中包含一个 ScrollListener 并在 bean 中存储最后的滚动位置,以便在重新访问时恢复它。
此代码在适配器中,用于填充特定行 (getView)
// We should reset or recover the scroll state
LinearLayoutManager llm = (LinearLayoutManager) filterContent.getLayoutManager();
if (filter.offset > 0) {
if (llm != null) llm.scrollToPosition(filter.offset + 1);
} else {
if (llm != null) llm.scrollToPosition(0);
}
// We need to clear every time. Because we are reusing the view
recycleView.clearOnScrollListeners();
// We need to create a new specific scroll listener
recycleView.addOnScrollListener(new RecyclerView.OnScrollListener() {
@Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
super.onScrolled(recyclerView, dx, dy);
// Keeping the offset for a future load
LinearLayoutManager llm = (LinearLayoutManager) recyclerView.getLayoutManager();
if (llm != null) filter.offset = llm.findFirstVisibleItemPosition();
}
});
这是一个非常广泛和复杂的问题。您的布局很复杂,因此需要一些努力才能使其高效。您可以做些什么来提高性能:
- 所有
RecyclerView
都应尽可能使用视图持有者。如果需要,定义多个视图类型。 parentRecyclerView
可能只有一种视图类型,即带有 childRecyclerView
的项目。 ChildRecyclerView
s 可能 也可以通过RecycledViewPool
共享视图持有者。更多信息在这里:https://developer.android.com/reference/android/support/v7/widget/RecyclerView.RecycledViewPool.html - 确保所有布局尽可能平坦。您拥有的视图类型越多,这一点就越重要。
- Glide是图片加载的不错选择。确保尽可能使用缓存。下载后缓存图像以减少对网络的影响。下载图像时使用占位符或进度条。重新使用视图时重置每个
ImageView
状态以避免闪烁。 - 取消加载您不再需要加载的项目的图像。这也应该可以帮助您减少网络连接 activity.
- 你也可以恢复滚动位置,这里应该没有问题。不过这是一个单独的主题,您可以参考此主题以了解更多详细信息:
- 并关注 MAT,是的。确保您没有使用太多资源很重要,在这种情况下这很容易。
希望对您有所帮助。祝你好运!