I have a RecyclerView that contains TextViews. The number of TextViews can vary and the size of them vary as well and can be dynamically changed.
When the user scrolls to a certain position within the list and exits app, I want to be able to return to that exact position in the next session.
To do this, I need to know how many pixels have scrolled past from where the current TextView in view started and where the current position of the scroll is. For example, if the user has the 3rd TextView in view and scrolls 100 pixels down from where that TextView started, I will be able to return to this spot with scrollToPositionWithOffset(2, 100). If the TextView changes size (due to font changes), I can also return to the same spot by calculating the percentage of offset using the TextView's height.
Problem is, I cannot get the offset value in any accurate manor.
I know I can keep a running calculation on the Y value scrolled using get scroll Y of RecyclerView or Webview, but this does not give me where the TextView actually started. I can listen to when the user scrolled past the start of any TextView and record the Y position there but this will be inaccurate on fast scrolling.
Is there a better way?
I solved this by converting to a ListView:
lateinit var adapterRead: AdapterRead // Custom Adapter
lateinit var itemListView: ListView
/*=======================================================================================================*/
// OnViewCreated
itemListView = view.findViewById(R.id.read_listview)
setListView(itemListView)
// Upon entering this Fragment, will automatically scroll to saved position:
itemListView.afterMeasured {
scrollToPosition(itemListView, getPosition(), getOffset())
}
itemListView.setOnScrollListener(object : AbsListView.OnScrollListener {
private var currentFirstVisibleItem = 0
var offset = 0
override fun onScrollStateChanged(view: AbsListView, scrollState: Int) {
// When scrolling stops, will save the current position and offset:
if(scrollState == AbsListView.OnScrollListener.SCROLL_STATE_IDLE) {
offset = if(itemListView.getChildAt(0) == null) 0 else itemListView.getChildAt(0).top - itemListView.paddingTop
saveReadPosition(getReadPosition(itemListView), offset)
}
}
override fun onScroll(view: AbsListView, firstVisibleItem: Int, visibleItemCount: Int, totalItemCount: Int) {
currentFirstVisibleItem = firstVisibleItem
}
})
/*=======================================================================================================*/
// Thanks to https://antonioleiva.com/kotlin-ongloballayoutlistener/ for this:
inline fun <T : View> T.afterMeasured(crossinline f: T.() -> Unit) {
viewTreeObserver.addOnGlobalLayoutListener(object : ViewTreeObserver.OnGlobalLayoutListener {
override fun onGlobalLayout() {
if(measuredWidth > 0 && measuredHeight > 0) {
viewTreeObserver.removeOnGlobalLayoutListener(this)
f()
}
}
})
}
/*=======================================================================================================*/
fun setListView(lv: ListView) {
adapterRead = AdapterRead(list, context!!)
lv.apply {this.adapter = adapterRead}
}
/*=======================================================================================================*/
fun scrollToPosition(lv: ListView, position: Int, offset: Int) {
lv.post { lv.setSelectionFromTop(position, offset) }
}
/*=======================================================================================================*/
fun saveReadPosition(position: Int, offset: Int) {
// Persist your data to database here
}
/*=======================================================================================================*/
fun getPosition() {
// Get your saved position here
}
/*=======================================================================================================*/
fun getOffse() {
// Get your saved offset here
}