class TestPagingActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val recyclerView = RecyclerView(this)
recyclerView.layoutManager = LinearLayoutManager(this)
val adapter = MyAdapter()
initData(adapter)
recyclerView.adapter =
adapter.withLoadStateFooter(object : LoadStateAdapter<MyViewHolder>() {
override fun displayLoadStateAsItem(loadState: LoadState): Boolean {
// also dispatch LoadState.NotLoading
return true
}
override fun onBindViewHolder(holder: MyViewHolder, loadState: LoadState) {
holder.itemView.setOnClickListener(null)
when (loadState) {
LoadState.Loading -> {
(holder.itemView as TextView).text = "Loading..."
}
is LoadState.Error -> {
(holder.itemView as TextView).text = "Load failed. Try again."
holder.itemView.setOnClickListener {
adapter.retry()
}
}
is LoadState.NotLoading -> {
if (loadState.endOfPaginationReached && adapter.itemCount > 0) {
(holder.itemView as TextView).text = "All Loaded"
}
}
}
}
override fun onCreateViewHolder(
parent: ViewGroup,
loadState: LoadState
): MyViewHolder {
val view = TextView(parent.context)
view.gravity = Gravity.CENTER
view.layoutParams = ViewGroup.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.WRAP_CONTENT
)
return MyViewHolder(view)
}
})
val helpWidget = MyHelpWidget(this).apply { setAdapter(adapter) }
setContentView(FrameLayout(this).apply {
addView(recyclerView)
addView(helpWidget)
})
}
private fun initData(adapter: MyAdapter) {
lifecycleScope.launch {
loadData().collectLatest {
adapter.submitData(it)
}
}
}
private fun loadData(): Flow<PagingData<MyItem>> {
return Pager(
config = PagingConfig(enablePlaceholders = false, pageSize = 5),
pagingSourceFactory = { MyPagingSource() }
).flow
}
class MyHelpWidget(context: Context) : FrameLayout(context) {
private val loading = ProgressBar(context)
private val empty = TextView(context)
private val error = Button(context)
init {
empty.text = "no items."
error.text = "error! try again."
addView(loading)
addView(empty)
addView(error)
hide()
}
fun <T : Any, VH : RecyclerView.ViewHolder> setAdapter(adapter: PagingDataAdapter<T, VH>) {
error.setOnClickListener {
adapter.refresh()
}
adapter.addLoadStateListener {
when (it.refresh) {
is LoadState.Error -> {
if (adapter.itemCount == 0) {
// show error page
error()
} else {
// do not dismiss old items, just show a toast.
hide()
ToastUtils.showShort("refresh failed.")
}
}
is LoadState.NotLoading -> {
if (adapter.itemCount == 0) {
// show empty page
empty()
} else {
// show list page
hide()
}
}
else -> {
// show loading page
loading()
}
}
}
}
fun hide() {
loading.visibility = View.GONE
empty.visibility = View.GONE
error.visibility = View.GONE
}
fun error() {
hide()
error.visibility = View.VISIBLE
}
fun empty() {
hide()
empty.visibility = View.VISIBLE
}
fun loading() {
hide()
loading.visibility = View.VISIBLE
}
}
class MyPagingSource : PagingSource<Int, MyItem>() {
// 如果是最常用的下拉刷新,这里直接返回 null 就可以,表示从头开始加载;
// 否则如果需要从中间开始加载的话,就按如下方法即可。
override fun getRefreshKey(state: PagingState<Int, MyItem>): Int? {
Log.d("xiaobo", "anchorPosition: ${state.anchorPosition}")
return state.anchorPosition?.let { anchorPosition ->
val anchorPage = state.closestPageToPosition(anchorPosition)
anchorPage?.prevKey?.plus(1) ?: anchorPage?.nextKey?.minus(1)
}
}
override suspend fun load(params: LoadParams<Int>): LoadResult<Int, MyItem> {
// main
// Log.d("xiaobo", "thread: ${Thread.currentThread().name}")
return try {
val page = params.key ?: 1
val list = mutableListOf<MyItem>()
val result = LoadResult.Page(
data = list,
prevKey = if (page == 1) null else page - 1,
nextKey = if (page == 5) null else page + 1
)
withContext(Dispatchers.Default) {
delay(800L)
if (Random().nextInt(4) == 0) {
Log.d("xiaobo", "page: $page, loadSize: ${params.loadSize}")
throw RuntimeException("test load failed.")
}
for (i in 0 until 5) {
list.add(MyItem((page - 1) * 5 + i))
delay(200L)
}
}
Log.d("xiaobo", "page: $page, loadSize: ${params.loadSize}, result: $result")
result
} catch (e: Exception) {
LoadResult.Error(e)
}
}
}
data class MyItem(val num: Int)
class MyAdapter : PagingDataAdapter<MyItem, MyViewHolder>(MyDiffCallback()) {
override fun onBindViewHolder(holder: MyViewHolder, position: Int) {
getItem(position)?.let {
(holder.itemView as Button).text = "${it.num} - click to refresh"
}
holder.itemView.setOnClickListener {
// trigger getRefreshKey() be called.
refresh()
}
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MyViewHolder {
val button = Button(parent.context)
button.isAllCaps = false
button.layoutParams = ViewGroup.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.WRAP_CONTENT
)
return MyViewHolder(button)
}
}
class MyViewHolder(itemView: View) : BaseViewHolder(itemView)
class MyDiffCallback : DiffUtil.ItemCallback<MyItem>() {
override fun areItemsTheSame(oldItem: MyItem, newItem: MyItem): Boolean {
return oldItem.num == newItem.num
}
override fun areContentsTheSame(oldItem: MyItem, newItem: MyItem): Boolean {
return oldItem == newItem
}
}
}
网友评论