I'm trying to scroll recycler view item to top of screen when user clicks on an item.
My layout is set as following
- <FrameLayout>
-- <ScrollView>
--- <LinearLayout /> // static content
--- <RecyclerView /> // dynamic content
--- <LinearLayout /> // static content
-- </ScrollView>
-- </FrameLayout>
What I want to achieve is when i click on an item of recyclerview. It should scroll up to top of the screen.
I have tried following so far but no luck.
->
// use recycler view's layout manager to scroll.
recyclerView.layoutManager.scrollToPositionWithOffset(position)
->
// use smooth scroll to scroll
recyclerView.smoothScrollToPosition(ItemPos)
->
// use main scroll view to scroll on the screen. This kind of works but does not move item to top of the page.
val y = recyclerView.y + recyclerView.getChildAt(position).y
activity.binding.mainScrollview?.smoothScrollTo(0, y.toInt())
Any help is appreciated. Thank you
Looks like in your case you don't have enough items.
You can't get recyclerView scroll lower than the last Item So it's either you make your own implementation of recyclerView by extending it,get add scroll amounts and when got to the last item you'll add up the translationY of the recyclerView.
Or you can add a few dummy Items.So they can be View with width and height of your normal Items.That should do it.
Have you tried layoutManager's scrollToPositionWithOffset function:
thirdItem.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
LinearLayoutManager layoutManager = (LinearLayoutManager) mRecyclerView
.getLayoutManager();
layoutManager.scrollToPositionWithOffset(0, 0);
}
});
i have a RecycleView with an adapter that show a list of servers
and the user must select one server.
when i call notifyItemChanged(previousPosition) inside the onClick() method
to make the old server unselected and the new server selected,
that's make the RecycleView list jump to up exactly in the middle of list.
and this problem happen just when i click on one of the last 2 or 3 servers inside the RecycleView list
here is the code of my RecyclerView.Adapter :
public class ServerAdapter extends RecyclerView.Adapter<ServerAdapter.ServerViewHolder> {
private List<Server> listServers = new ArrayList<>();
private int[] icons = new int[]{R.drawable.server1,R.drawable.server2,R.drawable.server3,R.drawable.server4,R.drawable.server5,R.drawable.server6,R.drawable.offline};
private int selected = 0;
private int previousSelected = 0;
public ServerAdapter(List<Server> listServers){
this.listServers = listServers;
}
#Override
public ServerViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.server_relative_layout,parent,false);
return new ServerViewHolder(view);
}
#Override
public void onBindViewHolder(final ServerViewHolder holder, final int position) {
if(position == selected){
holder.getBackground().setSelected(true);
}else{
holder.getBackground().setSelected(false);
}
holder.getBackground().setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
if(position != selected){
previousSelected = selected;
selected = position;
holder.getBackground().setSelected(true);
notifyItemChanged(previousSelected);
}
}
});
holder.getImageServer().setImageResource(icons[position%6]);
holder.getTextNameServer().setText(listServers.get(position).getName());
holder.getTextConnected().setText(listServers.get(position).getUrl());
}
#Override
public int getItemCount() {
return listServers.size();
}
public class ServerViewHolder extends RecyclerView.ViewHolder{
private ImageView imageServer;
private TextView textNameServer;
private TextView textConnected;
private View background;
public ServerViewHolder(View itemView) {
super(itemView);
imageServer = (ImageView)itemView.findViewById(R.id.imageServer);
textNameServer = (TextView)itemView.findViewById(R.id.textNameServer);
textConnected = (TextView)itemView.findViewById(R.id.textConnected);
background = itemView;
}
public ImageView getImageServer() {
return imageServer;
}
public TextView getTextConnected() {
return textConnected;
}
public TextView getTextNameServer() {
return textNameServer;
}
public View getBackground() {
return background;
}
}
}
any solutions to solve this problem ? thanks.
The problem happened exactly when i specify the layout height and do not let it to wrap_content
<android.support.v7.widget.RecyclerView
android:layout_width="wrap_content"
android:layout_height="400dp"
android:id="#+id/serverRecyclerView"
android:layout_margin="10dp"
/>
or when i put it below something for expample like that :
<android.support.v7.widget.RecyclerView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="#+id/serverRecyclerView"
android:layout_margin="10dp"
android:layout_below="#+id/image"/>
my code exactly is :
<android.support.v7.widget.RecyclerView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="#+id/serverRecyclerView"
android:layout_margin="10dp"
android:layout_alignTop="#+id/imageBall"
android:layout_alignParentRight="true"
android:layout_alignParentEnd="true"
android:layout_toRightOf="#+id/camera"
android:layout_toEndOf="#+id/camera"/>
Looks like this is a bug: https://code.google.com/p/android/issues/detail?id=203574
The best workaround seems to be Bart's answer to set the RecyclerView's LinearLayoutManager's AutoMeasure property to false.
LinearLayoutManager llm = new LinearLayoutManager(context);
llm.setAutoMeasureEnabled(false);
recyclerView.setLayoutManager(llm);
The set FixedSize to true solution had way too many side-effects...
RecyclerView.setHasFixedSize(true)
I don't know why, but I used:
RecyclerView.setHasFixedSize(true)
This worked for me. I hope it can help.
android:descendantFocusability="blocksDescendants"
android:descendantFocusability="blocksDescendants"
this attr solve my bug
RecyclerView.ItemAnimator animator = myRecyclerListView.getItemAnimator();
if (animator instanceof SimpleItemAnimator) {
((SimpleItemAnimator)animator).setSupportsChangeAnimations(false);
}
My RecyclerView was inside ConstraintLayout, and I also had such problem and calling setAutoMeasureEnabled(false) of RecyclerView's LayoutManager did not fix the issue for me, furthermore this method is deprecated in 28.0.0 version. What I did is that, I wrapped my RecyclerView with RelativeLayout and now it works like a charm. As mentioned in bugtracker, this "issue" is intented behaviour in LinearLayout and is not going to be fixed. So if it is possible, just wrap your RecyclerView something like this:
<RelativeLayout
android:id="#+id/container_messages_list"
android:layout_width="match_parent"
android:layout_height="0dp"
android:background="#drawable/chat_back_pattern"
app:layout_constraintBottom_toTopOf="#+id/bottom_view"
app:layout_constraintTop_toBottomOf="#+id/toolbar">
<android.support.v7.widget.RecyclerView
android:id="#+id/messages_list"
android:layout_width="match_parent"
android:layout_height="match_parent">
</android.support.v7.widget.RecyclerView>
</RelativeLayout>
for anyone who stumbles upon this issue, try using
yourRecyclerView.notifyItemChanged(int position, Object payload);
This one did the trick for me.
Using
setAutoMeasureEnabled(false);
also worked but in some edge cases recycler view was acting weird. Good luck!
RecyclerView can perform several optimizations if it can know in advance that RecyclerView's size is not affected by the adapter contents. RecyclerView can still change its size based on other factors (e.g. its parent's size) but this size calculation cannot depend on the size of its children or contents of its adapter (except the number of items in the adapter).
If your use of RecyclerView falls into this category, set this to true. It will allow RecyclerView to avoid invalidating the whole layout when its adapter contents change.
If we have a RecyclerView with match_parent as height/width, we should add setHasFixedSize(true) since the size of the RecyclerView itself does not change inserting or deleting items into it.
setHasFixedSize should be false if we have a RecyclerView with wrap_content as height/width because each element inserted by the adapter could change the size of the RecyclerView depending on the items inserted/deleted, so, the size of the RecyclerView will be different each time we add/delete items.
recyclerView.setHasFixedSize(true);
true if adapter changes cannot affect the size of the RecyclerView.
References
Android Developers Reference - RecyclerView
Understanding RecyclerView setHasFixedSize - Gastón Saillén
I came across the similar problem, just take care of the xml layout file.
Do not use the layout_below , layout_above or others similar properties in RecyclerView or RecyclerView's parent view.
You can use LinearLayout weight , layout_marginBottom or sth to achieve
layout_below or other.
The late answer better than nothing, if you're using NestedScrollView as the parent view of RecyclerView you should delete it.
I had a similar problem and I tryed all solutions listed above, but noone worked.
I was already padding the "Payloads" to "notifyItemChanged(position, payloads)" because I just needed to "upload" a checkbox value so I was passing the value inside "Payloads" without recalling the update of the entire viewholder.
This solution worked for all view holders in my recycler view except for the last one (and probably for all "recycled" ones, I mean those who recall the "onBindViewHolder" by "recycling" an existing view).
I think using "notifyItemChanged" will works if you have only the recyclerview and I also think that this problem of "auto-scrolling" is raised by nested scroll views & recycler views.
I was in the case exposed by "raed", so "ScroolView -> RecyclerView -> "n" x RecyclerView". I have a scroolview wich contains a recyclerview whose viewholders can contains a recycler views.
Delete the parent ScrollView is a really weird solution and I couldn't use it, so I setted the "onStopNestedScroll" inside the "ScrollView" and not inside the RecyclerView.
Personally I used it programmatically before the code part which calls the "notifyItemChanged" method by doing:
msvContainer.onStopNestedScroll(mRecyclerView);
Where "msvContainer" is my ScrollView which contains the RecyclerView, and "mRecyclerView" is my RecyclerView contained by the ScrollView.
This way worked 99% because the first time I call "notifyItemChanged" the view scroll up only for the ScrollView, so it hides a button inside my ScrollView which is below my RecyclerView but it doesn't scroll the RecyclerView items. After the first call "notifyItemChanged" works properly.
I found that calling:
msvContainer.stopNestedScroll();
works too. But i suggest to use the first method with the target view if you have multiple nested scroll views.
Anyway you should call "startNestedScroll" after you ran out of the critical part of re-updating your view holder because the targeted view, so in my case the RecyclerView, won't scroll until you call this method so it won't recycler his view holders too.
(In my case that I have multiple Recycler View inside a parent Recycler View inside a parent Scroll View if I was in need to call "notifyItemChanged" inside the most inner Recycler View i would use the "stopNestedScroll" method for every parent view and then re-activated the scroll after the scroll-critical part)
Hope this is helpful, have a nice coding!
Bye
In my case, all I did was to set the height of the recyclerview to "match_parent". Then in your MainActivity, do;
recyclerView.smoothScrollToPosition(yourAdapter.getItemCount()-1);
Thats all...
I'm creating an EPG like view for which I have multiple horizontal RecyclerViews (as tv programs) encapsulated inside a LinearLayout. When I scroll one of the RecyclerView, I want the rest of the views to be scrolled together.
#Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
layoutContent.setWeightSum(epg.getChannels().size());
//prepare recycler views and add into layoutContent based on epg channels
for(EPG.Channel ch : epg.getChannels()){
AppLog.error(TAG, "Creating RecyclerView for: " + ch.getDisplayName());
//create new recycler view
final RecyclerView rv = new RecyclerView(layoutContent.getContext());
lstRecyclerViews.add(rv);
//set layout manager
rv.setLayoutManager(new LinearLayoutManager(layoutContent.getContext(), LinearLayoutManager.HORIZONTAL, false));
//create adapter
rv.setAdapter(new MyAdapter(ch.getPrograms()));
rv.setItemAnimator(new DefaultItemAnimator());
//add into parent layout
LinearLayout.LayoutParams lp = new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, 0);
lp.weight = 1;
layoutContent.addView(rv, lp);
}
}
I've tried adding a scroll listener to my views but I'm confused with RecyclerView.OnScrollListener's onScrolled method as i can't figure out how to scroll other views.
Any help/suggestion would be helpful.
HorizontelScrollView
{
Linear Layout
{
Vertical list of horizontal recycler views ,
override horizontal recycler view's LayoutManager's
canScroolhorizontally() method to return false ,so that they
all scroll together according to the Grand Parent ScrollView.
}
Our main focus is to scroll that vertical list of horizontal recycler views horizontally ,
first i tried to keep all of them on a horizontal scroll view ,but that is something ,which the android system clearly rejects ,
so i kept one linear layout (in my case vertically oriented ) as a mediator.
so the epg grid now can scroll in vertically as they are inside one vertical recycler view as well as in horizontally because of the Horizontal scroll view.
and we should not allow horizontal list to scroll independentaly ,so I extended layoutmanager and disallow horizontal scrolling ,now they only scroll under grandParent scroll .
You need to always re-calculate position of current items in horizontal recycler views(next h.r.v.)
After scrolling in one h.r.v. re-calculate positions of others based on small amount of scroll movement occured in touched h.r.v.
Then override onViewAttachedToWindow in adapter and use method scrollToPositionWithOffset from LinearLayoutManager of particularly h.r.v to set it to right position.
Also when calculating movement dx, don't forget to disable onScrolled method when finger is down to avoid multiple handling of the same event.
FlexboxLayout (made by Google) lets you make something like this with a RecyclerView and a FlexboxLayoutManager. Since its version 3.0(June 28th 2017) you can drag horizontally through it.
For your example use it like this:
FlexboxLayoutManager layoutManager = new FlexboxLayoutManager(getActivity());
layoutManager.setFlexDirection(FlexDirection.ROW);
layoutManager.setFlexWrap(FlexWrap.WRAP);
recyclerView.setLayoutManager(layoutManager);
And don't forget to set the width of your RecyclerView to a bigger number than the screen size, in order to scroll, do not use match_parent:
<android.support.v7.widget.RecyclerView
android:layout_width="3600dp"
android:layout_height="match_parent"/>
Note: the ViewHolders are recycled by rows, not by columns, so be careful if you have too heavy items on a very long RecyclerView
You need to add textviews in horizontal rows. The length of the textview depends on the duration of the program. I have hosted a sample project on Github. Feel free to clone. https://github.com/xardox69/android_EPG
I created a header view by creating and binding a unique ViewHolder in RecyclerView due to special view type. The header is totally blank but it is full-span and its height is calculated very well for following animations when user scrolls the RecyclerView:
A TextView will move from the center of header view to Toolbar and finally "becomes" title of it. During the process, the TextView will shrink according to vertical offset.
Another TextView will move with the first TextView at the same time but it won't shrink. Instead, it will change alpha and finally become transparent when the first TextView is at correct position in Toolbar.
To implement them, I need to get the scroll distance of RecyclerView so that I can calculate translationY or alpha.
Here is what I've done now:
mRecyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
private int totalDy = 0;
#Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
totalDy -= dy;
// setAlpha, setTranslationY and setScale here according to totalDy.
}
});
It works fine when dataset doesn't change. However, when I scroll down, insert/remove any items to/from the adapter and notifyItemInserted/Removed(), the totalDy won't be accurate. For example, when I removed an item, totalDy won't go back 0 any more.
As a result, it seems that using totalDy to get scroll distance of RecyclerView won't work well. Are there any ways for developers to get it?
NOTICE: What I am using is a StaggeredGridLayoutManager and I feel it is different from LinearLayoutManager.
I've got a RecyclerView using the default Linear Layout Manager.
llm = new LinearLayoutManager(getActivity().getApplicationContext());
llm.setOrientation(LinearLayoutManager.VERTICAL);
ItemListView = (RecyclerView) rootView.findViewById(R.id.Itemlist);
ItemListView.setLayoutManager(llm);
final float scale = getActivity().getBaseContext().getResources().getDisplayMetrics().density;
I am trying to change the height of that one item view to display additional ui elements when the label is clicked.
I tried this with no success
vh.itemView.setMinimumHeight((int)(375 *(scale/160)));
Can I even change the item height? would I need to implement my own layoutManager?
Hide additional ui elements from item view initially by calling
(some_ui_element).setVisibility(View.GONE);
On item click, unhide the elements by calling
(some_ui_element).setVisibility(View.VISIBLE);
and make sure that the item layout in xml is set to wrap-content for its layout-height. The item cardview (or listview) got resized to show the ui elements.