How to get the offset of scroll position relative to the current text view's start?

886 Views Asked by At

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?

2

There are 2 best solutions below

0
On BEST ANSWER

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
}
1
On

Don't use position in pixels, use the index of the view. Using layout manager's findFirstVisibleItemPosition or findFirstCompletelyVisibleItemPosition.

That's a very popular question, although it may be intuitive to think and search for pixels not index.

Get visible items in RecyclerView

Find if the first visible item in the recycler view is the first item of the list or not

how to get current visible item in Recycler View

A good reason to not trust pixels is that it's not useful on some situations where index is, like rotating the screen, resizeing/splitting the app size to fit other apps side by side, foldable phones, and changing text / screen resolution.