I am trying to access the underlying data from a recyclerview item when it scrolls onto the screen.
I am using onAttachedToRecyclerView() inside my Adapter class. Then get the data within onScrolled().
Here is what I have so far:
override fun onAttachedToRecyclerView(recyclerView: RecyclerView) {
super.onAttachedToRecyclerView(recyclerView)
val manager = recyclerView.layoutManager
Log.i("ProductAdapter","$manager")
if (manager is StaggeredGridLayoutManager) {
val slm: StaggeredGridLayoutManager = manager
recyclerView.addOnScrollListener(object : RecyclerView.OnScrollListener() {
override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
super.onScrolled(recyclerView, dx, dy)
val firstVisibleItemPosition: Int
val firstVisibleItemPositions =
slm.findFirstVisibleItemPositions(null)
firstVisibleItemPosition = firstVisibleItemPositions[0]
if (firstVisibleItemPosition > -1) {
val item = getItem(firstVisibleItemPosition))
// do stuff with item data
}
}
})
}else{
Log.e("ProductAdapter","error")
}
}
when I set val manager = recyclerView.layoutManager, manager ends up being null. So the first if check never passes. My code is referencing this answer from another question.
Any ideas on how to fix this?
In my Fragment I was setting the adapter for the recyclerview one line before setting the layout manager.
Code before:
recyclerViewProducts.apply {
this.adapter = productAdapter
layoutManager = staggeredGridLayoutManager
itemAnimator?.changeDuration = 0
}
Code after:
recyclerViewProducts.apply {
layoutManager = staggeredGridLayoutManager
this.adapter = productAdapter
itemAnimator?.changeDuration = 0
}
Thanks to cactustictacs
Related
My RecyclerView scroll code
But this code gets called twice on the first start, I think findFirstCompletelyVisibleItemPosition is the problem
I used that code because I had to pull the scroll up. Is there any other way?
If you know how, please answer.
binding.rcvMessageRoom.run {
adapter = messageRoomRecyclerViewAdapter
layoutManager = LinearLayoutManager(context)
setHasFixedSize(true)
scrollToBottom()
addOnScrollListener(object : RecyclerView.OnScrollListener() {
override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
if (dy > 0) return
if ((layoutManager as LinearLayoutManager).findFirstCompletelyVisibleItemPosition() == 0) {
val handler = Handler()
handler.postDelayed({
viewModel.getChatMessage(chatRoomId, ++page, size, lastId)
}, 100)
}
}
})
}
I need to create a list with full screen videos.I used PagerSnapHelper for full screen child item. I have a single instance of Exoplayer and change video on scroll. Video also change on horizontally scroll.
RecyclerView.OnScrollListener() methods also called on horizontal scrolling.
Please suggest how I resolve these Issues.
RecyclerView should only scroll on vertical scrolling.
RecyclerView.OnScrollListener() should not call on horizontal scrolling
1. Scrolling Listener
class SnapOnScrollListener(
private val snapHelper: SnapHelper,
var behavior: Behavior = Behavior.NOTIFY_ON_SCROLL,
var onSnapPositionChangeListener: OnSnapPositionChangeListener? = null
) : RecyclerView.OnScrollListener() {
enum class Behavior {
NOTIFY_ON_SCROLL,
NOTIFY_ON_SCROLL_STATE_IDLE
}
override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
System.out.println("dx.. $dx dy $dy")
if (behavior == Behavior.NOTIFY_ON_SCROLL) {
maybeNotifySnapPositionChange(recyclerView)
System.out.println("dx.. $dx dy $dy snapChange")
}
}
override fun onScrollStateChanged(recyclerView: RecyclerView, newState: Int) {
System.out.println("dx......")
if ( behavior == Behavior.NOTIFY_ON_SCROLL_STATE_IDLE
&& newState == RecyclerView.SCROLL_STATE_IDLE
) {
maybeNotifySnapPositionChange(recyclerView)
System.out.println("dx......SnapChange")
}
}
private fun maybeNotifySnapPositionChange(recyclerView: RecyclerView) {
val snapPosition = snapHelper.getSnapPosition(recyclerView)
val snapPositionChanged =
(snapPosition != RecyclerView.NO_POSITION)
if (snapPositionChanged) {
onSnapPositionChangeListener?.onSnapPositionChange(snapPosition)
}
}
fun SnapHelper.getSnapPosition(recyclerView: RecyclerView): Int {
val layoutManager = recyclerView.layoutManager ?: return RecyclerView.NO_POSITION
val snapView = findSnapView(layoutManager) ?: return RecyclerView.NO_POSITION
return layoutManager.getPosition(snapView)
}
}
RecyclerView Adapter
private fun manageLiveShowList() {
liveShowAdapter = LiveShowAdapter(List.productList)
val layoutManager = LinearLayoutManager(requireContext(), RecyclerView.VERTICAL, false)
mLiveShowRecycler?.layoutManager = layoutManager
mLiveShowRecycler?.adapter = liveShowAdapter
snapHelper = PagerSnapHelper()
snapHelper.attachToRecyclerView(mLiveShowRecycler)
manageScrollListener(snapHelper)
}
3. Add Scroll Listener
private fun manageScrollListener(snapHelper: SnapHelper) {
val snapOnScrollListener = SnapOnScrollListener(
snapHelper,
SnapOnScrollListener.Behavior.NOTIFY_ON_SCROLL,
this
)
mLiveShowRecycler?.addOnScrollListener(snapOnScrollListener)
}
To disable scrolling of the RecyclerView you can modify layout manager as it has canScrollVertically and canScrollHorizontally methods. Note: the content inside of the view holders in recycler view can be scrollable by itself. That is another issue.
In your case override canScrollHorizontally to always return false:
private fun manageLiveShowList() {
liveShowAdapter = LiveShowAdapter(List.productList)
val layoutManager = LinearLayoutManager(requireContext(), RecyclerView.VERTICAL, false) {
override fun canScrollHorizontally(): Boolean = false
}
mLiveShowRecycler?.layoutManager = layoutManager
mLiveShowRecycler?.adapter = liveShowAdapter
snapHelper = PagerSnapHelper()
snapHelper.attachToRecyclerView(mLiveShowRecycler)
manageScrollListener(snapHelper)
}
Using below code to check that whether RecyclerView reached to bottom or not..
Means Checking that last item of the Recyclerview is visible or not..
For that I have googled and added Scroll listener in Recyclerview.
Using below code:
MyRecyclerView.addOnScrollListener(object : RecyclerView.OnScrollListener() {
override fun onScrollStateChanged(recyclerView: RecyclerView, newState: Int) {
super.onScrollStateChanged(recyclerView, newState)
}
override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
super.onScrolled(recyclerView, dx, dy)
if (!MyRecyclerView.canScrollVertically(1)) {
Toast.makeText(mContext, "Last", Toast.LENGTH_LONG).show();
}
}
})
Here, I am trying to check that Recyclerview reached to bottom or not.
Toast is not displaying when I scroll recyclerview to the bottom.
But, Toast displayed suddenly when items in the recyclerview binds first time. It should toast only when user scroll up to the bottom of the Recyclerview.
What might be the issue ?
Please guide. Thanks.
I think, using RecyclerView.LayoutManager api will better suit your needs.
You can check LinearLayoutManager::findLastCompletelyVisibleItemPosition or findLastVisibleItemPosition method in your RecyclerView.OnScrollListener
rvPlayers?.addOnScrollListener(object : RecyclerView.OnScrollListener() {
override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
super.onScrolled(recyclerView, dx, dy)
if (dy > 0) {
if (isLastVisable()) {
//code here
}
}
}
})
private fun isLastVisable(): Boolean {
val layoutManager = rvPlayers.layoutManager as LinearLayoutManager
val pos = layoutManager.findLastCompletelyVisibleItemPosition()
val numItems = adapter.itemCount
return (pos >= numItems - 1)
}
I am trying to add an infinite scroll to my Android Application but the onScrolled method doesnt work correctly I think so.
It will be called only once if I call the addOnScrollListener. But I think it should be called every time the RecyclerView has been scrolled.
linearLayoutManager = LinearLayoutManager(this)
recyclerViewNeuheiten.layoutManager = linearLayoutManager//LinearLayoutManager(this)
recyclerViewHistory.layoutManager = LinearLayoutManager(this)
recyclerViewBestSeller.layoutManager = LinearLayoutManager(this)
recyclerViewFavorites.layoutManager = LinearLayoutManager(this)
recyclerViewNeuheiten.adapter = neuheitenAdapter
recyclerViewHistory.adapter = historyAdapter
recyclerViewBestSeller.adapter = bestsellerAdapter
recyclerViewFavorites.adapter = favoriteAdapter
//setRecyclerViewScrollListener()
setRecyclerViewScrollListener()
private fun setRecyclerViewScrollListener() {
Log.v("scroll", "set listener")
recyclerViewNeuheiten.addOnScrollListener(object : RecyclerView.OnScrollListener() {
override fun onScrollStateChanged(recyclerView: RecyclerView, newState: Int) {
super.onScrollStateChanged(recyclerView, newState)
Log.v("scroll", "onScrollStateChanged newState $newState")
}
override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
Log.v("scroll", "onScrolled !!!!!")
super.onScrolled(recyclerView, dx, dy)
val currentItem = recyclerView.layoutManager!!.childCount
val totalItemCount = recyclerView.layoutManager!!.itemCount
Log.v("scroll", "currentItem $currentItem")
Log.v("scroll", "totalItemCount $totalItemCount")
Log.v("scroll", "lastVisibleItemPosition $lastVisibleItemPosition")
Log.v("scroll", "scroll out items ${linearLayoutManager.findFirstVisibleItemPosition()}")
}
})
}
I found the problem. I am using my RecyclerView inside a NestedScrollView. The onScrolled method triggers each scroll without a ScrollView.
I am using a RecyclerView to display items horizontally. I want to set the selected item to center of the view like this
.
This is how I am doing it:
LinearLayoutManager layoutManager
= new LinearLayoutManager(this, LinearLayoutManager.HORIZONTAL, false);
recyclerView.setLayoutManager(layoutManager);
To obtain middle item on your screen from RecyclerView you can attach OnScrollListener to RecyclerView and inside listener you should get position of current items then you should check if area of given item is on middle of screen.
Code sample in Kotlin:
// Attach OnScrollListener to your RecyclerView
addOnScrollListener(object : RecyclerView.OnScrollListener() {
override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
recyclerView.post {
selectMiddleItem()
}
}
})
// implementation of method that is called from OnScrollListener
private fun selectMiddleItem() {
val firstVisibleIndex = layoutManager.findFirstVisibleItemPosition()
val lastVisibleIndex = layoutManager.findLastVisibleItemPosition()
val visibleIndexes = listOf(firstVisibleIndex..lastVisibleIndex).flatten()
for (i in visibleIndexes) {
val vh = findViewHolderForLayoutPosition(i)
if (vh?.itemView == null) {
continue
}
val location = IntArray(2)
vh.itemView.getLocationOnScreen(location)
val x = location[0]
val halfWidth = vh.itemView.width * .5
val rightSide = x + halfWidth
val leftSide = x - halfWidth
val isInMiddle = screenWidth * .5 in leftSide..rightSide
if (isInMiddle) {
// "i" is your middle index and implement selecting it as you want
// optionsAdapter.selectItemAtIndex(i)
return
}
}
}
And as result you should get something like this:
This is for snapping the item in the center when scrolling, or when clicking on an ite.
You need to have a SnapHelper added to the RecyclerView. Here is how:
final RecyclerView recyclerViewObject = view.findViewById(R.id.recyclerViewObjectId);
final LinearSnapHelper snapHelper = new LinearSnapHelper();
snapHelper.attachToRecyclerView(recyclerViewObject);
recyclerViewObject.setOnFlingListener(snapHelper);
then you just call this code
recyclerViewObject.addOnItemTouchListener(
new RecyclerItemClickListener(getContext(), recyclerViewObject ,new RecyclerItemClickListener.OnItemClickListener() {
#Override public void onItemClick(View view, int position) {
recyclerViewObject.smoothScrollToPosition(position);
}
#Override public void onLongItemClick(View view, int position) {
}
})
);
You can achieve the same output with minimal lines of code. Where, view is view of selected adapter item and StaticData.SCREEN_WIDTH is device width.
View view = rvCategory.getChildAt(pos);
if (view == null)
return;
int scrollX = (view.getLeft() - (StaticData.SCREEN_WIDTH / 2)) + (view.getWidth() / 2);
rvCategory.smoothScrollBy(scrollX, 0);
Please try sort of this solution:
LinearLayoutManager layoutManager = ((LinearLayoutManager)recyclerView.getLayoutManager());
int totalVisibleItems = layoutManager.findLastVisibleItemPosition() - layoutManager.findFirstVisibleItemPosition()
int centeredItemPosition = totalVisibleItems / 2;
recyclerView.smoothScrollToPosition(position);
recyclerView.setScrollY(centeredItemPosition );
Hope this helps.
Define your custom layout manager as
class CenterLayoutManager : LinearLayoutManager {
constructor(context: Context) : super(context)
constructor(context: Context, orientation: Int, reverseLayout: Boolean) : super(context, orientation, reverseLayout)
constructor(context: Context, attrs: AttributeSet, defStyleAttr: Int, defStyleRes: Int) : super(context, attrs, defStyleAttr, defStyleRes)
override fun smoothScrollToPosition(recyclerView: RecyclerView, state: RecyclerView.State, position: Int) {
val centerSmoothScroller = CenterSmoothScroller(recyclerView.context)
centerSmoothScroller.targetPosition = position
startSmoothScroll(centerSmoothScroller)
}
private class CenterSmoothScroller(context: Context) : LinearSmoothScroller(context) {
override fun calculateDtToFit(viewStart: Int, viewEnd: Int, boxStart: Int, boxEnd: Int, snapPreference: Int): Int = (boxStart + (boxEnd - boxStart) / 2) - (viewStart + (viewEnd - viewStart) / 2)
}
}
Then assign this layout manager to your recycler view
myRecyclerview.layoutManager =
CenterLayoutManager(this, LinearLayoutManager.HORIZONTAL, false)
In recyclerview's item onClick method use
myRecyclerview.smoothScrollToPosition(position)
where position should get from onBindViewHolder
Also use LinearSnapHelper as
val snapHelper = LinearSnapHelper()
snapHelper.attachToRecyclerView(myRecyclerview)
it will controll scrolling effectively
Also attatch scroll listner to recyclerview to get item at center position
Recyclerview.setOnScrollListener(object:
RecyclerView.OnScrollListener() {
override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
super.onScrolled(recyclerView, dx, dy)
var view=recyclerView[0]
}
})
check out this stackoverflow answer for more details