onScrolled is being called without any user scrolling in recyclerView - android

I am trying to create pagination. For that I use retrofit calls to MovieDb API with page query in my viewModel:
fun getMovies(page: Int = 1) {
launch {
if (page == 1) {
liveData.value =
State.ShowLoading
}
val list = withContext(Dispatchers.IO) {
try {
val api: MovieApi? = RetrofitService.getClient()?.create(MovieApi::class.java)
val response =
api?.getPopularMoviesListCoroutine(BuildConfig.THE_MOVIE_DB_API_TOKEN, page)
if (response?.isSuccessful!!) {
val result = response.body()
val list = response?.body()?.results ?: emptyList()
val totalPage = response?.body()?.totalPages ?: 0
if (!result?.results.isNullOrEmpty()) {
movieDao.insertAll(result?.results!!)
}
Pair(totalPage, list)
} else {
Pair(
1, movieDao.getAll() ?: emptyList<Movie>()
)
}
} catch (e: Exception) {
Log.e("Moviedatabase", e.toString())
Pair(
1, movieDao.getAll() ?: emptyList<Movie>()
)
}
}
liveData.postValue(
State.Result(
totalPage = list.first,
list = list.second
)
)
liveData.value = State.HideLoading
}
}
and in my Fragment I use that viewModel:
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
val viewModelProviderFactory = ViewModelProviderFactory(requireContext())
movieListViewModel =
ViewModelProvider(this, viewModelProviderFactory).get(MoviesListViewModel::class.java)
return inflater.inflate(R.layout.fragment_movie_list, container, false)
}
Then in bind i call getMovies in recyclerView srolling:
recyclerView.addOnScrollListener(object :PaginationListener(layoutManager){
override fun loadMoreItems() {
Log.d("load_more", "true --"+currentPage )
isLoading = true
currentPage++
// mocking network delay for API call
Handler().postDelayed({
movieListViewModel.getMovies(page = currentPage)
}, 3000) }
PaginationListener:
override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
super.onScrolled(recyclerView, dx, dy)
val visibleItemCount = layoutManager.childCount
val totalItemCount = layoutManager.itemCount
val firstVisibleItemPosition = layoutManager.findFirstVisibleItemPosition()
if (!isLoading() && !isLastPage()) {
if (visibleItemCount + firstVisibleItemPosition >= totalItemCount &&
firstVisibleItemPosition >= 0 &&
totalItemCount >= PAGE_SIZE) {
loadMoreItems()
}
}
}
Also when setting data I use that sealed class objects:
is MoviesListViewModel.State.Result -> {
itemCount = result.list.size
if (currentPage != PaginationListener.PAGE_START) {
movieListAdapter?.removeLoading()
Log.d("load_more", "remove loading")
}
movieListAdapter?.addItems(result.list)
if (currentPage < result.totalPage) {
movieListAdapter?.addLoading()
Log.d("load_more", "add loading")
} else {
isLastPage = true
}
isLoading = false
So, loadMoreItems is called always. Please give me solution

you are right. onScroll calls every time activity is created.
here is part of my fragment which handle pagination.
I hope it helps :
//define out of method blocks (globaly):
boolean loaded = false;
int page = 2;
.
.
.
//in onCreate method:
list.setOnScrollListener(new AbsListView.OnScrollListener() {
public void onScrollStateChanged(AbsListView view, int scrollState) {
}
public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
if (firstVisibleItem + visibleItemCount == totalItemCount && totalItemCount != 0) {
if (!loaded) {
loadMore(nextPage++);
loaded = true;
}
}
}
});
.
.
.
.
.
//after list filled (onResponse):
int index = list.getFirstVisiblePosition();
View v = list.getChildAt(0);
int top = (v == null) ? 0 : v.getTop();
list.setSelectionFromTop(index, top);
loaded = false;
if you didn't understand, tell me to explain more and provide whole classes.

Related

Lazy Loading for the images in Recycler view

i am not able to do the lazy loading for the images
this is my code of the adapter. I am providing it all the urls from my database in the form of list please help
class Horizontal_lower_recycler(private val images:List<Image>,private val context: Context,private val recyclerView: RecyclerView):
RecyclerView.Adapter<Horizontal_lower_recycler.MyViewHolder>() {
private var isLoading = false
init {
recyclerView.addOnScrollListener(object : RecyclerView.OnScrollListener() {
override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
super.onScrolled(recyclerView, dx, dy)
val layoutManager = recyclerView.layoutManager as LinearLayoutManager
val visibleItemCount = layoutManager.childCount
val totalItemCount = layoutManager.itemCount
val firstVisibleItemPosition = layoutManager.findFirstVisibleItemPosition()
if (!isLoading) {
if (visibleItemCount + firstVisibleItemPosition >= totalItemCount
&& firstVisibleItemPosition >= 0
&& totalItemCount >= PAGE_SIZE
) {
isLoading = true
}
}
}
})
}
class MyViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
val image1: ImageView = itemView.findViewById(R.id.lower_image1)
val image2: ImageView = itemView.findViewById(R.id.lower_image2)
val image3: ImageView = itemView.findViewById(R.id.lower_image3)
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MyViewHolder {
val view = LayoutInflater.from(parent.context).inflate(
R.layout.bootm_recycler,
parent, false
)
return MyViewHolder(view)
}
override fun getItemCount(): Int {
return images.size / 3 + images.size % 3
}
override fun onBindViewHolder(holder: MyViewHolder, position: Int) {
val images = images.subList(position * 3, min(position * 3 + 3, images.size))
for (i in images.indices) {
when (i) {
0 -> {
Glide.with(holder.itemView).load(images[i].url).apply(RequestOptions().format(
DecodeFormat.PREFER_RGB_565).disallowHardwareConfig())
.into(holder.image1)
holder.image1.setOnClickListener {
val intent = Intent(context, image_full_size_avtivity::class.java)
intent.putExtra("url", images[i].url)
context.startActivity(intent)
}
}
1 -> {
Glide.with(holder.itemView).load(images[i].url).apply(RequestOptions().format(
DecodeFormat.PREFER_RGB_565).disallowHardwareConfig())
.into(holder.image2)
holder.image2.setOnClickListener {
val intent = Intent(context, image_full_size_avtivity::class.java)
intent.putExtra("url", images[i].url)
context.startActivity(intent)
}
}
2 -> {
Glide.with(holder.itemView).load(images[i].url).apply(RequestOptions().format(
DecodeFormat.PREFER_RGB_565).disallowHardwareConfig())
.into(holder.image3)
holder.image3.setOnClickListener {
val intent = Intent(context, image_full_size_avtivity::class.java)
intent.putExtra("url", images[i].url)
context.startActivity(intent)
}
}
}
}
}
}
Help me in doing the lazy loading. When i open the app all the images are loaded at once without scrolling the recycler view.

Handle pagination with recyclerview gridlayout manager dynamically in android

I am implementing pagination to show data from API by using the recyclerview gridlayout manager. I have tried multiple ways to implement it, finally found a solution but still, it is not working properly. I show data in the list from API, but whenever my data shows in recyclerview from API then it's on scrolllistener automatically works which it should only when I scroll the list.
I am adding a snippet of my code below.
private lateinit var scrollListener: RecyclerviewLoadMore
private lateinit var mLayoutManager: RecyclerView.LayoutManager
//int
private var pageNum = 1
private var limit = 0
motivationAdapter = MotivationsAdapter(requireActivity(), getVideoList, object : RecyclerViewClickHandler {
override fun onItemClicked(position: Int, type: Int, view: View) {
})
rvMotivation?.adapter = motivationAdapter
rvMotivation?.addItemDecoration(SpacesItemDecoration(2, 30, false))
mLayoutManager = GridLayoutManager(requireActivity(), 2)
rvMotivation?.setHasFixedSize(true)
(mLayoutManager as GridLayoutManager).spanSizeLookup =
object : GridLayoutManager.SpanSizeLookup() {
override fun getSpanSize(position: Int): Int {
return when (motivationAdapter?.getItemViewType(position)) {
RecyclerScrollEnums.VIEW_TYPE_ITEM.viewType -> 1
RecyclerScrollEnums.VIEW_TYPE_LOADING.viewType -> 2 //number of columns of the grid
else -> -1
}
}
}
Handler(Looper.getMainLooper()).postDelayed({
//scroll listener
scrollListener = RecyclerviewLoadMore(mLayoutManager as GridLayoutManager)
scrollListener.setOnLoadMoreListener(object :
OnLoadMoreListener {
override fun onLoadMore() {
//Get the number of the current Items of the main Arraylist
val start = motivationAdapter?.itemCount
scrollListener.isLoading = false
if(scrollListener.lastVisibleItem==limit){
motivationAdapter?.addLoadingView()
getVideoAPi()
}
}
})
rvMotivation?.addOnScrollListener(scrollListener)
}, 1000)
I notify data when getting data from the API success.
if (response.success == SOCKET_TRUE) {
pageNum += 1
if (limit>0) {
motivationAdapter?.removeLoadingView()
}
limit += response?.data?.size!!
updateAdapterCategory(response.data)
}
fun updateAdapterCategory(getMotivatedList: ArrayList<DataItem?>?) {
getVideoList?.addAll(getMotivatedList!!)
motivationAdapter?.notifyDataSetChanged()
}
This recyclerview scroll listener class which I found on the internet.
class RecyclerviewLoadMore : RecyclerView.OnScrollListener {
private var visibleThreshold = 5
private lateinit var mOnLoadMoreListener: OnLoadMoreListener
internal var isLoading: Boolean = false
internal var lastVisibleItem: Int = 0
internal var totalItemCount: Int = 0
private var mLayoutManager: RecyclerView.LayoutManager
//linear layout manager
constructor(layoutManager: LinearLayoutManager) {
this.mLayoutManager = layoutManager
}
//grid layout manager
constructor(layoutManager: GridLayoutManager) {
this.mLayoutManager = layoutManager
visibleThreshold *= layoutManager.spanCount
}
//staggered layout manager
constructor(layoutManager: StaggeredGridLayoutManager) {
this.mLayoutManager = layoutManager
visibleThreshold *= layoutManager.spanCount
}
//interface to load more data for pagination
fun setOnLoadMoreListener(mOnLoadMoreListener: OnLoadMoreListener) {
this.mOnLoadMoreListener = mOnLoadMoreListener
}
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)
totalItemCount = mLayoutManager.itemCount
if (mLayoutManager is StaggeredGridLayoutManager) {
val lastVisibleItemPositions =
(mLayoutManager as StaggeredGridLayoutManager).findLastVisibleItemPositions(null)
// get maximum element within the list
lastVisibleItem = getLastVisibleItem(lastVisibleItemPositions)
} else if (mLayoutManager is GridLayoutManager) {
lastVisibleItem = (mLayoutManager as GridLayoutManager).findLastVisibleItemPosition()
} else if (mLayoutManager is LinearLayoutManager) {
lastVisibleItem = (mLayoutManager as LinearLayoutManager).findLastVisibleItemPosition()
}
if (!isLoading && totalItemCount <= lastVisibleItem + visibleThreshold) {
mOnLoadMoreListener.onLoadMore()
isLoading = true
}
}
private fun getLastVisibleItem(lastVisibleItemPositions: IntArray): Int {
var maxSize = 0
for (i in lastVisibleItemPositions.indices) {
if (i == 0) {
maxSize = lastVisibleItemPositions[i]
} else if (lastVisibleItemPositions[i] > maxSize) {
maxSize = lastVisibleItemPositions[i]
}
}
return maxSize
}
}

How to snap to the left side of the item in RecyclerView

I have recyclerview with horizontal scroll and it snaps by default with LinearSnapHelper or PagerSnapHelper to the center of item. I want to snap to the left side of the each item. Is it possible?
You can easily extend PagerSnapHelper to align items by left side. There is one trick only needed with last item.
My solution is:
class AlignLeftPagerSnapHelper : PagerSnapHelper() {
private var horizontalHelper: OrientationHelper? = null
override fun findSnapView(layoutManager: RecyclerView.LayoutManager?): View? {
return getStartView(layoutManager as LinearLayoutManager, getHorizontalHelper(layoutManager))
}
private fun getStartView(layoutManager: LinearLayoutManager, helper: OrientationHelper): View? {
val firstVisibleChildPosition = layoutManager.findFirstVisibleItemPosition()
val lastCompletelyVisibleChildPosition = layoutManager.findLastCompletelyVisibleItemPosition()
val lastChildPosition = layoutManager.itemCount - 1
if (firstVisibleChildPosition != RecyclerView.NO_POSITION) {
var childView = layoutManager.findViewByPosition(firstVisibleChildPosition)
if (helper.getDecoratedEnd(childView) < helper.getDecoratedMeasurement(childView) / 2) {
childView = layoutManager.findViewByPosition(firstVisibleChildPosition + 1)
} else if (lastCompletelyVisibleChildPosition == lastChildPosition) {
childView = layoutManager.findViewByPosition(lastChildPosition)
}
return childView
}
return null
}
override fun calculateDistanceToFinalSnap(layoutManager: RecyclerView.LayoutManager, targetView: View): IntArray =
intArrayOf(distanceToStart(targetView, getHorizontalHelper(layoutManager)), 0)
override fun findTargetSnapPosition(
layoutManager: RecyclerView.LayoutManager,
velocityX: Int,
velocityY: Int
): Int {
val currentView = findSnapView(layoutManager) ?: return RecyclerView.NO_POSITION
val currentPosition = layoutManager.getPosition(currentView)
return if (velocityX < 0) {
(currentPosition - 1).coerceAtLeast(0)
} else {
(currentPosition + 1).coerceAtMost(layoutManager.itemCount - 1)
}
}
private fun distanceToStart(targetView: View, helper: OrientationHelper): Int =
helper.getDecoratedStart(targetView) - helper.startAfterPadding
private fun getHorizontalHelper(layoutManager: RecyclerView.LayoutManager): OrientationHelper {
if (horizontalHelper == null) {
horizontalHelper = OrientationHelper.createHorizontalHelper(layoutManager)
}
return horizontalHelper!!
}
}

Detect when RecyclerView reaches the bottom most position while scrolling

I have this code for a RecyclerView.
recyclerView = (RecyclerView)rootview.findViewById(R.id.fabric_recyclerView);
recyclerView.setLayoutManager(layoutManager);
recyclerView.addItemDecoration(new RV_Item_Spacing(5));
FabricAdapter fabricAdapter=new FabricAdapter(ViewAdsCollection.getFabricAdsDetailsAsArray());
recyclerView.setAdapter(fabricAdapter);
I need to know when the RecyclerView reaches bottom most position while scrolling. Is it possible ? If yes, how ?
there is also a simple way to do it
recyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
#Override
public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
super.onScrollStateChanged(recyclerView, newState);
if (!recyclerView.canScrollVertically(1)) {
Toast.makeText(YourActivity.this, "Last", Toast.LENGTH_LONG).show();
}
}
});
direction integers: -1 for up, 1 for down, 0 will always return false.
Use this code for avoiding repeated calls
recyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
#Override
public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
super.onScrollStateChanged(recyclerView, newState);
if (!recyclerView.canScrollVertically(1) && newState==RecyclerView.SCROLL_STATE_IDLE) {
Log.d("-----","end");
}
}
});
Just implement a addOnScrollListener() on your recyclerview. Then inside the scroll listener implement the code below.
RecyclerView.OnScrollListener mScrollListener = new RecyclerView.OnScrollListener() {
#Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
if (mIsLoading)
return;
int visibleItemCount = mLayoutManager.getChildCount();
int totalItemCount = mLayoutManager.getItemCount();
int pastVisibleItems = mLayoutManager.findFirstVisibleItemPosition();
if (pastVisibleItems + visibleItemCount >= totalItemCount) {
//End of list
}
}
};
After not being satisfied with most the other answers in this thread, I found something I think is better and is not anywhere on here.
recyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
#Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
if (!recyclerView.canScrollVertically(1) && dy > 0)
{
//scrolled to BOTTOM
}else if (!recyclerView.canScrollVertically(-1) && dy < 0)
{
//scrolled to TOP
}
}
});
This is simple and will hit exactly one time under all conditions when you have scrolled to the top or bottom.
Answer is in Kotlin, it will work in Java. IntelliJ should convert it for you if you copy and paste.
recyclerView.addOnScrollListener(object : RecyclerView.OnScrollListener(){
override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
// 3 lines below are not needed.
Log.d("TAG","Last visible item is: ${gridLayoutManager.findLastVisibleItemPosition()}")
Log.d("TAG","Item count is: ${gridLayoutManager.itemCount}")
Log.d("TAG","end? : ${gridLayoutManager.findLastVisibleItemPosition() == gridLayoutManager.itemCount-1}")
if(gridLayoutManager.findLastVisibleItemPosition() == gridLayoutManager.itemCount-1){
// We have reached the end of the recycler view.
}
super.onScrolled(recyclerView, dx, dy)
}
})
This will also work for LinearLayoutManager because it has the same methods used above. Namely findLastVisibleItemPosition() and getItemCount() (itemCount in Kotlin).
I was not getting a perfect solution by the above answers because it was triggering twice even on onScrolled
override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
super.onScrolled(recyclerView, dx, dy)
if( !recyclerView.canScrollVertically(RecyclerView.FOCUS_DOWN))
context?.toast("Scroll end reached")
}
Alternative solution which I had found some days ago,
rv_repatriations.addOnScrollListener(object : RecyclerView.OnScrollListener() {
override fun onScrollStateChanged(recyclerView: RecyclerView, newState: Int) {
super.onScrollStateChanged(recyclerView, newState)
if (!recyclerView.canScrollVertically(RecyclerView.FOCUS_DOWN) && recyclerView.scrollState == RecyclerView.SCROLL_STATE_IDLE
&& !isLoaded
) {
isLoaded = true
//do what you want here and after calling the function change the value of boolean
Log.e("RepatriationFragment", "Scroll end reached")
}
}
})
Using a boolean to ensure that it's not called multiple times when we hit the bottom.
Try This
I have used above answers it runs always when you will go at the end of recycler view,
If you want to check only one time whether it is on a bottom or not?
Example:- If I have the list of 10 items whenever I go on the bottom it will display me and again if I scroll top to bottom it will not print again, and if you add more lists and you go there it will again display.
Note:- Use this method when you deal with offset in hitting API
Create a class named as EndlessRecyclerViewScrollListener
import android.support.v7.widget.GridLayoutManager;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.support.v7.widget.StaggeredGridLayoutManager;
public abstract class EndlessRecyclerViewScrollListener extends RecyclerView.OnScrollListener {
// The minimum amount of items to have below your current scroll position
// before loading more.
private int visibleThreshold = 5;
// The current offset index of data you have loaded
private int currentPage = 0;
// The total number of items in the dataset after the last load
private int previousTotalItemCount = 0;
// True if we are still waiting for the last set of data to load.
private boolean loading = true;
// Sets the starting page index
private int startingPageIndex = 0;
RecyclerView.LayoutManager mLayoutManager;
public EndlessRecyclerViewScrollListener(LinearLayoutManager layoutManager) {
this.mLayoutManager = layoutManager;
}
// public EndlessRecyclerViewScrollListener() {
// this.mLayoutManager = layoutManager;
// visibleThreshold = visibleThreshold * layoutManager.getSpanCount();
// }
public EndlessRecyclerViewScrollListener(StaggeredGridLayoutManager layoutManager) {
this.mLayoutManager = layoutManager;
visibleThreshold = visibleThreshold * layoutManager.getSpanCount();
}
public int getLastVisibleItem(int[] lastVisibleItemPositions) {
int maxSize = 0;
for (int i = 0; i < lastVisibleItemPositions.length; i++) {
if (i == 0) {
maxSize = lastVisibleItemPositions[i];
}
else if (lastVisibleItemPositions[i] > maxSize) {
maxSize = lastVisibleItemPositions[i];
}
}
return maxSize;
}
// This happens many times a second during a scroll, so be wary of the code you place here.
// We are given a few useful parameters to help us work out if we need to load some more data,
// but first we check if we are waiting for the previous load to finish.
#Override
public void onScrolled(RecyclerView view, int dx, int dy) {
int lastVisibleItemPosition = 0;
int totalItemCount = mLayoutManager.getItemCount();
if (mLayoutManager instanceof StaggeredGridLayoutManager) {
int[] lastVisibleItemPositions = ((StaggeredGridLayoutManager) mLayoutManager).findLastVisibleItemPositions(null);
// get maximum element within the list
lastVisibleItemPosition = getLastVisibleItem(lastVisibleItemPositions);
} else if (mLayoutManager instanceof GridLayoutManager) {
lastVisibleItemPosition = ((GridLayoutManager) mLayoutManager).findLastVisibleItemPosition();
} else if (mLayoutManager instanceof LinearLayoutManager) {
lastVisibleItemPosition = ((LinearLayoutManager) mLayoutManager).findLastVisibleItemPosition();
}
// If the total item count is zero and the previous isn't, assume the
// list is invalidated and should be reset back to initial state
if (totalItemCount < previousTotalItemCount) {
this.currentPage = this.startingPageIndex;
this.previousTotalItemCount = totalItemCount;
if (totalItemCount == 0) {
this.loading = true;
}
}
// If it’s still loading, we check to see if the dataset count has
// changed, if so we conclude it has finished loading and update the current page
// number and total item count.
if (loading && (totalItemCount > previousTotalItemCount)) {
loading = false;
previousTotalItemCount = totalItemCount;
}
// If it isn’t currently loading, we check to see if we have breached
// the visibleThreshold and need to reload more data.
// If we do need to reload some more data, we execute onLoadMore to fetch the data.
// threshold should reflect how many total columns there are too
if (!loading && (lastVisibleItemPosition + visibleThreshold) > totalItemCount) {
currentPage++;
onLoadMore(currentPage, totalItemCount, view);
loading = true;
}
}
// Call this method whenever performing new searches
public void resetState() {
this.currentPage = this.startingPageIndex;
this.previousTotalItemCount = 0;
this.loading = true;
}
// Defines the process for actually loading more data based on page
public abstract void onLoadMore(int page, int totalItemsCount, RecyclerView view);
}
use this class like this
LinearLayoutManager linearLayoutManager = new LinearLayoutManager(getActivity());
recyclerView.setLayoutManager(linearLayoutManager);
recyclerView.addOnScrollListener(new EndlessRecyclerViewScrollListener( linearLayoutManager) {
#Override
public void onLoadMore(int page, int totalItemsCount, RecyclerView view) {
Toast.makeText(getActivity(),"LAst",Toast.LENGTH_LONG).show();
}
});
Its running perfect at my end, commnent me if you are getting any issue
Kotlin Answer
You can use this Kotlin function for best practice of bottom scroll following to create infinite or endless scrolling.
// Scroll listener.
private fun setupListenerPostListScroll() {
val scrollDirectionDown = 1 // Scroll down is +1, up is -1.
var currentListSize = 0
mRecyclerView.addOnScrollListener(
object : RecyclerView.OnScrollListener() {
override fun onScrollStateChanged(recyclerView: RecyclerView, newState: Int) {
super.onScrollStateChanged(recyclerView, newState)
if (!recyclerView.canScrollVertically(scrollDirectionDown)
&& newState == RecyclerView.SCROLL_STATE_IDLE
) {
val listSizeAfterLoading = recyclerView.layoutManager!!.itemCount
// List has more item.
if (currentListSize != listSizeAfterLoading) {
currentListSize = listSizeAfterLoading
// Get more posts.
postListScrollUpAction(listSizeAfterLoading)
}
else { // List comes limit.
showToastMessageShort("No more items.")
}
}
}
})
}
There is my implementation, it is very useful for StaggeredGridLayout.
Usage :
private EndlessScrollListener scrollListener =
new EndlessScrollListener(new EndlessScrollListener.RefreshList() {
#Override public void onRefresh(int pageNumber) {
//end of the list
}
});
rvMain.addOnScrollListener(scrollListener);
Listener implementation :
class EndlessScrollListener extends RecyclerView.OnScrollListener {
private boolean isLoading;
private boolean hasMorePages;
private int pageNumber = 0;
private RefreshList refreshList;
private boolean isRefreshing;
private int pastVisibleItems;
EndlessScrollListener(RefreshList refreshList) {
this.isLoading = false;
this.hasMorePages = true;
this.refreshList = refreshList;
}
#Override public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
super.onScrolled(recyclerView, dx, dy);
StaggeredGridLayoutManager manager =
(StaggeredGridLayoutManager) recyclerView.getLayoutManager();
int visibleItemCount = manager.getChildCount();
int totalItemCount = manager.getItemCount();
int[] firstVisibleItems = manager.findFirstVisibleItemPositions(null);
if (firstVisibleItems != null && firstVisibleItems.length > 0) {
pastVisibleItems = firstVisibleItems[0];
}
if (visibleItemCount + pastVisibleItems >= totalItemCount && !isLoading) {
isLoading = true;
if (hasMorePages && !isRefreshing) {
isRefreshing = true;
new Handler().postDelayed(new Runnable() {
#Override public void run() {
refreshList.onRefresh(pageNumber);
}
}, 200);
}
} else {
isLoading = false;
}
}
public void noMorePages() {
this.hasMorePages = false;
}
void notifyMorePages() {
isRefreshing = false;
pageNumber = pageNumber + 1;
}
interface RefreshList {
void onRefresh(int pageNumber);
} }
I was also searching for this question but I didn't find the answer that satisfied me, so I create own realization of recyclerView.
other solutions is less precise then mine. for example: if the last item is pretty big (lot of text) then callback of other solutions will come much earlier then recyclerView realy reached bottom.
my sollution fix this issue.
class CustomRecyclerView: RecyclerView{
abstract class TopAndBottomListener{
open fun onBottomNow(onBottomNow:Boolean){}
open fun onTopNow(onTopNow:Boolean){}
}
constructor(c:Context):this(c, null)
constructor(c:Context, attr:AttributeSet?):super(c, attr, 0)
constructor(c:Context, attr:AttributeSet?, defStyle:Int):super(c, attr, defStyle)
private var linearLayoutManager:LinearLayoutManager? = null
private var topAndBottomListener:TopAndBottomListener? = null
private var onBottomNow = false
private var onTopNow = false
private var onBottomTopScrollListener:RecyclerView.OnScrollListener? = null
fun setTopAndBottomListener(l:TopAndBottomListener?){
if (l != null){
checkLayoutManager()
onBottomTopScrollListener = createBottomAndTopScrollListener()
addOnScrollListener(onBottomTopScrollListener)
topAndBottomListener = l
} else {
removeOnScrollListener(onBottomTopScrollListener)
topAndBottomListener = null
}
}
private fun createBottomAndTopScrollListener() = object :RecyclerView.OnScrollListener(){
override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
checkOnTop()
checkOnBottom()
}
}
private fun checkOnTop(){
val firstVisible = linearLayoutManager!!.findFirstCompletelyVisibleItemPosition()
if(firstVisible == 0 || firstVisible == -1 && !canScrollToTop()){
if (!onTopNow) {
onTopNow = true
topAndBottomListener?.onTopNow(true)
}
} else if (onTopNow){
onTopNow = false
topAndBottomListener?.onTopNow(false)
}
}
private fun checkOnBottom(){
var lastVisible = linearLayoutManager!!.findLastCompletelyVisibleItemPosition()
val size = linearLayoutManager!!.itemCount - 1
if(lastVisible == size || lastVisible == -1 && !canScrollToBottom()){
if (!onBottomNow){
onBottomNow = true
topAndBottomListener?.onBottomNow(true)
}
} else if(onBottomNow){
onBottomNow = false
topAndBottomListener?.onBottomNow(false)
}
}
private fun checkLayoutManager(){
if (layoutManager is LinearLayoutManager)
linearLayoutManager = layoutManager as LinearLayoutManager
else
throw Exception("for using this listener, please set LinearLayoutManager")
}
private fun canScrollToTop():Boolean = canScrollVertically(-1)
private fun canScrollToBottom():Boolean = canScrollVertically(1)
}
then in your activity/fragment:
override fun onCreate() {
customRecyclerView.layoutManager = LinearLayoutManager(context)
}
override fun onResume() {
super.onResume()
customRecyclerView.setTopAndBottomListener(this)
}
override fun onStop() {
super.onStop()
customRecyclerView.setTopAndBottomListener(null)
}
hope it will hepl someone ;-)
Using Kotlin
recyclerView.addOnScrollListener(object : RecyclerView.OnScrollListener() {
override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
if (!recyclerView.canScrollVertically(1)) {
Toast.makeText(context, "Last", Toast.LENGTH_LONG).show();
}
}
})
I've seen to many responses for this question and I stand that all of them don't give accurate behavior as an outcome. However if you follow this approach I'm positive you'll get the best behavior.
rvCategories is your RecyclerView
categoriesList is the list passed to your adapter
binding.rvCategories.addOnScrollListener(object : RecyclerView.OnScrollListener() {
override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
val position = (recyclerView.layoutManager as LinearLayoutManager).findLastCompletelyVisibleItemPosition()
if (position + 1 == categoriesList.size) {
// END OF RECYCLERVIEW IS REACHED
} else {
// END OF RECYCLERVIEW IS NOT REACHED
}
}
})
Most of the answers are poorly constructed and have some issues. One of the common issues is if the user scrolls fast, the end reached block executes multiple times I've found a solution, where the end block runs just 1 single time.
override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
if (yourLayoutManager.findLastVisibleItemPosition() ==
yourLayoutManager.itemCount - 1 && !recyclerView.canScrollVertically(1)) {
Logger.log("End reached")
// Do your operations
}
super.onScrolled(recyclerView, dx, dy)
}
P.S. Sometimes if RecyclerView gets empty, the end listener might get called. As a solution, you can also add this check in the above code.
if (recyclerView.adapter?.itemCount!! > 0)
You can use this, if you put 1 thats will be indicated when you stay in end of list, if you want now when you stay in the start of the list you change 1 for -1
recyclerChat.addOnScrollListener(object : RecyclerView.OnScrollListener() {
override fun onScrollStateChanged(recyclerView: RecyclerView, newState: Int) {
super.onScrollStateChanged(recyclerView, newState)
if (!recyclerView.canScrollVertically(1)) {
}
}
})
This is my solution after reading all answers in this post.
I only want to show loading when the last item is shown at the end of the list and listview length is larger than screen height, meaning if there's only one or two items in the list, won't show the loading.
private var isLoadMoreLoading: Boolean = false
mRecyclerView.addOnScrollListener(object : RecyclerView.OnScrollListener() {
override fun onScrollStateChanged(recyclerView: RecyclerView, newState: Int) {
super.onScrollStateChanged(recyclerView, newState)
if (!isLoadMoreLoading) {
if (linearLayoutManager.findLastCompletelyVisibleItemPosition() == (list.size-1)) {
if (recyclerView.canScrollVertically(-1)) {
adapter?.addLoadMoreView()
loadMore()
}
}
}
}
})
private fun loadMore() {
isLoadMoreLoading = true
//call loadMore api
}
Because of this linearLayoutManager.findLastCompletelyVisibleItemPosition() == (list.size-1), we can know last item is shown, but we also need to know listview can scroll or not.
Therefore I added recyclerView.canScrollVertically(-1). Once you hit the bottom of the list, it cannot scroll down anymore. -1 means list can scroll up. That means listview length is larger than screen height.
This answer is in kotlin.
The most simple way to do it is in the adapter like this:
#Override
public void onBindViewHolder(HistoryMessageListAdapter.ItemViewHolder holder, int position) {
if (position == getItemCount()-1){
listener.onLastItemReached();
}
}
Because as soon as the last item is recycled the listener is triggered.
This is my solution:
val onScrollListener = object : RecyclerView.OnScrollListener() {
override fun onScrolled(recyclerView: RecyclerView?, dx: Int, dy: Int) {
directionDown = dy > 0
}
override fun onScrollStateChanged(recyclerView: RecyclerView, newState: Int) {
if (recyclerView.canScrollVertically(1).not()
&& state != State.UPDATING
&& newState == RecyclerView.SCROLL_STATE_IDLE
&& directionDown) {
state = State.UPDATING
// TODO do what you want when you reach bottom, direction
// is down and flag for non-duplicate execution
}
}
}
We can use Interface for get the position
Interface :
Create an Interface for listener
public interface OnTopReachListener { void onTopReached(int position);}
Activity :
mediaRecycleAdapter = new MediaRecycleAdapter(Class.this, taskList); recycle.setAdapter(mediaRecycleAdapter); mediaRecycleAdapter.setOnSchrollPostionListener(new OnTopReachListener() {
#Override
public void onTopReached(int position) {
Log.i("Position","onTopReached "+position);
}
});
Adapter :
public void setOnSchrollPostionListener(OnTopReachListener topReachListener) {
this.topReachListener = topReachListener;}#Override public void onBindViewHolder(MyViewHolder holder, int position) {if(position == 0) {
topReachListener.onTopReached(position);}}
Use this method after declaring and initializing your recyclerView with adapter
recyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
#Override
public void onScrollStateChanged(#NonNull RecyclerView recyclerView, int newState) {
super.onScrollStateChanged(recyclerView, newState);
}
#Override
public void onScrolled(#NonNull RecyclerView recyclerView, int dx, int dy) {
super.onScrolled(recyclerView, dx, dy);
if(dy > 0){ // Scrolling Down
//**Look at the condition inside if, this is how you can check, either you //have scrolled till last element of your recyclerView or not.**
//------------------------------------------------------------------------------
LinearLayoutManager linearLayoutManager = (LinearLayoutManager) recyclerView.getLayoutManager();
if (linearLayoutManager != null && linearLayoutManager.findLastCompletelyVisibleItemPosition() == recyclerViewList.size() - 1) {
//bottom of list!
}
//------------------------------------------------------------------------------
}else if(dy < 0){
// Scrolling Up
}
}
});
After a long search, I found the prefect solution, that only scrolls to bottom when you want to, and also maintains the smooth scroll behavior you get by using a ListAdapter:
adapter.registerAdapterDataObserver(object : RecyclerView.AdapterDataObserver() {
override fun onItemRangeInserted(positionStart: Int, itemCount: Int) {
super.onItemRangeInserted(positionStart, itemCount)
scrollToBottomIfUserIsAtTheBottom(linearLayoutManager, positionStart)
}
})
private fun scrollToBottomIfUserIsAtTheBottom(linearLayout: LinearLayoutManager, positionStart: Int) {
val messageCount = adapter.itemCount
val lastVisiblePosition = linearLayout.findLastCompletelyVisibleItemPosition()
if (lastVisiblePosition == -1 ||
(positionStart >= (messageCount - 1) &&
lastVisiblePosition == (positionStart - 1)))
{
recyclerView.scrollToPosition(positionStart)
}
}
A few notes:
If you use a list adapter and you add a new item to the list each update, you must create a new list each time for the smooth scroll to work.
Don't use smoothScrollToPosition with the ListAdapter! It ruins the ListAdapter "smoothier" behavior that happens when the diff works good and it detects that the change between old and new list is in a newly added item.
linearLayoutManager is adapter.layoutManager as LinearLayoutManager
Try this,
recyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
#Override
public void onScrollStateChanged(#NonNull RecyclerView recyclerView, int newState) {
super.onScrollStateChanged(recyclerView, newState);
}
#Override
public void onScrolled(#NonNull RecyclerView recyclerView, int dx, int dy) {
super.onScrolled(recyclerView, dx, dy);
int visibleItemCount = recyclerView.getLayoutManager().getChildCount();
int totalItemCount = recyclerView.getLayoutManager().getItemCount();
int firstVisibleItem = ((LinearLayoutManager) recyclerView.getLayoutManager()).findFirstVisibleItemPosition();
final int lastItem = firstVisibleItem + visibleItemCount;
if(lastItem == totalItemCount) {
if(previousLast != lastItem) {
previousLast = lastItem;
load();
}
}
}
});
Kotlin version of ScrollListener with the ability to set the indent from the last element to load
class MyScrollListener(
private val indentForAction: Int = DEFAULT_INDENT_TO_INVOKE_ACTION,
private val onEndReached: () -> Unit,
) : RecyclerView.OnScrollListener() {
private var currentListSize = 0
override fun onScrollStateChanged(recyclerView: RecyclerView, newState: Int) {
super.onScrollStateChanged(recyclerView, newState)
val linearLayoutManager = recyclerView.layoutManager as? LinearLayoutManager
?: error("ScrollListener works only with LinearLayoutManager")
val lastVisiblePosition = linearLayoutManager.findLastVisibleItemPosition()
val itemCount = linearLayoutManager.itemCount
if (lastVisiblePosition > itemCount - indentForAction && itemCount > 0) {
if (currentListSize != itemCount) {
currentListSize = itemCount
onEndReached.invoke()
}
}
}
private companion object {
const val DEFAULT_INDENT_TO_INVOKE_ACTION = 8
}
}

How to play video of video-view that is currently focus in recycler view

In my app there is a recycler-view having video-views, I want to auto-play the specific video view which is completely in focus when scrolled.
RecyclerView rv_featureList;
private View currentFocusedLayout, oldFocusedLayout;
#Override
public void onCreate(Bundle instanceState) {
rv_featuredList.addOnScrollListener(new RecyclerView.OnScrollListener() {
#Override
public void onScrollStateChanged(RecyclerView recyclerView,
int newState) {
super.onScrollStateChanged(recyclerView, newState);
if (newState == AbsListView.OnScrollListener.SCROLL_STATE_IDLE) {
//get the recyclerview position which is completely visible and first
int positionView = ((LinearLayoutManager) rv_featuredList.getLayoutManager()).findFirstCompletelyVisibleItemPosition();
Log.i("VISISBLE", positionView + "");
if (positionView >= 0) {
if (oldFocusedLayout != null) {
//Stop the previous video playback after new scroll
VideoView vv_dashboard = (VideoView) oldFocusedLayout.findViewById(R.id.vv_dashboard);
vv_dashboard.stopPlayback();
}
currentFocusedLayout = ((LinearLayoutManager) rv_featuredList.getLayoutManager()).findViewByPosition(positionView);
VideoView vv_dashboard = (VideoView) currentFocusedLayout.findViewById(R.id.vv_dashboard);
//to play video of selected recylerview, videosData is an array-list which is send to recyclerview adapter to fill the view. Here we getting that specific video which is displayed through recyclerview.
playVideo(videosData.get(positionView));
oldFocusedLayout = currentFocusedLayout;
}
}
}
});
}
Answer inspired from https://stackoverflow.com/a/45281307/9752209
Callback to get center item position when scrolling stopped
import android.content.Context
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
class RecyclerCenterItemFinder(
private val context: Context,
private val layoutManager: LinearLayoutManager,
private val callback: (Int) -> Unit,
private val controlState: Int = RecyclerView.SCROLL_STATE_IDLE
) :
RecyclerView.OnScrollListener() {
override fun onScrollStateChanged(recyclerView: RecyclerView, newState: Int) {
if (controlState == ALL_STATES || newState == controlState) {
val firstVisible = layoutManager.findFirstVisibleItemPosition()
val lastVisible = layoutManager.findLastVisibleItemPosition()
val itemsCount = lastVisible - firstVisible + 1
val screenCenter: Int = context.resources.displayMetrics.widthPixels / 2
var minCenterOffset = Int.MAX_VALUE
var middleItemIndex = 0
for (index in 0 until itemsCount) {
val listItem = layoutManager.getChildAt(index) ?: return
val topOffset = listItem.top
val bottomOffset = listItem.bottom
val centerOffset =
Math.abs(topOffset - screenCenter) + Math.abs(bottomOffset - screenCenter)
if (minCenterOffset > centerOffset) {
minCenterOffset = centerOffset
middleItemIndex = index + firstVisible
}
}
callback(middleItemIndex)
}
}
companion object {
const val ALL_STATES = 10
}
}
recycler.addOnScrollListener(RecyclerCenterItemFinder(requireContext(),
recycler.layoutManager,
{ centerItemPosition ->
playVideoAtPosition(centerItemPosition)
}
))

Categories

Resources