Catch Animation Event of SmoothScroll in RecyclerView [Android] - android

I have a GridLayout RecyclerView with a PageSnapHelper attached that essentially acts like a Vertical Linear RecyclerView (I inherited the code, not sure why this was done). The goal is to highlight the item currently centered in the view. Scrolling is done from code driven by two menu items to simulate going forward and backward from a remote. Scrolling of the view works fine. The issue seems to be that when I listen for the end of scrolling event there is animation going on which messes up my code to draw the highlight around the item. Here's the code I use to scroll the view based on the menu item:
#Override
public boolean onOptionsItemSelected(MenuItem item) {
//Get which arrow was pressed
int id = item.getItemId();
//Get count of items
int itemCount = recyclerView.getAdapter().getItemCount();
//Condition to moving down the list
if (id == R.id.next) {
//Make sure we're within bounds of the of the view from the top
if(count>=itemCount-1) {
return super.onOptionsItemSelected(item);
}
//Increment to count to next ViewHolder
count++;
//Condition for moving up list
} else if (id == R.id.prev) {
//Make sure we're within bounds
if(count == 0){
return super.onOptionsItemSelected(item);
}
//Decremennt to previous ViewHolder
count--;
}
//Scroll the RecyclerView to the position we want
layoutManager.setIsNext(id == R.id.next);
recyclerView.smoothScrollToPosition(count);
//If the item is already in view, then no scroll event is necessary and we can access the ViewHolder
HighlightViewHolder(count);
//In the event that the ViewHolder is off-screen listen for the end of the scrolling event to ensure the ViewHolder
//is created and in correct position on the screen
RecyclerView.OnScrollListener scrollListener = new RecyclerView.OnScrollListener() {
//Add a listener to that looks out for when the scrolling is complete
#Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
super.onScrolled(recyclerView, dx, dy);
Log.i("ScrollListener", "Scrolling has ended");
HighlightViewHolder(count);
recyclerView.removeOnScrollListener(this);
}
};
recyclerView.addOnScrollListener(scrollListener);
return super.onOptionsItemSelected(item);
When the app first begins and I the forward button, the first item is highlighted just fine:
On the next selection, which engages a scrolling event is where I have an issue:
What I would expect to happen is that when the scrolling event has ended, the item is in its centered position. Based on what I'm seeing I think there's some animation event that's still happening that I should be looking for the completion of. My highlighting code relies on the view in question to be in its expected location. I'm new to android and my searches for RecyclerView animations brought me to ItemAnimator. But reading the docs I'm not sure if this is what I'm looking for. Any ideas?

I couldn't find any events related to what I was looking for so I went ahead and used a Handler to wait an arbitrary amount of time before drawing the highlight. I updated the Listener as so:
//Add a listener to that looks out for when the scrolling is complete
#Override
public void onScrolled(final RecyclerView recyclerView, int dx, int dy) {
super.onScrolled(recyclerView, dx, dy);
Handler handler = new Handler();
Runnable task = new Runnable() {
#Override
public void run() {
HighlightViewHolder(count);
}
};
handler.postDelayed(task, 500);
recyclerView.removeOnScrollListener(this);
}

Related

RecyclerView with custom grid LayoutManager loses views and scrolling synchronization onFling

I have an Activity with 3 RecyclerViews in a ConstraintLayout. One is along the left side of the activity and uses a vertical LinearLayoutManager. One is on the top side of the activity and uses a horizontal LinearLayoutManger. The last one to the right of the first and under the second and using a custom LayoutManager that implements a grid that scrolls both horizontally and vertically. This creates a spreadsheet effect, and I need the RecyclerViews to scroll simultaneously in an direction. This works for the most part, except for the case where there is a quick flinging action. When a fling is detected there are two problems:
The RecyclerView using the custom LayoutManager seems to lag and for a time no views are visible in this recycler.
I suspect this is because the size of the Views means about 45 are visible each time, so on a fast fling all of these views are being constantly recycled.
The scrolling synchronization between RecyclerViews is lost, sometimes they are off by a little but if the list is large then they can be off by quite a bit. This is the more important issue as the views must align correctly to be useful to users. The speed of the fling that causes this is much lower than problem #1
Current Code
The scroll listener attached to all 3 RecyclerViews:
private RecyclerView.OnScrollListener syncScrollListener = new RecyclerView.OnScrollListener() {
#Override
public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
super.onScrollStateChanged(recyclerView, newState);
if (recyclerAssignment == recyclerView && newState == RecyclerView.SCROLL_STATE_DRAGGING) {
draggingView = 1;
} else if (recyclerGrades == recyclerView && newState == RecyclerView.SCROLL_STATE_DRAGGING) {
draggingView = 2;
} else if (recyclerStudent == recyclerView && newState == RecyclerView.SCROLL_STATE_DRAGGING) {
draggingView = 3;
}
}
#Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
super.onScrolled(recyclerView, dx, dy);
if (draggingView == 1 && recyclerView == recyclerAssignment) {
recyclerGrades.scrollBy(dx, dy);
} else if (draggingView == 2 && recyclerView == recyclerGrades) {
recyclerAssignment.scrollBy(dx, dy);
recyclerStudent.scrollBy(dx, dy);
} else if (draggingView == 3 && recyclerView == recyclerStudent) {
recyclerGrades.scrollBy(dx, dy);
}
}
};
The custom LayoutManager is exactly the same as this: https://github.com/devunwired/recyclerview-playground/blob/master/app/src/main/java/com/example/android/recyclerplayground/layout/FixedGridLayoutManager.java
What I've Tried
I noticed there is a possibility to add an OnFlingListener but it seems a fling calls the Recycler's scrollBy function, and they have different parameters.
I tried to detect when a scroll state changed to idle in the grid RecyclerView and then scroll the other views, but this caused an infinite loop.
Further thoughts
I thought it might make sense to extend RecyclerView to override the method that handles a fling, but I'm unsure exactly what to do in that method to make this work correctly.
I understand that to solve problem #1 I probably have to store more views out of frame. This is fine but I'm a big confused on how that logic might work in the LayoutManager.
The layout manager returns a "delta" variable when it scrolls either horizontally or vertically, and the comments mentions this is "for handling flings" but there are no further comments and I don't think this is behaving quite as intended.
Any help solving these two problems is greatly appreciated.

Hiding FloatingAction items when you scroll to the bottom of a list?

So if you have a RecyclerView that extends to the end of the screen and use a FloatingActionButton or the third-party FloatingActionMenu, then there is a slight problem with cover-up: If you scroll to the end of the list, the floating button covers up part of the row and there is no way to see or get to what's underneath it.
Is there a way Android allows you to:
Detect if your list has a sufficient number of items in it, i.e. if there is a row at the bottom of the screen visible.
and
Slide the buttons out of the way if you scroll down to the very end, and then slide them back in if you start scrolling back up?
Edit: Or, alternatively, add dynamic padding to the end of the RecyclerView that only shows up if you've scrolled all the way down?
The common pattern to solve the problem of covering up parts of UI is to hide FAB as soon as the user starts scrolling down ... You can achieve this by this code fragment (used with RecyclerView):
fDemandsRv.addOnScrollListener(new RecyclerView.OnScrollListener() {
#Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
super.onScrolled(recyclerView, dx, dy);
if(dy > 0 ){
if(fFab.isShown()) fFab.hide();
} else {
if(!fFab.isShown()) fFab.show();
}
}
});
If you insist on hiding it on the very end and you are using RecyclerView with LinearLayoutManager, you can check method findLastCompletelyVisibleItemPosition() on the LayoutManager object during the OnScrollListener callback ...
Of course it is possible. The solution was posted here.
The main idea behind it is that FloatingActionButton.Behavior is overriden and changed, so that it reacts to RecyclerView and CoordinatorLayout scrolling.
However, in your case RecyclerViewScrollListener must be different. Here is the implementation:
private class RecyclerViewScrollListener extends RecyclerView.OnScrollListener {
FloatingActionButton mChild;
public RecyclerViewScrollListener(FloatingActionButton child) {
this.mChild = child;
}
#Override
public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
super.onScrollStateChanged(recyclerView, newState);
if (newState == RecyclerView.SCROLL_STATE_IDLE && recyclerView.canScrollVertically(Integer.MAX_VALUE)) {
mChild.show();
} else {
mChild.hide();
}
}
#Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
super.onScrolled(recyclerView, dx, dy);
if (!recyclerView.canScrollVertically(Integer.MAX_VALUE)) {
mChild.hide();
}
}
}
Edit:
When filling RecyclerView with data, invoke such function on it in order to disable or enable scrolling:
private void changeCoordinatorLayoutScrollPossibility(final RecyclerView recyclerView) {
new Handler().post(new Runnable() {
#Override
public void run() {
if (recyclerView.getMeasuredHeight() < alertsRecyclerView.computeVerticalScrollRange()) {
recyclerView.setNestedScrollingEnabled(true);
} else {
recyclerView.setNestedScrollingEnabled(false);
}
}
});
}

Android EditText's container (RecyclerView) auto scroll when EditText is Focused

I got a RecyclerView whose element contains a EditText widget.
1.currently focusing on the second EditText,the first EditText is partly showed on the screen.
the first EditText is partly showed
2.when click the first EditText, it got focus, then RecyclerView would automatically scroll down to fully show the first EditText.
RecyclerView automatically scroll down when first EditText got focused
This is great,but what I want is RecyclerView should not automatically scroll.
Any solution?
thanks!
Actually, there is a scroll state of RecycleView that you can control like this;
Create your own LayoutManager and override the scrollHorizontallyBylike this.
#Override
public int scrollHorizontallyBy(int dx, RecyclerView.Recycler recycler, RecyclerView.State state) {
int nScroll = 0;
// Do not let auto scroll
if (recyclerView.getScrollState() != RecyclerView.SCROLL_STATE_SETTLING){
nScroll = super.scrollHorizontallyBy(dx, recycler, state);
}
}
So, what is the SCROLL_STATE_SETTLING?
The RecyclerView is currently animating to a final position while not
under outside control.
I finally fixed it by disable the RecyclerView from scrolling except it receive a touch event.
first, I custom a LayoutManager:
#Override
public int scrollHorizontallyBy(int dx, RecyclerView.Recycler recycler, RecyclerView.State state) {
if(!horizontal) {
return 0;
}
#Override
public int scrollVerticallyBy(int dy, RecyclerView.Recycler recycler, RecyclerView.State state) {
if(!vertical) {
return 0;
}
when receive click event :
I set horizontal and vertical to false ,these cause RecyclerView cannot scroll any more!
and I subClass The recyclerView and Override onTouchEvent:
public boolean onTouchEvent(MotionEvent e) {
//we enable scrollHorizontallyBy and scrollVerticallyBy only in Touch Event, set layoutManager vertical and horizontal to true
......
return super.onTouchEvent(e);
}
so MyRecyclerView cannot scroll when click to find that the focus child is an EditText. But it can scroll when Receive Touch Event!

How to cancel scrolling when scroll to a specific position in Android Scrollview?

I have a problem while scrolling in Scrollview. I would like to cancel scrolling immediately when I've scrolled to a specific position (like end of scrollview). That means my finger is still held on screen, but Scrollview can't scroll anymore. How can I accomplish this?
#Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
if (!mScrollable) return false;
else return super.onInterceptTouchEvent(ev);
}
If you want to cancel/stop scroll when you have reached particular position, you can use smoothScrollToPosition(int pos), where pos is the adapter position.
Also to make it a smooth experience for user, you can tell the listview to post it when ui thread is free:
getListView().post(new Runnable() {
#Override
public void run() {
getListView().smoothScrollToPosition(pos);
}
});

stop scrollView in the middle of the scroll

I have a scrollview with a lot of content. Now when user do a fling or scroll down, I want the scrollview to stop at a particular view location, where I am doing some animation, and then user can again fling or scroll down.
I have tried the disabling of scrollview as mentioned here but It only disables when the scrollview completely stops and cannot stop in the middle of a fling.
Is there any way I can stop a fling of the scrollview when a certain view location or a certain y value is reached?
I had the same problem my solution was.
listView.smoothScrollBy(0,0)
This will stop the scrolling.
To stop a fling at a particular point simply call
fling(0)
If you are only concerned about flings this is the most logical way to do so in my opinion, because velosityYis set to 0 and thereby the fling is stopped immediately.
Here is the javadoc of the fling method:
/**
* Fling the scroll view
*
* #param velocityY The initial velocity in the Y direction. Positive
* numbers mean that the finger/cursor is moving down the screen,
* which means we want to scroll towards the top.
*/
One approach may be to use smoothScrollToPosition, which stops any existing scrolling motion. Note this method requires API level >= 8 (Android 2.2, Froyo).
Note that if the current position is far away from the desired position, then the smooth scrolling will take quite a long time and look a bit jerky (at least in my testing on Android 4.4 KitKat). I also found that a combination of calling setSelection and smoothScrollToPosition could sometimes causes the position to "miss" slightly, this seems to happen only when the current position was very close to the desired position.
In my case, I wanted my list to jump to the top (position=0) when the user pressed a button (this is slightly different from your use case, so you will need to adapt this to your needs).
I used the following method to
private void smartScrollToPosition(ListView listView, int desiredPosition) {
// If we are far away from the desired position, jump closer and then smooth scroll
// Note: we implement this ourselves because smoothScrollToPositionFromTop
// requires API 11, and it is slow and janky if the scroll distance is large,
// and smoothScrollToPosition takes too long if the scroll distance is large.
// Jumping close and scrolling the remaining distance gives a good compromise.
int currentPosition = listView.getFirstVisiblePosition();
int maxScrollDistance = 10;
if (currentPosition - desiredPosition >= maxScrollDistance) {
listView.setSelection(desiredPosition + maxScrollDistance);
} else if (desiredPosition - currentPosition >= maxScrollDistance) {
listView.setSelection(desiredPosition - maxScrollDistance);
}
listView.smoothScrollToPosition(desiredPosition); // requires API 8
}
In my action handler for the button I then called this as follows
case R.id.action_go_to_today:
ListView listView = (ListView) findViewById(R.id.lessonsListView);
smartScrollToPosition(listView, 0); // scroll to top
return true;
The above does not directly answer your question, but if you can detect when the current position is at or near your desired position, then maybe you could use smoothScrollToPosition to stop the scrolling.
You need to disable ScrollView handling of the fling operation. To do this simply override the fling method in ScrollView and comment super.fling(). Let me know if this works !
public class CustomScrollView extends ScrollView {
#Override
public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY)
{
return false;
}
#Override
public void fling (int velocityY)
{
/*Scroll view is no longer gonna handle scroll velocity.
* super.fling(velocityY);
*/
}
}
I'm trying to achieve a similar situation. I have a partial solution:
I've managed to stop the ScrollView at a particular y coordinate by overriding onScrollChanged and then calling smoothScrollTo. I allow the user to continue scrolling down past that point by keeping a boolean that indicates if this was the first fling/drag or not. And the same goes for scrolling from bottom to top - I stop the scroll at the same point again. And then allow the user to continue scrolling upward again.
The code looks something like this:
boolean beenThereFromTop = false;
boolean beenThereFromBottom = false;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
final ScrollView scrollView = (ScrollView) findViewById(R.id.scrollView);
scrollView.getViewTreeObserver().addOnScrollChangedListener(new ViewTreeObserver.OnScrollChangedListener() {
#Override
public void onScrollChanged() {
TextView whereToStop = (TextView) findViewById(R.id.whereToStop);
final int y = whereToStop.getBottom();
int scrollY = scrollView.getScrollY();
// manage scrolling from top to bottom
if (scrollY > y) {
if (!beenThereFromTop) {
beenThereFromTop = true;
scrollView.post(new Runnable() {
#Override
public void run() {
scrollView.smoothScrollTo(0, y);
}
});
}
}
if (scrollY < y && beenThereFromTop) {
beenThereFromTop = false;
}
// manage scrolling from bottom to top
if (scrollY < y) {
if (!beenThereFromBottom) {
beenThereFromBottom = true;
scrollView.post(new Runnable() {
#Override
public void run() {
scrollView.smoothScrollTo(0, y);
}
});
}
}
if (scrollY > y && beenThereFromBottom) {
beenThereFromBottom = false;
}
}
});
}
Well im actualy using this method:
list.post(new Runnable()
{
#Override
public void run()
{
list.smoothScrollToPositionFromTop(arg2, 150);
}
});

Categories

Resources