improved save/restore position

This commit is contained in:
Mariotaku Lee 2016-12-24 21:07:11 +08:00
parent d0c05a2600
commit e70056dd13
3 changed files with 68 additions and 55 deletions

View File

@ -27,6 +27,7 @@ import android.view.View
import android.view.ViewGroup
import android.widget.TextView
import org.apache.commons.lang3.ArrayUtils
import org.mariotaku.ktextension.rangeOfSize
import org.mariotaku.ktextension.safeMoveToPosition
import org.mariotaku.library.objectcursor.ObjectCursor
import org.mariotaku.microblog.library.twitter.model.Activity
@ -357,6 +358,10 @@ class ParcelableActivitiesAdapter(
}
fun findPositionBySortTimestamp(timestamp: Long): Int {
return rangeOfSize(activityStartIndex, activityCount - 1).indexOfFirst { timestamp > 0 && getTimestamp(it) <= timestamp }
}
interface ActivityAdapterListener {
fun onGapClick(holder: GapViewHolder, position: Int)
@ -453,12 +458,12 @@ class ParcelableActivitiesAdapter(
}
companion object {
val ITEM_VIEW_TYPE_STUB = 0
val ITEM_VIEW_TYPE_GAP = 1
val ITEM_VIEW_TYPE_LOAD_INDICATOR = 2
val ITEM_VIEW_TYPE_TITLE_SUMMARY = 3
val ITEM_VIEW_TYPE_STATUS = 4
val ITEM_VIEW_TYPE_EMPTY = 5
}
}

View File

@ -37,6 +37,8 @@ import edu.tsinghua.hotmobi.HotMobiLogger
import edu.tsinghua.hotmobi.model.MediaEvent
import kotlinx.android.synthetic.main.fragment_content_recyclerview.*
import org.mariotaku.kpreferences.get
import org.mariotaku.ktextension.isNullOrEmpty
import org.mariotaku.ktextension.rangeOfSize
import org.mariotaku.twidere.BuildConfig
import org.mariotaku.twidere.Constants.KEY_NEW_DOCUMENT_API
import org.mariotaku.twidere.R
@ -196,66 +198,75 @@ abstract class AbsActivitiesFragment protected constructor() :
saveReadPosition(layoutManager.findFirstVisibleItemPosition())
}
/**
* Activities loaded, update adapter data & restore load position
*
* Steps:
* 1. Save current read position if not first load (adapter data is not empty)
* 1.1 If readFromBottom is true, save position on screen bottom
* 2. Change adapter data
* 3. Restore adapter data
* 3.1 If lastVisible was last item, keep lastVisibleItem position (load more)
* 3.2 Else, if readFromBottom is true:
* 3.1.1 If position was first, keep lastVisibleItem position (pull refresh)
* 3.1.2 Else, keep lastVisibleItem position
* 3.2 If readFromBottom is false:
* 3.2.1 If position was first, set new position to 0 (pull refresh)
* 3.2.2 Else, keep firstVisibleItem position (gap clicked)
*/
override fun onLoadFinished(loader: Loader<List<ParcelableActivity>>, data: List<ParcelableActivity>) {
val rememberPosition = preferences[rememberPositionKey]
val readPositionTag = currentReadPositionTag
val readFromBottom = preferences[readFromBottomKey]
var lastReadId: Long
val lastVisiblePos: Int
val lastVisibleTop: Int
val tag = currentReadPositionTag
val layoutManager = layoutManager
if (readFromBottom) {
lastVisiblePos = layoutManager.findLastVisibleItemPosition()
} else {
lastVisiblePos = layoutManager.findFirstVisibleItemPosition()
}
if (lastVisiblePos != RecyclerView.NO_POSITION && lastVisiblePos < adapter.itemCount) {
val activityStartIndex = adapter.activityStartIndex
val activityEndIndex = activityStartIndex + adapter.activityCount
val lastItemIndex = Math.min(activityEndIndex, lastVisiblePos)
lastReadId = adapter.getTimestamp(lastItemIndex)
val positionView = layoutManager.findViewByPosition(lastItemIndex)
lastVisibleTop = positionView?.top ?: 0
} else if (rememberPosition && tag != null) {
lastReadId = readStateManager.getPosition(tag)
lastVisibleTop = 0
} else {
lastReadId = -1
lastVisibleTop = 0
}
adapter.setData(data)
val activityStartIndex = adapter.activityStartIndex
// The last activity is activityEndExclusiveIndex - 1
val activityEndExclusiveIndex = activityStartIndex + adapter.activityCount
val firstLoad = adapterData.isNullOrEmpty()
if (activityEndExclusiveIndex >= 0 && rememberPosition && tag != null) {
val lastItemId = adapter.getTimestamp(activityEndExclusiveIndex)
// Activity corresponds to last read timestamp was deleted, use last item timestamp
// instead
if (lastItemId > 0 && lastReadId < lastItemId) {
lastReadId = lastItemId
}
var lastReadId: Long = -1
var lastReadViewTop: Int = 0
var loadMore = false
// 1. Save current read position if not first load
if (!firstLoad) {
val lastVisibleItemPosition = layoutManager.findLastVisibleItemPosition()
val statusRange = rangeOfSize(adapter.activityStartIndex, adapter.activityCount - 1)
val lastReadPosition = if (readFromBottom) {
lastVisibleItemPosition
} else {
layoutManager.findFirstVisibleItemPosition()
}.coerceIn(statusRange)
lastReadId = adapter.getTimestamp(lastReadPosition)
lastReadViewTop = layoutManager.findViewByPosition(lastReadPosition)?.top ?: 0
loadMore = lastVisibleItemPosition >= statusRange.endInclusive
} else if (rememberPosition && readPositionTag != null) {
lastReadId = readStateManager.getPosition(readPositionTag)
lastReadViewTop = 0
}
adapter.setData(data)
refreshEnabled = true
var restorePosition = -1
if (loader !is IExtendedLoader || loader.fromUser) {
adapter.loadMoreSupportedPosition = if (hasMoreData(data)) ILoadMoreSupportAdapter.END else ILoadMoreSupportAdapter.NONE
var pos = -1
for (i in activityStartIndex until activityEndExclusiveIndex) {
if (lastReadId != -1L && adapter.getTimestamp(i) <= lastReadId) {
pos = i
break
}
if (hasMoreData(data)) {
adapter.loadMoreSupportedPosition = ILoadMoreSupportAdapter.END
} else {
adapter.loadMoreSupportedPosition = ILoadMoreSupportAdapter.NONE
}
if (pos != -1 && adapter.isActivity(pos) && (readFromBottom || lastVisiblePos != 0)) {
if (layoutManager.height == 0) {
// RecyclerView has not currently laid out, ignore padding.
layoutManager.scrollToPositionWithOffset(pos, lastVisibleTop)
} else {
layoutManager.scrollToPositionWithOffset(pos, lastVisibleTop - layoutManager.paddingTop)
}
restorePosition = adapter.findPositionBySortTimestamp(lastReadId)
}
if (restorePosition != -1 && adapter.isActivity(restorePosition) && (loadMore || readFromBottom
|| (rememberPosition && firstLoad))) {
if (layoutManager.height == 0) {
// RecyclerView has not currently laid out, ignore padding.
layoutManager.scrollToPositionWithOffset(restorePosition, lastReadViewTop)
} else {
layoutManager.scrollToPositionWithOffset(restorePosition, lastReadViewTop - layoutManager.paddingTop)
}
}
if (loader is IExtendedLoader) {
loader.fromUser = false
}
@ -368,10 +379,6 @@ abstract class AbsActivitiesFragment protected constructor() :
super.onStop()
}
override fun onDestroyView() {
super.onDestroyView()
}
override fun scrollToStart(): Boolean {
val result = super.scrollToStart()
if (result) {

View File

@ -316,7 +316,8 @@ abstract class AbsStatusesFragment protected constructor() :
} else {
onHasMoreDataChanged(false)
}
if (restorePosition != -1 && adapter.isStatus(restorePosition) && (loadMore || readFromBottom)) {
if (restorePosition != -1 && adapter.isStatus(restorePosition) && (loadMore || readFromBottom
|| (rememberPosition && firstLoad))) {
if (layoutManager.height == 0) {
// RecyclerView has not currently laid out, ignore padding.
layoutManager.scrollToPositionWithOffset(restorePosition, lastReadViewTop)