Tried all the related questions but it did not work :(
I am building endless scrolling with Recycler View.
PROBLEM: onScrolled method is always called without the user scrolling the screen.
Android Documentation Guide describes this for onScrolled method:
Case 1) Callback method to be invoked when the RecyclerView has been scrolled. This will be called after the scroll has completed.
Case 2) This callback will also be called if visible item range changes after a layout calculation. In that case, dx and dy will be 0.
My onScrolled method is being called without the user scrolling because of Case 2. I logged dy in the screen and it turns out that onScrolled is being called with dy=0. This happens in the second case.
If I use:
if (dy > 0){
// The user is scrolling down
}
It turns out that my method onScrolled is never called, no matter how many times I scroll the Recycler View.
RecyclerAdapterSmallVideos.java
public class RecyclerAdapterSmallVideos extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
private List<VideosBigSize> videosBigSizeList;
private Context mContext;
final int VIEW_TYPE_ITEM = 0, VIEW_TYPE_LOADING = 1;
ILoadMore loadMore;
boolean isLoading;
int visibleThreshold = 5;
int lastVisibleItem, totalItemCount;
public RecyclerAdapterSmallVideos(RecyclerView recyclerView, Context context, List<VideosBigSize> videosBigSizeList) {
this.videosBigSizeList = videosBigSizeList;
this.mContext = context;
final LinearLayoutManager linearLayoutManager = (LinearLayoutManager) recyclerView.getLayoutManager();
recyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
@Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
super.onScrolled(recyclerView, dx, dy);
totalItemCount = linearLayoutManager.getItemCount();
lastVisibleItem = linearLayoutManager.findLastVisibleItemPosition();
if (!isLoading && totalItemCount <= (lastVisibleItem + visibleThreshold)) {
if (loadMore != null) {
// This is running without the user scrolling the Recycler View.
loadMore.onLoadMore();
}
isLoading = true;
}
}
});
}
@Override
public int getItemViewType(int position) {
return videosBigSizeList.get(position) == null ? VIEW_TYPE_LOADING: VIEW_TYPE_ITEM;
}
public void setLoadMore(ILoadMore loadMore) {
this.loadMore = loadMore;
}
@NonNull
@Override
public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
if (viewType == VIEW_TYPE_ITEM) {
View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_small_video, parent, false);
return new ViewHolderSmallVideos(view);
} else if (viewType == VIEW_TYPE_LOADING) {
View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_loading, parent, false);
return new ViewHolderLoading(view);
}
return null;
}
@Override
public void onBindViewHolder(@NonNull final RecyclerView.ViewHolder holder, final int position) {
/*########### Case 1) The holder is a instance off ViewHolderSmallVideos (Items) ###########*/
if (holder instanceof ViewHolderSmallVideos) {
Log.i("Debugging", "onBindViewHolder: Binding instance of ViewHolder SmallVideos: " + holder.getAdapterPosition());
VideosBigSize singleVideoBigSize = videosBigSizeList.get(position);
ViewHolderSmallVideos viewHolderSmallVideos = (ViewHolderSmallVideos) holder;
// Step 1) Images
GlideApp.with(viewHolderSmallVideos.thumbNail.getContext())
.load(singleVideoBigSize.getThumnail_url())
.centerCrop()
.into(viewHolderSmallVideos.thumbNail); // Video Thumbnail
// Step 2) Determine the Max Length for each TextView
setTextMaxLength(viewHolderSmallVideos.videoTitle, viewHolderSmallVideos.channelName, mContext);
// Step 3) Texts
viewHolderSmallVideos.videoTitle.setText(singleVideoBigSize.getVideo_title());
viewHolderSmallVideos.channelName.setText(singleVideoBigSize.getChannel_name());
viewHolderSmallVideos.videoViews.setText(singleVideoBigSize.getVideo_views());
viewHolderSmallVideos.videoUploadDate.setText(singleVideoBigSize.getVideo_upload_date());
viewHolderSmallVideos.videoDuration.setText(singleVideoBigSize.getVideo_duration());
// Step 4) Handle clicks
viewHolderSmallVideos.constraintLayoutEachItem.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Toast.makeText(mContext, String.valueOf(holder.getAdapterPosition()), Toast.LENGTH_SHORT).show();
}
});
}
/*########### Case 2) The holder is a instance off ViewHolderLoading (progressBar) ###########*/
else if (holder instanceof ViewHolderLoading) {
Log.i("Debugging", "onBindViewHolder: Binding instance of ViewHolder Loading: " + holder.getAdapterPosition());
ViewHolderLoading viewHolderLoading = (ViewHolderLoading) holder;
viewHolderLoading.progressBar.setIndeterminate(true);
}
}
/**
* Get the number of items in the adapter
* @return the number of items in the adapter
*/
@Override
public int getItemCount() { return videosBigSizeList.size(); }
public void setLoaded() {
isLoading = false;
}
/**
* Determine a maximum number of characters for the Title and Channel Name
* If they are too big and the width of the screen is too small, the text
* will get bigger than the thumbnail and then the layout will be disturbed.
* @param videoTitle the title of the video
* @param channelName the name of the channel who uploaded the video
* @param context the activity context
*/
private void setTextMaxLength(TextView videoTitle, TextView channelName, Context context) {
DisplayMetrics displayMetrics = new DisplayMetrics();
WindowManager windowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
if (windowManager != null) {
// Get the current width of the screen in DP
windowManager.getDefaultDisplay().getMetrics(displayMetrics);
float dpWidth = displayMetrics.widthPixels / displayMetrics.density;
// 1) If the device is 420dp or less;
// 2) Limit the number of characters for the title and channel name
if (dpWidth <= 420) {
videoTitle.setFilters(new InputFilter[]{new InputFilter.LengthFilter(35)});
channelName.setFilters(new InputFilter[]{new InputFilter.LengthFilter(25)});
}
}
}
}
ProfileFragment.java
private void setupRecyclerView(final List<VideosBigSize> userAllVideosList) {
LinearLayoutManager linearLayoutManager = new LinearLayoutManager(mContext);
mRecyclerView.setLayoutManager(linearLayoutManager);
mRecyclerView.setNestedScrollingEnabled(false);
final RecyclerAdapterSmallVideos adapterSmallVideos = new RecyclerAdapterSmallVideos(mRecyclerView, mContext, userAllVideosList);
mRecyclerView.setAdapter(adapterSmallVideos);
// Set Load more event
adapterSmallVideos.setLoadMore(new ILoadMore() {
@Override
public void onLoadMore() {
if (userAllVideosList.size() <= 50) {
mRecyclerView.post(new Runnable() {
@Override
public void run() {
userAllVideosList.add(null);
adapterSmallVideos.notifyItemInserted(userAllVideosList.size() - 1);
}
});
new Handler().postDelayed(new Runnable() {
@Override
public void run() {
userAllVideosList.remove(userAllVideosList.size() - 1);
adapterSmallVideos.notifyItemRemoved(userAllVideosList.size());
// Random more data
int index = userAllVideosList.size();
int end = index + 10;
for (int i = index; i < end; i++) {
// Add more data to userAllVideosList...
adapterSmallVideos.notifyItemInserted(i);
}
adapterSmallVideos.setLoaded();
}
}, 2000);
}
// Loaded everything already
else {
Toast.makeText(mContext, "Load data completed!", Toast.LENGTH_SHORT).show();
}
}
});
}
My ProfileFragment Layout
fragment_profile.xml
It is important to notice that:
1) My Recycler View is inside NesteScrollView because I need to hide/show the toolbar. Remember I setted mRecyclerView.setNestedScrollingEnabled(false) because it triggers the NestedScrollView;
2) There are more things in my layout, beside the recycler view.
You should use Paging Library to do that, click to see the best tutorial:
And the Github source code of the tutorial
It turns out that because paging library is a new feature, I was only finding Kotlin tutorials. Then I found this one that probably is the best one written in Java. Really thanks for the author, I struggled with this a whole month.