gpt4 book ai didi

Android Paging 3 - 滚动和加载新页面时出现闪烁、故障或位置跳跃

转载 作者:行者123 更新时间:2023-12-04 23:37:13 29 4
gpt4 key购买 nike

大家好,我正在使用 Android Jetpack Paging 库 3,我正在创建一个实现网络 + 数据库场景的新闻应用程序,并且我正在关注 google https://codelabs.developers.google.com/codelabs/android-paging 的代码实验室,我做的几乎就像在代码实验室中一样,我几乎匹配了示例中显示的所有操作https://github.com/android/architecture-components-samples/tree/main/PagingWithNetworkSample .
它几乎可以正常工作...但是我的后端响应是页面键控的,我的意思是响应带有新闻列表和下一页 url,远程调解器获取数据,填充数据库,设置存储库,设置 View 模型。 ..
问题是 :
当 recyclerview 加载数据时,会发生以下情况:recyclerview 闪烁,项目跳转,被删除,再次添加等等。
我不知道为什么 recyclerview 或其 itemanimator 会这样,看起来如此丑陋和故障。
更重要的是,当我滚动到列表的末尾时,会获取新项目,并且再次出现故障和跳跃效果。

如果您能帮助我,我将不胜感激,我坐了三天,非常感谢您。这是我的代码 fragment :

@Entity(tableName = "blogs")
data class Blog(
@PrimaryKey(autoGenerate = true)
val databaseid:Int,

@field:SerializedName("id")
val id: Int,
@field:SerializedName("title")
val title: String,

@field:SerializedName("image")
val image: String,

@field:SerializedName("date")
val date: String,

@field:SerializedName("share_link")
val shareLink: String,

@field:SerializedName("status")

val status: Int,

@field:SerializedName("url")
val url: String
) {
var categoryId: Int? = null
var tagId: Int? = null
}
这是DAO
@Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun insertAll(blogs: List<Blog>)

@Query("DELETE FROM blogs")
suspend fun deleteAllBlogs()

@Query("SELECT * FROM blogs WHERE categoryId= :categoryId ORDER BY id DESC")
fun getBlogsSourceUniversal(categoryId:Int?): PagingSource<Int, Blog>

@Query("SELECT * FROM blogs WHERE categoryId= :categoryId AND tagId= :tagId ORDER BY id DESC")
fun getBlogsSourceUniversalWithTags(categoryId:Int?,tagId:Int?): PagingSource<Int, Blog>
新闻数据库Kt
abstract class NewsDatabaseKt : RoomDatabase() {

abstract fun articleDAOKt(): ArticleDAOKt
abstract fun remoteKeyDao(): RemoteKeyDao

companion object {


@Volatile
private var INSTANCE: NewsDatabaseKt? = null


fun getDatabase(context: Context): NewsDatabaseKt =
INSTANCE ?: synchronized(this) {
INSTANCE ?: buildDatabase(context).also { INSTANCE = it }
}


private fun buildDatabase(context: Context) =
Room.databaseBuilder(context.applicationContext,
NewsDatabaseKt::class.java,
"news_database_kt")
.build()
}
远程调解员
    @ExperimentalPagingApi
class BlogsRemoteMediator(private val categoryId: Int,
private val service: NewsAPIInterfaceKt,
private val newsDatabase: NewsDatabaseKt,
private val tagId : Int? = null ,
private val initialPage:Int = 1
) : RemoteMediator<Int, Blog>() {

override suspend fun initialize(): InitializeAction {

return InitializeAction.LAUNCH_INITIAL_REFRESH
}

override suspend fun load(loadType: LoadType, state: PagingState<Int, Blog>): MediatorResult {
try {
val page = when (loadType) {
REFRESH ->{
initialPage

}
PREPEND -> {
return MediatorResult.Success(endOfPaginationReached = true)}
APPEND -> {

val remoteKey = newsDatabase.withTransaction {
newsDatabase.remoteKeyDao().remoteKeyByLatest(categoryId.toString())
}
if(remoteKey.nextPageKey == null){
return MediatorResult.Success(endOfPaginationReached = true)
}
remoteKey.nextPageKey.toInt()
}


}


val apiResponse =
if(tagId == null) {
service.getCategoryResponsePage(RU, categoryId, page.toString())
}else{
service.getCategoryTagResponsePage(RU,categoryId,tagId,page.toString())
}
val blogs = apiResponse.blogs
val endOfPaginationReached = blogs.size < state.config.pageSize

newsDatabase.withTransaction {
// clear all tables in the database
if (loadType == LoadType.REFRESH) {

newsDatabase.remoteKeyDao().deleteByLatest(categoryId.toString())
if(tagId == null) {
newsDatabase.articleDAOKt().clearBlogsByCatId(categoryId)
}else {
newsDatabase.articleDAOKt().clearBlogsByCatId(categoryId,tagId)
}
}

blogs.map {blog ->
blog.categoryId = categoryId
if(tagId != null) {
blog.tagId = tagId
}
}
newsDatabase.remoteKeyDao().insert(LatestRemoteKey(categoryId.toString(),
apiResponse.nextPageParam))
newsDatabase.articleDAOKt().insertAll(blogs)

}

return MediatorResult.Success(
endOfPaginationReached = endOfPaginationReached
)
} catch (exception: IOException) {
return MediatorResult.Error(exception)
} catch (exception: HttpException) {
return MediatorResult.Error(exception)
}

}
分页存储库
 class PagingRepository(
private val service: NewsAPIInterfaceKt,
private val databaseKt: NewsDatabaseKt
){
@ExperimentalPagingApi
fun getBlogsResultStreamUniversal(int: Int, tagId : Int? = null) : Flow<PagingData<Blog>>{
val pagingSourceFactory = {
if(tagId == null) {
databaseKt.articleDAOKt().getBlogsSourceUniversal(int)

}else databaseKt.articleDAOKt().getBlogsSourceUniversalWithTags(int,tagId)
}
return Pager(
config = PagingConfig(
pageSize = 1
)
,remoteMediator =
BlogsRemoteMediator(int, service, databaseKt,tagId)
,pagingSourceFactory = pagingSourceFactory
).flow
}
}
博客查看模型
class BlogsViewModel(private val repository: PagingRepository):ViewModel(){

private var currentResultUiModel: Flow<PagingData<UiModel.BlogModel>>? = null
private var categoryId:Int?=null

@ExperimentalPagingApi
fun getBlogsUniversalWithUiModel(int: Int, tagId : Int? = null):
Flow<PagingData<UiModel.BlogModel>> {

val lastResult = currentResultUiModel


if(lastResult != null && int == categoryId){
return lastResult
}

val newResult: Flow<PagingData<UiModel.BlogModel>> =
repository.getBlogsResultStreamUniversal(int, tagId)
.map { pagingData -> pagingData.map { UiModel.BlogModel(it)}}
.cachedIn(viewModelScope)

currentResultUiModel = newResult
categoryId = int
return newResult
}

sealed class UiModel{
data class BlogModel(val blog: Blog) : UiModel()
}
政治 fragment Kotlin
      @ExperimentalPagingApi
class PoliticsFragmentKotlin : Fragment() {

private lateinit var recyclerView: RecyclerView
private lateinit var pagedBlogsAdapter:BlogsAdapter

lateinit var viewModelKt: BlogsViewModel
lateinit var viewModel:NewsViewModel

private var searchJob: Job? = null

@ExperimentalPagingApi
private fun loadData(categoryId:Int, tagId : Int? = null) {

searchJob?.cancel()
searchJob = lifecycleScope.launch {


viewModelKt.getBlogsUniversalWithUiModel(categoryId, tagId).collectLatest {
pagedBlogsAdapter.submitData(it)

}
}
}

@ExperimentalPagingApi
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?): View? {
val view = inflater.inflate(R.layout.fragment_blogs, container, false)
viewModelKt = ViewModelProvider(requireActivity(),Injection.provideViewModelFactory(requireContext())).get(BlogsViewModel::class.java)

viewModel = ViewModelProvider(requireActivity()).get(NewsViewModel::class.java)
pagedBlogsAdapter = BlogsAdapter(context,viewModel)
val decoration = DividerItemDecoration(context, DividerItemDecoration.VERTICAL)
recyclerView = view.findViewById(R.id.politics_recyclerView)
recyclerView.addItemDecoration(decoration)

initAdapter()
loadData(categoryId)
initLoad()
return view
}

private fun initLoad() {
lifecycleScope.launchWhenCreated {
Log.d("meylis", "lqunched loadstate scope")
pagedBlogsAdapter.loadStateFlow
// Only emit when REFRESH LoadState for RemoteMediator changes.
.distinctUntilChangedBy { it.refresh }
// Only react to cases where Remote REFRESH completes i.e., NotLoading.
.filter { it.refresh is LoadState.NotLoading }
.collect { recyclerView.scrollToPosition(0) }
}
}

private fun initAdapter() {
recyclerView.adapter = pagedBlogsAdapter.withLoadStateHeaderAndFooter(
header = BlogsLoadStateAdapter { pagedBlogsAdapter.retry() },
footer = BlogsLoadStateAdapter { pagedBlogsAdapter.retry() }
)

lifecycleScope.launchWhenCreated {
pagedBlogsAdapter.loadStateFlow.collectLatest {
swipeRefreshLayout.isRefreshing = it.refresh is LoadState.Loading
}
}

pagedBlogsAdapter.addLoadStateListener { loadState ->
// Only show the list if refresh succeeds.
recyclerView.isVisible = loadState.source.refresh is LoadState.NotLoading
// Show loading spinner during initial load or refresh.
progressBar.isVisible = loadState.source.refresh is LoadState.Loading
// Show the retry state if initial load or refresh fails.
retryButton.isVisible = loadState.source.refresh is LoadState.Error

// Toast on any error, regardless of whether it came from RemoteMediator or PagingSource
val errorState = loadState.source.append as? LoadState.Error
?: loadState.source.prepend as? LoadState.Error
?: loadState.append as? LoadState.Error
?: loadState.prepend as? LoadState.Error
errorState?.let {
Toast.makeText(context, "\uD83D\uDE28 Wooops ${it.error}", Toast.LENGTH_LONG
).show()
}
}
}


companion object {

@JvmStatic
fun newInstance(categoryId: Int, tags : ArrayList<Tag>): PoliticsFragmentKotlin {
val args = Bundle()
args.putInt(URL, categoryId)
args.putSerializable(TAGS,tags)
val fragmentKotlin = PoliticsFragmentKotlin()
fragmentKotlin.arguments = args
Log.d("meylis", "created instance")
return fragmentKotlin
}
}
博客适配器
class BlogsAdapter(var context: Context?, var newsViewModel:NewsViewModel) : 
PagingDataAdapter<BlogsViewModel.UiModel.BlogModel, RecyclerView.ViewHolder>
(REPO_COMPARATOR) {

private val VIEW = 10

override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
return when (viewType) {
VIEW -> MyViewHolder(LayoutInflater.from(parent.context).inflate(R.layout.card_layout, parent, false))
}
}

override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {

val uiModel = getItem(position)

if(uiModel == null){
if(uiModel is BlogsViewModel.UiModel.BlogModel){(holder as MyViewHolder).bind(null)}
}

if(uiModel is BlogsViewModel.UiModel.BlogModel){(holder as
MyViewHolder).bind(uiModel.blog)}


}

override fun getItemViewType(position: Int): Int {
return VIEW
}


companion object {
private val REPO_COMPARATOR = object : DiffUtil.ItemCallback<BlogsViewModel.UiModel.BlogModel>() {
override fun areItemsTheSame(oldItem: BlogsViewModel.UiModel.BlogModel, newItem: BlogsViewModel.UiModel.BlogModel): Boolean =
oldItem.blog.title == newItem.blog.title
override fun areContentsTheSame(oldItem: BlogsViewModel.UiModel.BlogModel, newItem: BlogsViewModel.UiModel.BlogModel): Boolean =
oldItem == newItem
}

}
MyViewHolder
class MyViewHolder(var container: View) : RecyclerView.ViewHolder(container) {
var cv: CardView
@JvmField
var mArticle: TextView
var date: TextView? = null
@JvmField
var time: TextView
@JvmField
var articleImg: ImageView
@JvmField
var shareView: View
var button: MaterialButton? = null
@JvmField
var checkBox: CheckBox

var progressBar: ProgressBar

private var blog:Blog? = null

init {
cv = container.findViewById<View>(R.id.cardvmain) as CardView
mArticle = container.findViewById<View>(R.id.article) as TextView
articleImg = container.findViewById<View>(R.id.imgvmain) as ImageView
//button = (MaterialButton) itemView.findViewById(R.id.sharemain);
checkBox = container.findViewById<View>(R.id.checkboxmain) as CheckBox
time = container.findViewById(R.id.card_time)
shareView = container.findViewById(R.id.shareView)
progressBar = container.findViewById(R.id.blog_progress)
}

fun bind(blog: Blog?){
if(blog == null){
mArticle.text = "loading"
time.text = "loading"
articleImg.visibility = View.GONE
}else {
this.blog = blog
mArticle.text = blog.title
time.text = blog.date

if (blog.image.startsWith("http")) {
articleImg.visibility = View.VISIBLE
val options: RequestOptions = RequestOptions()
.centerCrop()
.priority(Priority.HIGH)

GlideImageLoader(articleImg,
progressBar).load(blog.image, options)
} else {
articleImg.visibility = View.GONE
}
}

}
}
NewsApi接口(interface)
interface NewsAPIInterfaceKt {

@GET("sort?")
suspend fun getCategoryResponsePage(@Header("Language") language: String, @Query("category")
categoryId: Int, @Query("page") pageNumber: String): BlogsResponse

@GET("sort?")
suspend fun getCategoryTagResponsePage(@Header("Language") language: String,
@Query("category") categoryId: Int,@Query("tag") tagId:Int, @Query("page") pageNumber: String)
:BlogsResponse

companion object {

fun create(): NewsAPIInterfaceKt {
val logger = HttpLoggingInterceptor()
logger.level = HttpLoggingInterceptor.Level.BASIC


val okHttpClient = UnsafeOkHttpClient.getUnsafeOkHttpClient()

return Retrofit.Builder()
.baseUrl(BASE_URL)
.client(okHttpClient)
.addConverterFactory(GsonConverterFactory.create())
.build()
.create(NewsAPIInterfaceKt::class.java)
}
}
}
我试过设置 initialLoadSize = 1
但问题依然存在
编辑:感谢您的回答@dlam,是的,确实如此,我的网络 API 返回按 id 排序的结果列表。顺便说一句,当应用程序脱机运行时,项目也会跳转。
在线刷新和加载视频
online loading and paging
online loading and paging(2)
离线刷新和加载视频
offline loading and refreshing
再次感谢,这是我的要点链接 https://gist.github.com/Aydogdyshka/7ca3eb654adb91477a42128de2f06ea9
编辑
非常感谢@dlam,当我设置 pageSize=10 时,跳转消失了……然后我想起了为什么我首先设置 pageSize=1……当我刷新时,加载了 3 x pageSize 的项目,即使我覆盖了 initialLoadSize = 10 ,刷新后它仍然加载 3 x pageSize 调用 append 2x ,我可能做错了什么,当我刷新时只加载第一页的正确方法是什么?

最佳答案

只是从评论中跟进:
设置pageSize = 10解决了这个问题。
问题在于 pageSize太小,导致 PagingSource刷新未覆盖视口(viewport)的加载页面。由于源刷新替换列表并经过DiffUtil ,您需要提供 initialLoadSize足够大,以至于有一些重叠(否则滚动位置会丢失)。
顺便说一句 - 分页会根据 PagingConfig.prefetchDistance 自动加载附加数据.如果 RecyclerView将项目绑定(bind)到足够靠近列表的边缘,它将自动触发 APPEND/PREPEND 加载。这就是默认 initialLoadSize 的原因。是 3 * pageSize ,但是如果您仍然遇到额外的负载,我建议您调整 prefetchDistance 或增加 initialLoadSize更远。

关于Android Paging 3 - 滚动和加载新页面时出现闪烁、故障或位置跳跃,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/66627912/

29 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com