I would like to check when smoothScrollToPosition has finished scrolling back to the first item of recyclerview. I tried doing it like this which only works while smoothScrollToPosition is still scrolling:
recyclerView.getLayoutManager().smoothScrollToPosition(recyclerView,new RecyclerView.State(), 0);
if (!recyclerView.getLayoutManager().isSmoothScrolling()) {
Log.d(TAG, "Scrolling has ended.");
}
I use onScrollStateChanged(RecyclerView recyclerView, int newState) method for tracking scrolling state. The method to initiate scroll looks like this:
private void scrollToPosition(int position){
recyclerView.removeOnScrollListener(onScrollListener);
recyclerView.addOnScrollListener(onScrollListener);
recyclerView.smoothScrollToPosition(position);
}
And here is the listener:
RecyclerView.OnScrollListener onScrollListener = new RecyclerView.OnScrollListener() {
#Override
public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
switch (newState) {
case SCROLL_STATE_IDLE:
//we reached the target position
recyclerView.removeOnScrollListener(this);
break;
}
}
};
So, when recyclerView reaches SCROLL_STATE_IDLE, that means it has finished scrolling. Don't forget to remove listener in this state, so it won't trigger on the next scroll.
There is a listener for it:
https://developer.android.com/reference/android/support/v7/widget/RecyclerView.OnScrollListener
void onScrolled (RecyclerView recyclerView,
int dx,
int dy)
Callback method to be invoked when the RecyclerView has been scrolled. This will be called after the scroll has completed.
Related
My Android App has several RecyclerViews whose items need to be rearrangeable by the user. Every item contains a 'reorder control' which, when pressed, must start the drag of that item. (Comparable to the 'reorder item' in an iOS table view cell). The drag should not be started by a long press. My App uses data binding.
I found several examples in which the ItemTouchHelper is used to enable drag & drop in a RecyclerView. All these examples use the standard long press to start dragging. The code below shows the BindingAdapter to enable drag & drop as it is used in most examples:
#BindingAdapter(value = {"draggable"})
public static void bindRecyclerViewDraggable(RecyclerView recyclerView, boolean draggable) {
ItemTouchHelper helper = new ItemTouchHelper(new ItemTouchHelper.SimpleCallback(ItemTouchHelper.DOWN | ItemTouchHelper.UP, 0) {
#Override
public boolean onMove(#NonNull RecyclerView recyclerView, #NonNull RecyclerView.ViewHolder dragged, #NonNull RecyclerView.ViewHolder target) {
int draggedPos = dragged.getAdapterPosition();
int targetPos = target.getAdapterPosition();
recyclerView.getAdapter().notifyItemMoved(draggedPos, targetPos);
return true;
}
#Override
public void onSwiped(#NonNull RecyclerView.ViewHolder viewHolder, int direction) {
}
});
helper.attachToRecyclerView(recyclerView);
}
This method works fine in case long press is used to trigger the drag, but how to trigger the drag when the 'reorder control' is pressed on an item?
One solution I see is that I instantiate the ItemTouchHelper in the ViewModel that is associated with the View that contains the RecyclerView and then attach that to the RecyclerView through data binding. However, that does not feel right in some way. The ItemTouchHelper is a View class (it extends RecyclerView.ItemDecoration) and I want as little as possible View related stuff in my ViewModel.
Can anyone think of a better solution to solve this problem?
I am working with Navigation menu view and I am scrolling it programmatically and then get a Y position of its item.
But the code that get the item positions is running immediately after the list (using the layout manager) starts to scroll and when it haven't finished to scroll and change the positions yet.
How can I do the rest of code after "scrollToPositionWithOffset" wait the end of the scroll action to run?
Here's my code:
RecyclerView recyclerView = (RecyclerView) navigationView.getChildAt(0);
LinearLayoutManager layoutManager = (LinearLayoutManager) recyclerView.getLayoutManager();
layoutManager.scrollToPositionWithOffset(4, 0);
//it should wait the line above run completely to can run too
mImage.animate().y(navView.getChildAt(4).getY());
You should use a scroll listener that listens for the Scrolling state to change to idle.. See below.
recyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
#Override
public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
if (newState == RecyclerView.SCROLL_STATE_IDLE) {
mImage.animate().y(navView.getChildAt(4).getY());
recyclerView.removeOnScrollListener(this);
}
}
}
layoutManager.scrollToPositionWithOffset(4, 0);
On researching i found this :
final LinearLayoutManager layoutManager = new LinearLayoutManager(getActivity(), LinearLayoutManager.VERTICAL, false) {
#Override
public void onLayoutChildren(final Recycler recycler, final State state) {
super.onLayoutChildren(recycler, state);
//TODO if the items are filtered, considered hiding the fast scroller here
final int firstVisibleItemPosition = findFirstVisibleItemPosition();
if (firstVisibleItemPosition != 0) {
// this avoids trying to handle un-needed calls
if (firstVisibleItemPosition == -1)
//not initialized, or no items shown, so hide fast-scroller
mFastScroller.setVisibility(View.GONE);
return;
}
final int lastVisibleItemPosition = findLastVisibleItemPosition();
int itemsShown = lastVisibleItemPosition - firstVisibleItemPosition + 1;
//if all items are shown, hide the fast-scroller
mFastScroller.setVisibility(mAdapter.getItemCount() > itemsShown ? View.VISIBLE : View.GONE);
}
};
The good thing here is that it works well and will handle even keyboard being shown/hidden.
The bad thing is that it gets called on cases that aren't interesting (meaning it has false positives), but it's not as often as scrolling events, so it's good enough for me.
Can I detect the direction of scroll when I add the on scroll listener to my recycler view?
The code is as follows:
recyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
#Override
public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
switch (recyclerView.getScrollState()) {
case RecyclerView.SCROLL_STATE_DRAGGING :
LinearLayout.LayoutParams layoutParams = (LinearLayout.LayoutParams) relativeBaseLayout.getLayoutParams();
if(layoutParams.weight < 1.0f) {
SlideAnimation slideAnimation = new SlideAnimation(layoutParams.weight, 1.0f, relativeBaseLayout);
slideAnimation.setDuration(250);
relativeBaseLayout.startAnimation(slideAnimation);
}
break;
default:
break;
}
}
});
I want this function to work only if I scroll down.
Currently it'll work on both scroll up and down. What changes do I make?
I do not want to implement the onScrolled() method since that will be called after the scroll has finished. I want to detect it during the scroll.
What am I missing here?
In my onBindViewHolder of my RecyclerView.Adapter<SearchAdapter.ViewHolder> when user clicks on cardview a button becomes visible. But when I'm scrolling recyclerview some other items buttons are shown as visible too. Why is this happening?
this is my code:
#Override
public void onBindViewHolder(final ViewHolder viewHolder, final int position) {
viewHolder.card.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
if (viewHolder.content_layout.getVisibility() == View.VISIBLE) {
viewHolder.content_layout.setVisibility(View.GONE);
viewHolder.address.setMaxLines(2);
viewHolder.attribute.setMaxLines(2);
} else {
viewHolder.content_layout.setVisibility(View.VISIBLE);
viewHolder.address.setMaxLines(8);
viewHolder.attribute.setMaxLines(8);
}
}
});
...
}
Once you start scrolling down the list your views get recycled. This means a previously inflated ViewHolder (some that gets created in onCreateViewHolder) is reused.
So what you have to do is to remember the clicked positions (e.g. via a SparseBooleanArray) and check in onBindViewHolder whether the view should be visible (previously clicked) or not.
You can find a basic usage example of the SparseBooleanArray in this StackOverflow post
The 'other' visible items buttons are the ones using the same viewholder that was modified in the callback. So because viewholders (and views) are recycled :
They should only store information that can be retrieved each time the viewholder is bound to a position.
Anything that may be changed in the views state should be refreshed in onBindViewHolder()
In your case you should store the 'is selected' somewhere else and reset the visibility and maxlines in onBindViewHolder() (not only in the callback)
Good idea is to make a class object with all data you need for one item in recycler view, also add there one boolean isItemWasClicked and inside onBindViewHolder() check this boolean and make buttons visible or not.
For example:
public class OneItemOfList{
int priceToDisplay;
String name;
String date;
boolean wasClicked;
}
public class YourAdapter extends RecyclerView.Adapter<OneItemOfList.ViewHolder> {
ArrayList<OneItemOfList> items;
...
#Override
public void onBindViewHolder(ViewHolder viewHolder, final int position) {
viewHolder.view.setText(items.get(position).name);
if (items.get(position).wasClicked)
viewHolder.button.setVisible(View.VISIBLE);
else
viewHolder.button.setVisible(View.GONE);
viewHolder.view2.setOnClickListener(...
OnClick(...){
items.get(position).wasClicked = !items.get(position).wasClicked;
});
}
...
}
create an array for example Boolean array, and when each position clicked, set true in same position of array. and in onBindViewHolder check if that array[position] is true set that item visible if.
I a using a Fragment which extends a ListFragment, this then uses an ArrayAdapter in order to create a dynamic list of custom rows (this is all working perfectly).
What I am now trying to do is implement an onTouch event where when the user swipes each row left of right then do something. This logic is all working the only issue is I am having to do the onTouch event as the ACTION_UP does not seem to get called first time.
#Override
public void onListItemClick(ListView l, View v, final int position, long id) {
super.onListItemClick(l, v, position, id);
v.setOnTouchListener(new View.OnTouchListener() {
int initialX = 0;
final float slop = ViewConfiguration.get(getActivity()).getScaledTouchSlop() * 1.5f;
#Override
public boolean onTouch(final View v, MotionEvent event) {
switch(event.getActionMasked()) {
case MotionEvent.ACTION_DOWN:
initialX = (int) event.getX();
return true;
case MotionEvent.ACTION_UP:
// Do something here
return false;
}
return false;
}
});
}
I am returning true from the ACTION_DOWN so from my understanding this should indicate to the onTouch event to continue processing touch events and in my above code this will be the ACTION_UP when the user lifts his finger from the screen, not sure what I am missing here.
Further to this if at all possible I would also like to bind an onClick event onto the same view (not essential as already have workaround if I can get above working).
You must have used OnTouchListener listener for swiping each rows of listView, make sure that you are returning false on ACTION_UP event.
Parent view receives a call back only when child views have not consumed it. So if each rows of list view is having a onTouchListener, you have to propagate event to parent by returning false.
Hope it will help.