Android分页v3与只是本地房间数据库:我如何滚动到页面列表中的特定位置?



我不确定这是否是分页v3的正确用例,但这里是。

我所有的数据都在本地的Room数据库中(没有网络调用,没有api)。我有一个潜在的大数据集的待办事项列表(每个日期一个)。我有一个RecyclerView,它向用户显示这些待办事项列表。显然,我不想为用户加载所有的待办事项列表,所以我选择使用分页数据源,并决定使用Paging v3。

我按照这个youtube教程来实现分页。

我得到了一个基本用例的一切工作,但现在我卡住了。我想让用户跳转到列表中的特定日期。我还想听听他们是否滚动到一个新的日期,并为他们创建一个新的待办事项列表。

我不知道如何滚动我的RecyclerView或页面源到列表中的特定点。

我的道:

@Transaction
@Query("SELECT * FROM todo_list ORDER BY date DESC")
fun loadTodoListsPaged(): PagingSource<Int, TodoList>

My View Model:

class TodoListViewModel(application: Application) : AndroidViewModel(application) {
val todoListsPaged = Pager(PagingConfig(
pageSize = 10,
enablePlaceholders = false,
maxSize = 30
)) {
myTodoListDao.loadTodoListsPaged()
}.flow.cachedIn(viewModelScope)
}

我的适配器:

class TodoListPagingDataAdapter(
val context: Context
): PagingDataAdapter<TodoList, TodoListViewHolder>(TodoListDiffUtilCallback()) {
...
}

我不确定这是否可能,因为Room only supports PagingSource with Key of type Int.

使用分页v3,我如何跳转或滚动到列表中的特定点?

理想情况下,我只需传递日期,它就会告诉我需要的页面。然后我将页面传递给适配器/recyclerview/pager,它会为我加载它。

我最终将一个可变的日期列表存储为LiveData,并使用Transformation Switch Map将其映射到我的待办事项列表(将日期列表传递给数据库并获得相关列表)。当用户滚动时,我从这个可变列表中添加/删除日期,它会自动更新。它比分页要好得多,也简单得多。如果有人想知道更多的细节,尽管问。

我不知道是否有一种方法可以根据已适应数据中的特定值跳转。如果一个答案被张贴出来说,否则更多的权力给你。

如果你不能找到方法使用PagingSource,我想我可能有一个解决方案。

当用户以编程方式输入日期时,滚动列表直到找到它。

fun scrollTilFindDate(date : Date, offset : Int){
var dateFound : Boolean = false;
var todos: List<Todo> = adapter.snapshot().items;
for(todo : Todo in todos){
if(todo.date == date){
dateFound = true;
break;
}
}
if(!recyclerView.canScrollVertically(1) || dateFound){
return;
}else{
var newOffset : Int = offset+pageSize;
recyclerView.post(()->{
recyclerView.scrollToPosition(newOffset);
scrollTilFindDate(date,newOffset);  
});
}
}

有人询问我的替代解决方案的更多细节。这个解决方案很简单,而且很适合我的需要。我可以通过改变ViewModel的当前日期跳转到任何日期,它会自动加载该日期周围的其他日期(根据分页配置设置)。

我的RecyclerView一次只显示一个项目。我使用SnapOnScrollListeneronSnapPositionReadyToSnap来更新ViewModel的当前日期并触发日期加载。

取决于您如何订购日期(ASC vs DESC),并取决于您的RecyclerView是否将reverseLayout设置为true, loadNextPage/loadPrevPage可能是向后的。所以请记住这一点。

ViewModel

这包含了大部分逻辑&可能会被分开。它做了繁重的工作,以方便按日期加载项目。

// NOTE: taskListRepository is just a wrapper for my Room DAOs. 
class MyViewModel(application: Application) : AndroidViewModel(application) {

val selectedDateLive = MutableLiveData(LocalDate.now())
var selectedDate get() = selectedDateLive.value
set(value) { selectedDateLive.value = value }
// I used Android's paging config as my own config, but I did NOT use paging itself
private val pageConfigForDates = PagingConfig(
pageSize = 10, // how many to load in each page
prefetchDistance = 3, // how far from the end before we should load more; defaults to page size
initialLoadSize = 10, // how many items should we initially load; defaults to 3x page size
maxSize = 16 // how many items do we keep in memory; defaults to unlimited (must be > pageSize + 2*prefetchDistance)
)
private var datesLoaded: List<LocalDate> = emptyList()
private val datesToLoadLive = Transformations.switchMap(selectedDateLive) { currentDate ->
val indexOfCurrent = datesLoaded.indexOf(currentDate)
// initial dates
if (datesLoaded.isEmpty() || indexOfCurrent == -1) {
val initialDates = getDatesAround(currentDate, pageConfigForDates.initialLoadSize)
datesLoaded = initialDates
ensureListsExistForDates(initialDates)
return@switchMap MutableLiveData(datesLoaded)
}
// previous dates
val shouldLoadPreviousPage = indexOfCurrent < pageConfigForDates.prefetchDistance
if (shouldLoadPreviousPage) {
val pastDates = getPastDates(datesLoaded.first(), pageConfigForDates.pageSize)
datesLoaded = pastDates.plus(datesLoaded).take(pageConfigForDates.maxSize)
ensureListsExistForDates(pastDates)
return@switchMap MutableLiveData(datesLoaded)
}
// next dates
val shouldLoadNextPage = (datesLoaded.count() - indexOfCurrent) < pageConfigForDates.prefetchDistance
if (shouldLoadNextPage) {
val futureDates = getFutureDates(datesLoaded.last(), pageConfigForDates.pageSize)
datesLoaded = datesLoaded.plus(futureDates).takeLast(pageConfigForDates.maxSize)
ensureListsExistForDates(futureDates)
return@switchMap MutableLiveData(datesLoaded)
}
return@switchMap MutableLiveData(datesLoaded)
}

private fun getPastDates(date: LocalDate, numberOfDays: Int): List<LocalDate> {
return (numberOfDays downTo 1L).map { date.minusDays(it) }
}
private fun getDatesAround(date: LocalDate, numberOfDays: Int): List<LocalDate> {
val halfNumberOfDays = numberOfDays / 2
val counter = ((-halfNumberOfDays..0L) + (1L..halfNumberOfDays)).takeLast(numberOfDays)
return counter.map { date.plusDays(it) }.toList()
}
private fun getFutureDates(date: LocalDate, numberOfDays: Int): List<LocalDate> {
return (1L..numberOfDays).map { date.plusDays(it) }
}
private fun ensureListsExistForDates(dates: List<LocalDate>) {
viewModelScope.launch(Dispatchers.IO) {
val items = taskListRepository.getTaskListsWithTasksForDates(dates)
if (items.count() < pageConfigForDates.pageSize) {
val missingDates = dates.minus(items.map { it.taskList.date })
taskListRepository.createTaskLists(missingDates)
}
}
}
private val taskListsFromDatesLive = Transformations.distinctUntilChanged(datesToLoadLive).switchMap { dates ->
taskListRepository.getTaskListsWithTasksForDatesLive(dates!!)
}
}

访问DB的DAO

@Dao
interface TaskListWithTasksDao {
@Transaction
@Query("SELECT * FROM task_list WHERE date IN (:dates) ORDER BY date DESC")
suspend fun getTaskListWithTasksForDates(dates: List<LocalDate>): List<TaskListWithTasks>
@Transaction
@Query("SELECT * FROM task_list WHERE date IN (:dates) ORDER BY date DESC")
fun getTaskListWithTasksForDatesLive(dates: List<LocalDate>): LiveData<List<TaskListWithTasks>>
}

数据类(用于外键关系)

data class TaskListWithTasks(
@Embedded
var taskList: TaskList = TaskList(),
@Relation(parentColumn = "id", entityColumn = "task_list_id", entity = TaskListItem::class)
var tasks: List<TaskListItem> = emptyList()
)

最新更新