赞
踩
当我们用RecyclerView来展示服务器返回的大量数据时,通常我们都需要实现分页的效果。以前我们都是通过监听RecyclerView的滚动事件,当RecyclerView滑动到底部的时候再次请求网络,把数据展示到RecyclerView上。现在Google提供了一个分页库来帮助开发者更轻松的实现在RecyclerView中逐步而且优雅地加载数据
本文我将以Google官方提供的PagingWithNetworkSample为例,手把手教你使用Android分页库。官方Demo地址
首先我们来简单看一下Paging库的工作示意图,主要是分为如下几个步骤
接下来我将使用分页库来加载https://www.reddit.com(需要翻墙)提供的API数据
首先,创建一个Android项目,同时勾选Kotlin支持。本项目使用Kotlin编写
然后添加所需要的依赖项
//网络库
implementation "com.squareup.retrofit2:retrofit:2.3.0"
implementation "com.squareup.retrofit2:converter-gson:2.3.0"
implementation "com.squareup.okhttp3:logging-interceptor:3.9.0"
implementation 'androidx.recyclerview:recyclerview:1.0.0'
//Android Lifecycle架构
implementation 'androidx.lifecycle:lifecycle-runtime:2.0.0'
implementation "androidx.lifecycle:lifecycle-extensions:2.0.0"
//Android paging架构
implementation 'androidx.paging:paging-runtime:2.0.0'
我们使用Retrofit来请求网络数据,我们来看下Api定义和实体类定义
API接口
//RedditApi.kt interface RedditApi { @GET("/r/{subreddit}/hot.json") fun getTop( @Path("subreddit") subreddit: String, @Query("limit") limit: Int): Call<ListingResponse> //获取下一页数据,key为after @GET("/r/{subreddit}/hot.json") fun getTopAfter( @Path("subreddit") subreddit: String, @Query("after") after: String, @Query("limit") limit: Int): Call<ListingResponse> class ListingResponse(val data: ListingData) class ListingData( val children: List<RedditChildrenResponse>, val after: String?, val before: String? ) data class RedditChildrenResponse(val data: RedditPost) companion object { private const val BASE_URL = "https://www.reddit.com/" fun create(): RedditApi = create(HttpUrl.parse(BASE_URL)!!) fun create(httpUrl: HttpUrl): RedditApi { val logger = HttpLoggingInterceptor(HttpLoggingInterceptor.Logger { Log.d("API", it) }) logger.level = HttpLoggingInterceptor.Level.BASIC val client = OkHttpClient.Builder() .addInterceptor(logger) .build() return Retrofit.Builder() .baseUrl(httpUrl) .client(client) .addConverterFactory(GsonConverterFactory.create()) .build() .create(RedditApi::class.java) } } }
实体类
RedditPost.kt data class RedditPost( @PrimaryKey @SerializedName("name") val name:String, @SerializedName("title") val title:String, @SerializedName("score") val score:Int, @SerializedName("author") val author:String, @SerializedName("subreddit") @ColumnInfo(collate = ColumnInfo.NOCASE) val subreddit:String, @SerializedName("num_comments") val num_comments: Int, @SerializedName("created_utc") val created: Long, val thumbnail: String?, val url: String? ){ var indexInResponse:Int = -1 }
现在获取网络数据的能力我们已经有了,这和我们之前自己实现分页功能没有什么两样。使用Paging库,第一步我们需要一个DataSource。现在我们需要利用DataSource通过Api去获取数据。DataSource有三个实现类ItemKeyedDataSource、PageKeyedDataSource、PositionalDataSource
本例我们将扩展PageKeyedDataSource来加载数据
public abstract void loadInitial(@NonNull LoadInitialParams<Key> params,
@NonNull LoadInitialCallback<Key, Value> callback);
public abstract void loadBefore(@NonNull LoadParams<Key> params,
@NonNull LoadCallback<Key, Value> callback);
public abstract void loadAfter(@NonNull LoadParams<Key> params,
@NonNull LoadCallback<Key, Value> callback);
PageKeyedDataSource中有三个抽象方法。
class PageKeyedSubredditDataSource( private val redditApi: RedditApi, private val subredditName: String, private val retryExecutor: Executor ) : PageKeyedDataSource<String,RedditPost>(){ override fun loadInitial(params: LoadInitialParams<String>, callback: LoadInitialCallback<String, RedditPost>) { val request = redditApi.getTop( subreddit = subredditName, limit = params.requestedLoadSize ) val response = request.execute() val data = response.body()?.data val items = data?.children?.map { it.data } ?: emptyList() callback.onResult(items, data?.before, data?.after) } override fun loadAfter(params: LoadParams<String>, callback: LoadCallback<String, RedditPost>) { redditApi.getTopAfter(subreddit = subredditName, after = params.key, limit = params.requestedLoadSize).enqueue( object : retrofit2.Callback<RedditApi.ListingResponse> { override fun onFailure(call: Call<RedditApi.ListingResponse>, t: Throwable) { } override fun onResponse( call: Call<RedditApi.ListingResponse>, response: Response<RedditApi.ListingResponse>) { if (response.isSuccessful) { val data = response.body()?.data val items = data?.children?.map { it.data } ?: emptyList() callback.onResult(items, data?.after) } else { // retry = { // loadAfter(params, callback) // } // networkState.postValue( // NetworkState.error("error code: ${response.code()}")) } } } ) } override fun loadBefore(params: LoadParams<String>, callback: LoadCallback<String, RedditPost>) { TODO("not implemented") //To change body of created functions use File | Settings | File Templates. } }
使用LivePagedListBuilder可以生成LiveData<PageList>对象。有了LiveData当获取到了数据我们就可以通知PageListAdapter去更新RecyclerView了
class InMemoryByPageKeyRepository(private val redditApi: RedditApi, private val networkExecutor: Executor) : RedditPostRepository { @MainThread override fun postOfSubreddit(subReddit: String, pageSize: Int): Listing<RedditPost> { val sourceFactory = RedditDataSourceFactory(redditApi, subReddit, networkExecutor) val livePagedList = LivePagedListBuilder(sourceFactory, pageSize) // provide custom executor for network requests, otherwise it will default to // Arch Components' IO pool which is also used for disk access .setFetchExecutor(networkExecutor) .build() return Listing( pagedList = livePagedList ) } }
PagingActivity.kt private fun getViewModel(): RedditViewModel { return ViewModelProviders.of(this, object : ViewModelProvider.Factory { override fun <T : ViewModel?> create(modelClass: Class<T>): T { val repository = InMemoryByPageKeyRepository(api, Executors.newFixedThreadPool(5)) @Suppress("UNCHECKED_CAST") return RedditViewModel(repository) as T } })[RedditViewModel::class.java] } private val api by lazy { RedditApi.create() } private fun initAdapter() { val adapter = PostsAdapter() list.adapter = adapter list.layoutManager = LinearLayoutManager(this) //Live<PageList<RedditPost>> 增加监听 model.posts.observe(this, Observer<PagedList<RedditPost>> { adapter.submitList(it) }) }
class PostsAdapter :PagedListAdapter<RedditPost,RecyclerView.ViewHolder>(object : DiffUtil.ItemCallback<RedditPost>() {
override fun areContentsTheSame(oldItem: RedditPost, newItem: RedditPost): Boolean =
oldItem == newItem
override fun areItemsTheSame(oldItem: RedditPost, newItem: RedditPost): Boolean =
oldItem.name == newItem.name}){
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
return RedditPostViewHolder.create(parent)
}
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
if(holder is RedditPostViewHolder) holder.bind(getItem(position))
}
}
这与我们平时创建没有什么两样 略过不表。
至此我们就已经完整地将Paging库的关键技术点都已经介绍了。实践出真知。请clone项目并运行
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。