关于
RecyclerView 集成库使你在你的应用中能够使用 RecyclerViewPreloader ,它可以在用户滑动 RecyclerView 时自动加载稍微超前一些的图片。
配合使用正确的图片尺寸和高效率的磁盘缓存策略,这个库可以显著减少用户滑动图片列表时看到的加载指示器的数量。
Gradle 依赖
要使用 RecyclerView 集成库,在你的 build.gradle 文件中添加一个依赖:
compile (“com.github.bumptech.glide:recyclerview-integration:4.3.0”) {
// Excludes the support library because it’s already included by Glide.
transitive = false
}
当然你还需要确保你的应用中有 RecyclerView 的依赖并正在你的应用中使用 RecyclerView :) 。
设置
为了使用 RecyclerView 集成库,你需要做以下步骤:
- 创建一个 PreloadSizeProvider
- 创建一个 PreloadModelProvider
- 创建一个 RecyclerViewPreloader 并将你前两步创建的 PreloadSizeProvider 和 PreloadModelProvider 赋值进去
- 将你的 RecyclerViewPreloader 添加到你的 RecyclerView 做为一个 scroll listener。
上面的每一个步骤在下面会详细讲解。
PreloadSizeProvider
在添加完 gradle 依赖后,你接下来需要创建一个 PreloadSizeProvider。PreloadSizeProvider 负责保证你的 RecyclerViewPreloader 使用与你的适配器中 onBindViewHolder 方法一样的尺寸来加载图片。
如果你的 RecyclerView 里有统一的 View 尺寸、你使用 into(ImageView)来加载图片并且你没有使用 override() 方法来设置一个不同的尺寸,那么你可以使用 ViewPreloadSizeProvider。
如果你使用 override() 方法或其他情况导致加载的图片尺寸并不完全匹配你的 View 尺寸,你可以使用 FixedPreloadSizeProvider。
如果在你的 RecyclerView 中决定给定位置下图片尺寸的逻辑并不适合上述两种场景,你还可以编写你自己的 [PreloadSizeProvider]的实现。
如果你使用固定尺寸加载你的图片,通常 FixedPreloadSizeProvider 是最简单的:
private final imageWidthPixels = 1024;
private final imageHeightPixels = 768;
...
PreloadSizeProvider sizeProvider =
new FixedPreloadSizeProvider(imageWidthPixels, imageHeightPixels);
PreloadModelProvider
下一步是实现你的 PreloadModelProvider。PreloadModelProvider主要做两个事情,第一个是收集并返回一个给定位置的 Model(即你传给 Glide 的 load(Object) 方法的对象,例如 URL 或 文件路径)列表。第二是取出一个 Model 并生产一个 RequestBuilder,用于预加载给定的 Model 到内存中。
例如,假设我们有一个 RecyclerView,它包含一个图片的 url 列表,RecyclerView的每个位置展示一个 URL 。然后,假设你在你的 RecyclerView.Adapter 的 onBindViewHolder 方法中这样加载图片:
private List<String> myUrls = ...;
...
@Override
public void onBindViewHolder(ViewHolder viewHolder, int position) {
ImageView imageView = ((MyViewHolder) viewHolder).imageView;
String currentUrl = myUrls.get(position);
GlideApp.with(fragment)
.load(currentUrl)
.override(imageWidthPixels, imageHeightPixels)
.into(imageView);
}
这样的话,你的 PreloadModelProvider 实现大概看起来像这样:
private List<String> myUrls = ...;
...
private class MyPreloadModelProvider implements PreloadModelProvider {
@Override
@NonNull
List<U> getPreloadItems(int position) {
String url = myUrls.get(position);
if (TextUtils.isEmpty(url)) {
return Collections.emptyList();
}
return Collections.singletonList(url);
}
@Override
@Nullable
RequestBuilder getPreloadRequestBuilder(String url) {
return
GlideApp.with(fragment)
.load(url)
.override(imageWidthPixels, imageHeightPixels);
}
}
有一点十分重要,从 getPreloadRequestBuilder 中返回的 RequestBuilder ,必须与你从 onBindViewHolder 里启动的请求使用完全相同的一组选项 (占位符, 变换等) 和完全相同的尺寸。如果对于一个给定位置,两种方法提供的任何选项不完全相同,你的预加载请求将被浪费,因为它加载的图片会被缓存,但是缓存键却与你在 onBindViewHolder 里图片的缓存键不同。如果您无法使这些缓存键匹配,请参阅调试页面。
如果对于一个给定的位置你不需要加载任何东西,你可以从 getPreloadItems 返回一个空列表。如果你稍晚时候发现你无法从一个给定的 Model 创建一个 RequestBuilder,你可以从 getPreloadRequestBuilder 方法返回 null 。
RecyclerViewPreloader
当你创建好你的 PreloadSizeProvider 和 PreloadModelProvider,你就已经准备好创建 RecyclerViewPreloader 了:
private final imageWidthPixels = 1024;
private final imageHeightPixels = 768;
private List<String> myUrls = ...;
...
PreloadSizeProvider sizeProvider =
new FixedPreloadSizeProvider(imageWidthPixels, imageHeightPixels);
PreloadModelProvider modelProvider = new MyPreloadModelProvider();
RecyclerViewPreloader<Photo> preloader =
new RecyclerViewPreloader<>(
Glide.with(this), modelProvider, sizeProvider, 10 /*maxPreload*/);
这里使用 10 作为 maxPreload 仅仅十个占位符,关于如何选择这个数字更详细的讨论,请直接看下一节。
maxPreload
maxPreload 是一个整数,指示你想预加载多少条数据。最优解因你的图片尺寸,质量,RecyclerView 的布局而异,有些时候甚至与你的应用所运行的设备有关。
一个好的起点是,选择一个足够大的数,大到能包含两到三行的所有图片。一旦你选择了你的初始数字,你可以尝试在几个不同的设备上运行你的应用,并做必要的微调来最大化缓存命中率。
一个过大的数字将意味着你预加载得过远,不实用。过小的数字则会阻止你提前加载足够的图片。
RecyclerView
最后一步,当你拥有你的 RecyclerViewPreloader 之后,就可以将它添加为你的 RecyclerView 的一个滑动监听器:
RecyclerView myRecyclerView = (RecyclerView) findViewById(R.id.recycler_view);
myRecyclerView.addOnScrollListener(preloader);
将 RecyclerViewPreloader 添加为一个滑动监听器,将允许 RecyclerViewPreloader 自动提前加载用户滑动方向上的图片,并监听滑动方向和加速度的改变。
警告 - Glide 的默认滑动监听器 RecyclerToListViewScrollListener 假定你在使用一个 LinearLayoutManager 或其子类,如果实际情况并非如此将会发生 crash 。如果你在使用不同的 LayoutManager 类型,你需要实现你自己的 OnScrollListener,并翻译一下 RecyclerView 提供位置的调用,以及 RecyclerViewPreloader 关于这些位置的调用。
总体代码
当你完成了上述步骤之后,你的代码看起来应该类似这样:
public final class ImagesFragment extends Fragment {
// These are totally arbitrary, pick sizes that are right for your UI.
private final imageWidthPixels = 1024;
private final imageHeightPixels = 768;
// You will need to populate these urls somewhere...
private List<String> myUrls = ...;
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View result = inflater.inflate(R.layout.images_fragment, container, false);
PreloadSizeProvider sizeProvider =
new FixedPreloadSizeProvider(imageWidthPixels, imageHeightPixels);
PreloadModelProvider modelProvider = new MyPreloadModelProvider();
RecyclerViewPreloader<Photo> preloader =
new RecyclerViewPreloader<>(
Glide.with(this), modelProvider, sizeProvider, 10 /*maxPreload*/);
RecyclerView myRecyclerView = (RecyclerView) result.findViewById(R.id.recycler_view);
myRecyclerView.addOnScrollListener(preloader);
// Finish setting up your RecyclerView etc.
myRecylerView.setLayoutManager(...);
myRecyclerView.setAdapter(...);
...
return result;
}
private class MyPreloadModelProvider implements PreloadModelProvider {
@Override
@NonNull
public List<U> getPreloadItems(int position) {
String url = myUrls.get(position);
if (TextUtils.isEmpty(url)) {
return Collections.emptyList();
}
return Collections.singletonList(url);
}
@Override
@Nullable
public RequestBuilder getPreloadRequestBuilder(String url) {
return
GlideApp.with(fragment)
.load(url)
.override(imageWidthPixels, imageHeightPixels);
}
}
}