I am using a RecyclerView with a Horizontal LinearLayoutManager.
recyclerView.setLayoutManager(new LinearLayoutManager(this, LinearLayoutManager.HORIZONTAL,false));
For the adapter items to snap at the center, I've attached a LinearSnapHelper to the recyclerview.
SnapHelper helper = new LinearSnapHelper();
helper.attachToRecyclerView(recyclerView);
Now, I have two scenarios where I would want an item to come to center
When my activity launches, it should launch with a specific item snapped to center.
On tapping on an item in the recyclerview, it should get centered. For this, I've overridden the OnClick method in adapter's ViewHolder.
For both scenarios, I'm using
recyclerView.smoothScrollToPosition(position);
and the item gets centered. This however happens with a bouncy animation where a little extra scroll happens first and post that the item bounces back.
How can I disable this bouncy animation to get a smooth scroll?
Things I've tried -
Used below APIs in place of smoothScrollToPosition
LinearLayoutManager.scrollToPosition()
LinearLayoutManager.scrollToPositionWithOffset()
Both the above APIs don't give a smooth scroll, plus the item doesn't get centered properly (since it's difficult to figure out the correct offset value for items that are not yet created/recycled back during the API call)
I couldn't find any method to disable/override animations in RecyclerView's Documentation. Could someone please help..
The solution is to use extended LinearLayoutManager:
import android.content.Context;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.LinearSmoothScroller;
import android.support.v7.widget.RecyclerView;
public class NoBounceLinearLayoutManager extends LinearLayoutManager {
public NoBounceLinearLayoutManager(Context context, int orientation, boolean reverseLayout) {
super(context, orientation, reverseLayout);
}
#Override
public void smoothScrollToPosition(RecyclerView recyclerView, RecyclerView.State state, final int position) {
LinearSmoothScroller linearSmoothScroller = new LinearSmoothScroller(recyclerView.getContext()) {
#Override
protected int getHorizontalSnapPreference() {
return position > findFirstVisibleItemPosition() ? SNAP_TO_START : SNAP_TO_END;
}
};
linearSmoothScroller.setTargetPosition(position);
startSmoothScroll(linearSmoothScroller);
}
}
Related
I am currently detecting when a specific view becomes visible to the user or goes out of sight and stopping the RecyclerView scroll at that view position. So far I have been able to achieve this with the help of the OnScrollListener together with RecyclerView.stopScroll and RecyclerView.scrollTo, but the only issue now is that if the user flings the list it will get past that view position since the fling is still "acting" after the scroll to position has been called, causing the list to overshoot the item position.
How can I end the user fling action programmatically? Something like recyclerView.fling(0) exists?
I ended up using LinearLayoutManager.scrollToPositionWithOffset with the position of the target and 0 as the offset after calling RecyclerView.stopScroll.
So far the fling behavior is no longer causing the list scroll to overshoot the intended position when using these two together.
public class CustomLayoutManager extends LinearLayoutManager {
private boolean isScrollEnabled = true;
public CustomLayoutManager (Context context) {
super(context);
}
public void setScrollEnabled(boolean flag) {
this.isScrollEnabled = flag;
}
#Override
public boolean canScrollVertically() {
//Similarly you can customize "canScrollHorizontally()" for managing horizontal scroll
return isScrollEnabled && super.canScrollVertically();
}
}
Set this as your recyclerview layout manager and toggle the scrolling with setScrollEnabled(true/false) with the layoutManager object
Assuming your Recyclerview rv
CustomLayoutManager lm=new CustomLayoutManager(context of your activity);
rv.setLayoutManager(lm);
lm.setScrollEnabled(true/false);
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());
}
});
I am trying to make a application that displays data in list using RecyclerView, In LinearLayoutManager there is a option to set Stack from end and reverse layout that allows for me to show the newest data on top of list. Is there anyway I can show the newest data at position 0 (at top) in staggered grid layout?
StaggeredGridLayoutManager has the method setReverseLayout.
Use it like this:
mLayoutManager = new StaggeredGridLayoutManager(getActivity());
mLayoutManager.setReverseLayout(true);
You can use GridLayoutManagaer. In constructor you can pass true/false to reverse layout or can use setReverseLayout(boolean) method. check this
You can scroll to the end after updating(inserting) you data (when it's needed)
private void updateData(#NonNull List<Item> items, boolean moveToTheEnd) {
adapter.updateItems(items);
if(moveToTheEnd) {
recyclerView.scrollToPosition(adapter.getItemCount() - 1);
}
}
The solution that worked for me, without having those spaces at the beginning, is to add this line at the beginning of your onBindViewHolder method in your adapter:
position = getItemCount()-1-position;
So your code will be something like this:
#Override
public void onBindViewHolder(#NonNull MyAdapter.ViewHolder holder, int position) {
position = getItemCount()-1-position;
holder.textView.setText(mObjects.get(position).getText());
}
Hi I have vertical list (Custom ArrayAdapter) where each element has a horizontal RecyclerView, to give me a table like format.
What I want to do is, if one row is scrolled horizontally, rest rows too should be. I tried putting them just inside horizontal scroll view and various tricks available, but got no luck.
If possible, I would also like this synchronization keeping first element fixed.
Make an instance of RecyclerView.OnScrollListener. This instance will be common to all your horizontal recycler views. When onScrolled( ) gets called, scroll all the views except the one on which this got called.
Adding more to Little Child's answer, it was trickier than expected, I had to create a custom listener that was in my main Activity that was called by onScroll. This is because onScroll was in a getView method and did not get access to other views in listview, but my mainactivty could access them.
In my listadapter, I added
public interface RecycleListener {
public void onScroll(int scroll_x);
}
Then in getView
mRecyclerView.setOnScrollListener(new RecyclerView.OnScrollListener() {
#Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
listener.onScroll(recyclerView.computeHorizontalScrollOffset());
}
});
and finally in MainActivity
dayAdapter.setRecycleListener(new DayAdapter.RecycleListener() {
int busy = 0;
#Override
public void onScroll(int scroll_x)
{
if(busy == 1) return;
busy = 1;
for(int i = 0;i<days.size();i++)
{
View view = lv.getChildAt(i);
if(view == null) continue;
RecyclerView r = (RecyclerView) view.findViewById(R.id.timetable_row_recycler);
LinearLayoutManager layoutManager = ( LinearLayoutManager) r.getLayoutManager();
layoutManager.scrollToPositionWithOffset(0,0-scroll_x);
}
busy = 0;
}
});
Note I found out that you can easily get scroll position by using int scroll_x = recyclerView.computeHorizontalScrollOffset() however there is no direct method to appy it back. The method layoutManager.scrollToPosition(i) scrolls to ith element and not the scroll position, so the only correct possible way is
layoutManager.scrollToPositionWithOffset(0,0-scroll_x);
or (in case of custom layoutmanager)
((LinearLayoutManager) layoutManager).scrollToPositionWithOffset(0,0-scroll_x);
We basically apply negative offset to the 0th position, so instead of scrolling to offset before 0th, it scrolls to our desired scroll_x.
UPDATE
this worked but caused a lot of lag, so later I ended up switching to android.widget.TableLayout, and inserted rows (android.widget.TableRow) like I was trying with recyclerview.
In my horizontal listview when I swipe one once, the scrolling is working as it should be, but what I need is after a one swipe, next list item to be shown.(just like trimmedia preview in android )
You have to override the fling method of your ListView.
The method "fling" is used to emulate the inertia of the list when you remove your finger from the screen. If the method is empty, this has the effect to scroll only on the next item.
Here an example:
import android.content.Context;
import android.widget.HorizontalScrollView;
public class MyListView extends HorizontalScrollView {
public MyListView(Context context) {
super(context);
}
#Override
public void fling(int velocityX) {
//super.fling(velocityX);
}
}
I didn't test but it's how I did in a little more complex on another project.