I'm trying to get data from bottom to top (from the last item uploaded to the first item uploaded like Instagram) but I couldn't make this. I searched a lot and found this answer here.
I made it with firebaseRecyclerAdapter and worked fine but with custom Adapter I couldn't do it!
here is the method I used:
#Override
public ItemsRecyclerView getItem(int pos){
return super.getItem(getCount() - 1 - pos);
}
What you should be doing is reversing the recyclerview, forget about the method that you used above.
And do this when you set your recycler view:
LinearLayoutManager manager = new LinearLayoutManager(context);
manager.setReverseLayout(true);
manager.setStackFromEnd(true);
recycler_View.setLayoutManager(manager);
Possible solution:
try this:
Maybe try to reverse the list that you pass to your adapter like this:
//your list
List<model> your_list = new ArrayList()<>;
//before you pass it to the adapter do this
Collections.reverse(your_list);
//now your list is reversed, pass it to the adapter
Adapter adapter = new Adapter (your_list,.....,....);
Description: I am working on one demo with the functionality that user can select an option from the item of horizontal Recyclerview. In this, I got stuck in below queries.
Queries:
1: How to make Recyclerview cyclic?
For example, I have 10 items named 1,2,3,4,5,6,7,8,9,10. Now if user scrolls the recyclerview then he/she will be able to scroll it endlessly. i.e 1,2,3,4,5...9,10,1,2...9,10,1,2,3..9,10 like this.
To achieve this I have used this answer. Somehow it worked but only in forward direction.
2: How to give Snap center to the particular item which is selected by the user?
For example: As shown in the image, if the user clicks on '15' then that item will come to the center. Same as if the user clicks on '07' then it should come to the center position.
I have implemented this demo. But it doesn't work on click on the item.
The code which I have done so far is as below:
Layout Manager:
final CenterZoomLayoutManager mLayoutManager = new CenterZoomLayoutManager(mContext,
CenterZoomLayoutManager.HORIZONTAL, false);// To get centered item zoom
recyclerview.setLayoutManager(mLayoutManager);
Adapter object:
mAdapter = new CustomAdapter(mContext, arrayList, new RecyclerViewClickListener() {
#Override
public void recyclerViewListClicked(View v, int position) {
}
});
recyclerview.setAdapter(mAdapter);
Any help is appreciated!
Thanks.
CarouselView may fulfill your requirements. Please check following GitHub links for the same.
https://android-arsenal.com/details/1/1481
https://github.com/nicklockwood/iCarousel
https://www.pocketmagic.net/a-3d-carousel-view-for-android/
https://github.com/clkasd/CarouselViewProject
How do I create a circular (endless) RecyclerView?
There is no way of making it infinite, but there is a way to make it
look like infinite.
in your adapter override getCount() to return something big like
Integer.MAX_VALUE:
#Override
public int getCount() {
return Integer.MAX_VALUE;
}
in getItem() and getView() modulo divide (%) position by real item
number:
#Override
public Fragment getItem(int position) {
int positionInList = position % fragmentList.size();
return fragmentList.get(positionInList);
}
at the end, set current item to something in the middle (or else, it
would be endless only in downward direction).
// scroll to middle item
recyclerView.getLayoutManager().scrollToPosition(Integer.MAX_VALUE / 2);
For snapping
use SnapHelper or scrollToPositionWithOffset, where offset is (width/2 - itemWidth/2)
I can use this library
You can solve your problem by personalizing this library. By selecting each item, you can use the ScrollToX function to navigate to that item.
https://github.com/mahdimoaz/RecyclerViewHorizontalCenterSelected
I have a recyclerview with several items. I need to show the items starting from bottom i.e. first item should be displayed at bottom and after scrolling from bottom to top, further items will get displayed.
For this what I tried is,
linearlayoutManager.setStackFromEnd(true);
This helped to start the items from bottom. But when there are several items, more than the size to fit into the screen at the same time then the first item goes moving at the top as the item count increases.
I also tried with
linearlayoutManager.scrollToPosition(0);
This didn't change anything.
Here is an image showing what I am looking for.
There are few more items in the list above which are still not visible.
Here how it looks When these items are scrolled,
.
I have checked view hierarchy of this view in the image which only shows a recyclerview and the items within it. And the recyclerview's height is match_parent i.e. the top space with no items showing another view underneath of the recyclerview which is a google map.
I just need to know how I can achieve this feature of the recyclerview without using more extra views.
Any help would be greatly appreciated..!!
Try to init your Linear layout as follow
RecyclerView.LayoutManager layoutManager = new LinearLayoutManager(getContext(), LinearLayoutManager.VERTICAL, true);
The third boolean indicates that it should reverse the items or not.
But remember that your datas should be sorted from newest to oldest also.
LinearLayoutManager linearLayoutManager = new LinearLayoutManager(context, LinearLayoutManager.VERTICAL, true);
linearLayoutManager.setStackFromEnd(true);
linearLayoutManager.setReverseLayout(true);
Then once your data is populated
adapter.notifyDataSetChanged();
recyclerView.post(new Runnable() {
#Override
public void run() {
recyclerView.smoothScrollToPosition(0);
}
});
Try using a reversed LinearLayoutManager:
LinearLayoutManager linearlayoutManager = new LinearLayoutManager(context, VERTICAL, true);
write this line in xml to start list from bottom
android:stackFromBottom="true"
<android.support.v7.widget.RecyclerView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:stackFromBottom="true">
</android.support.v7.widget.RecyclerView>
and second thing reverse your array list before add in adapter
Add these attributes to your recyclerview (XML)
android:paddingTop="500dp"
android:clipToPadding="false"
You can also set it in the code like this:
recyclerView.setPadding(0, 500, 0, 0);
recyclerView.setClipToPadding(false);
This is for different screen sizes
int height = recyclerView.getHeight()
recyclerView.setPadding(0, height - 200, 0, 0);
recyclerView.setClipToPadding(false);
If you don't know how to get screen width, height and convert dp to px
so here are the links: How to get screen width and height and Converting pixels to dp
Seems like what you want is to have a separate view type in your recycler adapter that doesn't show anything but is the height of the map. This would allow you to 'scroll up' from the bottom where the list starts and continue seeing different views.
A simple example:
public class ScreenBottomRecyclerAdapter extends RecyclerView.Adapter {
final int VIEW_TYPE_MAP = 0;
final int VIEW_TYPE_ROW = 1;
#Override
public int getItemCount() {
return dataset.length + 1;
}
#Override
public int getItemViewType(int position) {
if (position == 0) {
return VIEW_TYPE_MAP;
} else {
return VIEW_TYPE_ROW;
}
}
#Override
public RecyclerView.ViewHolder createViewHolder(ViewGroup parent, int viewType) {
switch (viewType) {
case VIEW_TYPE_MAP: return new ViewHolder(null); // This is an empty recycler row so you can see and interact with the map, it will need to be the height of the map so you'll probably want to make a new view type that the same height but doesn't show anything and doesn't intercept touches
case VIEW_TYPE_ROW: return new RowViewHolder(someInflatedView); // This will be the rows you are already creating
}
}
#Override
public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
switch (getItemViewType(position)) {
case VIEW_TYPE_ROW: holder.update(position - 1) // Do your normal update here, but remember that your data will be off by 1 for the map view row in the recycler
}
}
}
Use below code to reverse list:
LinearLayoutManager layoutManager = new LinearLayoutManager(getActivity(), RecyclerView.VERTICAL, false);
Important: Set layout manager which have property reverse layout to true
layoutManager.setReverseLayout(true);
RecyclerView.setLayoutManager(layoutManager);
This code will display reverse list which you may have assigned to RecyclerView
I use smoothScrollToPosition to scroll RecyclerView. It does scroll every time a new entry is inserted; but to the top, not to the bottom, which is the direction i want.
list_chat = (RecyclerView) findViewById(R.id.list_chat);
//Set up Layout Manager
linearLayoutManager = new LinearLayoutManager(this);
linearLayoutManager.setStackFromEnd(true);
list_chat.setLayoutManager(linearLayoutManager);
//set adapter
list_chat.setAdapter(adapter);
//set scroll
list_chat.post(new Runnable() {
#Override
public void run() {
list_chat.smoothScrollToPosition(adapter.getItemCount());
}
});
The adapter is from Firebase
adapter = new FirebaseRecyclerAdapter<ChatItem, ChatRecylerViewHolder>(ChatItem.class,R.layout.chat_item
,ChatRecylerViewHolder.class,queryChat ) {
#Override
protected void populateViewHolder(ChatRecylerViewHolder viewHolder, ChatItem model, int position) {
viewHolder.tvAuthorChat.setText(model.chatAuthor);
viewHolder.tvContentChat.setText(model.chatContent);
}
};
You do notice you are using linearLayoutManager.setStackFromEnd(true); this mean you first position is at the bottom. I suggest you a better option.
RecycleView didn't work the way listView work, you can scroll it with your layout manager something like this
linearLayoutManager.scrollToPositionWithOffset(position,offset);
Which position is the position you want to scroll to, offset is the offset within the current position. You could just use with one parameter as well.
linearLayoutManager.scrollToPosition(position);
Ok. I found the answer.
First, the old problem with my question: i thought list_chat.post is called whenever an item is inserted (turn out that is wrong). The reason for it keeps scrolling top is linearLayoutManager.setStackFromEnd(true);
Thus, the question comes down to Where to call the scrolling ?
The answer is : Since adapter manages data, it makes sense to guess that adapter will notify the insertion.
Here is the code
adapter.registerAdapterDataObserver(new RecyclerView.AdapterDataObserver() {
#Override
public void onItemRangeInserted(int positionStart, int itemCount) {
super.onItemRangeInserted(positionStart, itemCount);
list_chat.smoothScrollToPosition(adapter.getItemCount());
}
});
How does one refresh the data displayed in RecyclerView (calling notifyDataSetChanged on its adapter) and make sure that the scroll position is reset to exactly where it was?
In case of good ol' ListView all it takes is retrieving getChildAt(0), checking its getTop() and calling setSelectionFromTop with the same exact data afterwards.
It doesn't seem to be possible in case of RecyclerView.
I guess I'm supposed to use its LayoutManager which indeed provides scrollToPositionWithOffset(int position, int offset), but what's the proper way to retrieve the position and the offset?
layoutManager.findFirstVisibleItemPosition() and layoutManager.getChildAt(0).getTop()?
Or is there a more elegant way to get the job done?
I use this one.^_^
// Save state
private Parcelable recyclerViewState;
recyclerViewState = recyclerView.getLayoutManager().onSaveInstanceState();
// Restore state
recyclerView.getLayoutManager().onRestoreInstanceState(recyclerViewState);
It is simpler, hope it will help you!
I have quite similar problem. And I came up with following solution.
Using notifyDataSetChanged is a bad idea. You should be more specific, then RecyclerView will save scroll state for you.
For example, if you only need to refresh, or in other words, you want each view to be rebinded, just do this:
adapter.notifyItemRangeChanged(0, adapter.getItemCount());
EDIT: To restore the exact same apparent position, as in, make it look exactly like it did, we need to do something a bit different (See below how to restore the exact scrollY value):
Save the position and offset like this:
LinearLayoutManager manager = (LinearLayoutManager) mRecycler.getLayoutManager();
int firstItem = manager.findFirstVisibleItemPosition();
View firstItemView = manager.findViewByPosition(firstItem);
float topOffset = firstItemView.getTop();
outState.putInt(ARGS_SCROLL_POS, firstItem);
outState.putFloat(ARGS_SCROLL_OFFSET, topOffset);
And then restore the scroll like this:
LinearLayoutManager manager = (LinearLayoutManager) mRecycler.getLayoutManager();
manager.scrollToPositionWithOffset(mStatePos, (int) mStateOffset);
This restores the list to its exact apparent position. Apparent because it will look the same to the user, but it will not have the same scrollY value (because of possible differences in landscape/portrait layout dimensions).
Note that this only works with LinearLayoutManager.
--- Below how to restore the exact scrollY, which will likely make the list look different ---
Apply an OnScrollListener like so:
private int mScrollY;
private RecyclerView.OnScrollListener mTotalScrollListener = new RecyclerView.OnScrollListener() {
#Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
super.onScrolled(recyclerView, dx, dy);
mScrollY += dy;
}
};
This will store the exact scroll position at all times in mScrollY.
Store this variable in your Bundle, and restore it in state restoration to a different variable, we'll call it mStateScrollY.
After state restoration and after your RecyclerView has reset all its data reset the scroll with this:
mRecyclerView.scrollBy(0, mStateScrollY);
That's it.
Beware, that you restore the scroll to a different variable, this is important, because the OnScrollListener will be called with .scrollBy() and subsequently will set mScrollY to the value stored in mStateScrollY. If you do not do this mScrollY will have double the scroll value (because the OnScrollListener works with deltas, not absolute scrolls).
State saving in activities can be achieved like this:
#Override
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
outState.putInt(ARGS_SCROLL_Y, mScrollY);
}
And to restore call this in your onCreate():
if(savedState != null){
mStateScrollY = savedState.getInt(ARGS_SCROLL_Y, 0);
}
State saving in fragments works in a similar way, but the actual state saving needs a bit of extra work, but there are plenty of articles dealing with that, so you shouldn't have a problem finding out how, the principles of saving the scrollY and restoring it remain the same.
Keep scroll position by using #DawnYu answer to wrap notifyDataSetChanged() like this:
val recyclerViewState = recyclerView.layoutManager?.onSaveInstanceState()
adapter.notifyDataSetChanged()
recyclerView.layoutManager?.onRestoreInstanceState(recyclerViewState)
Yes you can resolve this issue by making the adapter constructor only one time, I am explaining the coding part here :
if (appointmentListAdapter == null) {
appointmentListAdapter = new AppointmentListAdapter(AppointmentsActivity.this);
appointmentListAdapter.addAppointmentListData(appointmentList);
appointmentListAdapter.setOnStatusChangeListener(onStatusChangeListener);
appointmentRecyclerView.setAdapter(appointmentListAdapter);
} else {
appointmentListAdapter.addAppointmentListData(appointmentList);
appointmentListAdapter.notifyDataSetChanged();
}
Now you can see I have checked the adapter is null or not and only initialize when it is null.
If adapter is not null then I am assured that I have initialized my adapter at least one time.
So I will just add list to adapter and call notifydatasetchanged.
RecyclerView always holds the last position scrolled, therefore you don't have to store last position, just call notifydatasetchanged, recycler view always refresh data without going to top.
Thanks
Happy Coding
The top answer by #DawnYu works, but the recyclerview will first scroll to the top, then go back to the intended scroll position causing a "flicker like" reaction which isn't pleasant.
To refresh the recyclerView, especially after coming from another activity, without flickering, and maintaining the scroll position, you need to do the following.
Ensure you are updating you recycler view using DiffUtil. Read more about that here: https://www.journaldev.com/20873/android-recyclerview-diffutil
Onresume of your activity, or at the point you want to update your activity, load data to your recyclerview. Using the diffUtil, only the updates will be made on the recyclerview while maintaining it position.
Hope this helps.
Here is an option for people who use DataBinding for RecyclerView.
I have var recyclerViewState: Parcelable? in my adapter. And I use a BindingAdapter with a variation of #DawnYu's answer to set and update data in the RecyclerView:
#BindingAdapter("items")
fun setRecyclerViewItems(
recyclerView: RecyclerView,
items: List<RecyclerViewItem>?
) {
var adapter = (recyclerView.adapter as? RecyclerViewAdapter)
if (adapter == null) {
adapter = RecyclerViewAdapter()
recyclerView.adapter = adapter
}
adapter.recyclerViewState = recyclerView.layoutManager?.onSaveInstanceState()
// the main idea is in this call with a lambda. It allows to avoid blinking on data update
adapter.submitList(items.orEmpty()) {
adapter.recyclerViewState?.let {
recyclerView.layoutManager?.onRestoreInstanceState(it)
}
}
}
Finally, the XML part looks like:
<androidx.recyclerview.widget.RecyclerView
android:id="#+id/possible_trips_rv"
android:layout_width="match_parent"
android:layout_height="0dp"
app:items="#{viewState.yourItems}"
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"/>
I was making a mistake like this, maybe it will help someone :)
If you use recyclerView.setAdapter every time new data come, it calls the adapter clear() method every time you use it, which causes the recyclerview to refresh and start over. To get rid of this, you need to use adapter.notiftyDatasetChanced().
1- You need to save scroll position like this
rvProduct.addOnScrollListener(new RecyclerView.OnScrollListener() {
#Override
public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
super.onScrollStateChanged(recyclerView, newState);
recyclerViewState = rvProduct.getLayoutManager().onSaveInstanceState(); // save recycleView state
}
});
2- And after you call notifyDataSetChanged then onRestoreInstanceState like this example
productsByBrandAdapter.addData(productCompareList);
productsByBrandAdapter.notifyDataSetChanged();
rvProduct.getLayoutManager().onRestoreInstanceState(recyclerViewState); // restore recycleView state
I have not used Recyclerview but I did it on ListView. Sample code in Recyclerview:
setOnScrollListener(new RecyclerView.OnScrollListener() {
#Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
rowPos = mLayoutManager.findFirstVisibleItemPosition();
It is the listener when user is scrolling. The performance overhead is not significant. And the first visible position is accurate this way.
Create Extention and use it entirely your App, if you are using DiffUtil you don't need to add adapter.notifyDataSetChanged()
fun RecyclerView.reStoreState(){
val recyclerViewState = this.layoutManager?.onSaveInstanceState()
this.layoutManager?.onRestoreInstanceState(recyclerViewState)
}
Then use it like this below
yourRecyclerView.reStoreState()
adapter.submitList(yourData)
yourRecyclerView.adapter = adapter
#BindingAdapter("items")
fun <T> RecyclerView.setItems(items: List<T>?) {
(adapter as? ListAdapter<T, *>)?.submitList(items) {
layoutManager?.onSaveInstanceState().let {
layoutManager?.onRestoreInstanceState(it)
}
}
}
mMessageAdapter.registerAdapterDataObserver(new RecyclerView.AdapterDataObserver() {
#Override
public void onChanged() {
mLayoutManager.smoothScrollToPosition(mMessageRecycler, null, mMessageAdapter.getItemCount());
}
});
The solution here is to keep on scrolling recyclerview when new message comes.
The onChanged() method detects the action performed on recyclerview.
That's working for me in Kotlin.
Create the Adapter and hand over your data in the constructor
class LEDRecyclerAdapter (var currentPole: Pole): RecyclerView.Adapter<RecyclerView.ViewHolder>() { ... }
change this property and call notifyDataSetChanged()
adapter.currentPole = pole
adapter.notifyDataSetChanged()
The scroll offset doesn't change.
If you have one or more EditTexts inside of a recyclerview items, disable the autofocus of these, putting this configuration in the parent view of recyclerview:
android:focusable="true"
android:focusableInTouchMode="true"
I had this issue when I started another activity launched from a recyclerview item, when I came back and set an update of one field in one item with notifyItemChanged(position) the scroll of RV moves, and my conclusion was that, the autofocus of EditText Items, the code above solved my issue.
best.
Just return if the oldPosition and position is same;
private int oldPosition = -1;
public void notifyItemSetChanged(int position, boolean hasDownloaded) {
if (oldPosition == position) {
return;
}
oldPosition = position;
RLog.d(TAG, " notifyItemSetChanged :: " + position);
DBMessageModel m = mMessages.get(position);
m.setVideoHasDownloaded(hasDownloaded);
notifyItemChanged(position, m);
}
I had this problem with a list of items which each had a time in minutes until they were 'due' and needed updating. I'd update the data and then after, call
orderAdapter.notifyDataSetChanged();
and it'd scroll to the top every time. I replaced that with
for(int i = 0; i < orderArrayList.size(); i++){
orderAdapter.notifyItemChanged(i);
}
and it was fine. None of the other methods in this thread worked for me. In using this method though, it made each individual item flash when it was updated so I also had to put this in the parent fragment's onCreateView
RecyclerView.ItemAnimator animator = orderRecycler.getItemAnimator();
if (animator instanceof SimpleItemAnimator) {
((SimpleItemAnimator) animator).setSupportsChangeAnimations(false);
}