Android: onScrollStateChanged SCROLL_STATE_IDLE sometimes doesn't fire - android

I'm running into a bit of a problem. What I'm doing: I've got a ListView which has got some images in it. To make the scrolling smoother I've disabled the images to show up when scrolling. Now there seems to be a bug in Android which sometimes causes the scroll state to not change back from SCROLL_STATE_FLING back to SCROLL_STATE_IDLE, which causes my images to not show up again.
My first thought was to set an onTouchListener and check when I get ACTION_UP, but that doesn't help because the SCROLL_STATE_FLING state is obviously being set after that. So now I've thought I could start a timer when the SCROLL_STATE_FLING state is being set and check after some time if the state is still in fling mode and then invalidate my view. But I don't think that's a very good solution.
Does anyone have a better idea on how I could do that? I've seen this reply but I need a solution for API level < 9 (plus it also sometimes happen when it's not overscrolling)
Here's my code for that:
mList.setOnScrollListener(new OnScrollListener() {
#Override
public void onScrollStateChanged(AbsListView view, int scrollState) {
mListAdapter.setIsScrolling(scrollState != SCROLL_STATE_IDLE);
Log.i(this, "scrollStateChanged" + scrollState);
if (scrollState == SCROLL_STATE_IDLE) {
mList.invalidateViews();
}
}
#Override
public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
}
});
Thanks,
Maria

I had the same problem, so my solution was to just detect if the scrollview position has reached the last page and in that case always load the images regardless of the scroll state (since the problem seems to always occur when the user flings to the end of the listview). So modifying your code you would have:
mList.setOnScrollListener(new OnScrollListener() {
#Override
public void onScrollStateChanged(AbsListView view, int scrollState) {
mListAdapter.setIsScrolling(scrollState != SCROLL_STATE_IDLE);
Log.i(this, "scrollStateChanged" + scrollState);
int first = view.getFirstVisiblePosition();
int count = view.getChildCount();
if (scrollState == SCROLL_STATE_IDLE || (first + count > mListAdapter.getCount()) ) {
mList.invalidateViews();
}
}
#Override
public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
}
});

I ran into the same issue with a RecyclerView.
Jason Burke's answer is correct, but I'll provide an alternate way of checking if you are at the beginning/end of the RecyclerView/ListView.
I do it in onScrolled instead of onScrollStateChanged since I don't trust that I get a state changed event in the case when you scroll/fling to the edge of the RecyclerView.
recyclerView.addOnScrollListener(object : RecyclerView.OnScrollListener() {
override fun onScrollStateChanged(recyclerView: RecyclerView, newState: Int) {
super.onScrollStateChanged(recyclerView, newState)
if (newState == RecyclerView.SCROLL_STATE_IDLE) {
updateUi()
}
}
override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
super.onScrolled(recyclerView, dx, dy)
/* If the user scrolls to the edges of the recyclerview, we can't trust that we get the SCROLL_STATE_IDLE state.
* Therefore we have to update the view here in onScrolled for these cases
*/
if (!recyclerView.canScrollVertically(1) || !recyclerView.canScrollVertically(-1)) {
updateUi()
}
}
}
In my case it was actually a horizontal RecyclerView, so I had to do this instead:
if (!recyclerView.canScrollHorizontally(1) || !recyclerView.canScrollHorizontally(-1)) {
updateUi()
}

I have had this same problem and posted a workaround on the bug list:
For anybody still running into this problem (as I was last week) a
workaround that works for me is the following: If android SDKInt == 7
set a onTouchListener on the (Abs)ListView
In that onTouchListener when the OnTouch event action is
MotionEvent.ACTION_UP || action == MotionEvent.ACTION_CANCEL you force
a onScrollStateChanged with first a SCROLL_STATE_FLING and then a
SCROLL_STATE_IDLE
Code example: In the onCreate:
if(androidSDKInt <= 7){
listViewDateSelector.setOnTouchListener(new FingerTracker(onScrollListener)); }
Then add a private class with:
private class FingerTracker implements View.OnTouchListener {
private OnScrollListener myOnScrollListener;
public FingerTracker(OnScrollListener onScrollListener){
myOnScrollListener = onScrollListener; }
public boolean onTouch(View view, MotionEvent event) {
final int action = event.getAction();
boolean mFingerUp = action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_CANCEL;
if (mFingerUp) {
myOnScrollListener.onScrollStateChanged((AbsListView) view, OnScrollListener.SCROLL_STATE_FLING);
myOnScrollListener.onScrollStateChanged((AbsListView) view, OnScrollListener.SCROLL_STATE_IDLE);
}
return false; } }

Related

RecyclerView scroll to drag and dissmiss

I have overlay view that is animated from top. In that scene i have recycler view with elements list.
When user scrolls list to the end, scrolling further should scroll the whole recyclerview up (uncovering view beneath) and on ACTION_UP should animate recycler out of the screen.
overriding onTouch events doesn't work :
RecyclerView.setOnTouch() - causes dragging to glitch
ParentView.setOnTouch() - works only when dragging other views (not RecyclerView it self).
It's like new Tinder scroll that uncovers reactions (2017-11-17)
Any ideas how to do it?
Try this :
recyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
#Override
public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
super.onScrollStateChanged(recyclerView, newState);
}
#Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
super.onScrolled(recyclerView, dx, dy);
}
});
In the onScrolled method you can translate the whole recyclerView.
To check if you have reached the end then use this method in the onScrolled method
recyclerView.canScrollVertically(1);
This will return a boolean. Then you can check and move the view.
Hope this helps.
OK, solved it my self. when i talked about glitching onTouch i made a mistake. I set translation to my RecyclerView, but i used MotionEvent.getX() instead of MotionEvent.getRawX() which caused this wild glitch due to inconsistent touch coordinate returned by getX()
Also
recyclerView.canScrollVertically(1);
by Sarthak Gandhi helped.
override fun onTouch(p0: View?, p1: MotionEvent): Boolean {
if (p1.action == MotionEvent.ACTION_UP && dragging) {
dragging = false
mInitailPoint = null
currentDy.invoke(0F)
return true
}
if (p1.action == MotionEvent.ACTION_DOWN || p1.action == MotionEvent.ACTION_MOVE) {
if (!dragging && !recyclerView.canScrollVertically(1)) {
mInitailPoint = Point(p1.rawX.toInt(), p1.rawY.toInt())
mCurrentPoint = Point(p1.rawX.toInt(), p1.rawY.toInt())
dragging = true
return true
} else if (dragging && p1.action == MotionEvent.ACTION_MOVE) {
mCurrentPoint = Point(p1.rawX.toInt(), p1.rawY.toInt())
val dy = mCurrentPoint!!.y.toFloat() - mInitailPoint!!.y.toFloat()
if (dy < 0) {
currentDy.invoke(dy)
} else {
currentDy.invoke(0F)
dragging = false
return false
}
return true
}
}
return false
}
at this point currentDy.invoke(dy) actually sets translation to RecyclerView of course this isn't complete code to achieve tinder stuff and there is no fling animations, but this part is what the question about.
Moral of the story: if you set OnTouchListener for the view you want to translate - use raw coordinates to calculate translation

How to load more records on scrolling in tv android box

I have set up setOnScrollListener event on my listview its working good in mobile here is my code
movieList.setOnScrollListener(new AbsListView.OnScrollListener() {
#Override
public void onScrollStateChanged(AbsListView view, int scrollState) {
if(scrollState == AbsListView.OnScrollListener.SCROLL_STATE_TOUCH_SCROLL){
userScrolled = true;
}
}
#Override
public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
if(userScrolled && (firstVisibleItem + visibleItemCount >= totalItemCount)){
pageNum++;
int index = movieList.getFirstVisiblePosition();
float indexY = movieList.getScrollY();
load_loader.setVisibility(View.VISIBLE);
getMovies();
movieList.setSelection(index);
userScrolled = false;
}
}
});
Now i have installed my app to android tv box my full app is working good in tv box but list view not loading more records i think because of touch scroll tv is not touch its operated by remote navigation please help me to solve this problem thank you
Try setting listView.setItemsCanFocus(true); to your ListView so that focusable views inside of your listItems won't be ignored. Source.
You may also check this documentation on how you can actually navigate around your app when using remote control buttons instead of a touch screen.

OnScoll called multiple times in ListView

While using this, if i scroll a small amount, why is onScroll() called for 20 or so times i.e 5-6 times for each firstVisibleItem. Why not its called once for each scroll action?
listView.setOnScrollListener(new OnScrollListener() {
public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
Log.d("hey","called"+firstVisibleItem);
}
public void onScrollStateChanged(AbsListView view, int scrollState) {
}
}
});
If you look at implentation of ListView, you can see that mOnScrollListener.onScroll is called only once in ListView, actually in invokeOnItemScrollListener() method.
void invokeOnItemScrollListener() {
if (mFastScroll != null) {
mFastScroll.onScroll(mFirstPosition, getChildCount(), mItemCount);
}
if (mOnScrollListener != null) {
mOnScrollListener.onScroll(this, mFirstPosition, getChildCount(), mItemCount);
}
onScrollChanged(0, 0, 0, 0); // dummy values, View's implementation does not use these.
}
So let's try find some usages of invokeOnItemScrollListener(). We got a lot, including usages in AbsListView. In AbsListView, invokeOnItemScrollListener() is called indirectly every time in onTouchMove method. So now, it should be clear why you receive onScroll callback so many times.

Detect Top of ListView Scroll

I want to detect the Top of List view and i am using this method.
#Override
public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
if (firstVisibleItem == 0)
swipeRefreshLayout.setEnabled(true);
else
swipeRefreshLayout.setEnabled(false);
}
This works fine, but the problem is I have attached the Header View to list as well. When I scrolled up, as soon as the first item is visible(not the Header view) it calls pull to refresh of list view. How can i detect that the List's Header is completely visible.
My List View Is
View imageSlider = inflater.inflate(R.layout.image_slider_layout, null, false);
findViewById(imageSlider);
mPullRefreshListView.addHeaderView(imageSlider);
private void findViewById(View view) {
mViewPager = (ViewPager) view.findViewById(R.id.view_pager);
mIndicator = (CirclePageIndicator) view.findViewById(R.id.indicator);
}
hmm, Interesting answer lies in onTouch listener rather then onScroll, see the implementation below, it exactly tells u when header is completely visible, you can refine the logic further.. its just a quick implementation form me.
// Set a on touch listener for your list
mList.setOnTouchListener(new OnTouchListener(){
#Override
public boolean onTouch(View v, MotionEvent event) {
// get the top of first child
View mView = mList.getChildAt(0);
int top = mView.getTop();
switch(event.getAction()){
case MotionEvent.ACTION_MOVE:
// see if it top is at Zero, and first visible position is at 0
if(top == 0 && mList.getFirstVisiblePosition() == 0){
Toast.makeText(MainActivity.this, "Header Item Visible",
Toast.LENGTH_SHORT).show();
}
}
// dont forget to retrun false here
return false;
}
});
You can simply identifies it with visibleitemCount in onScroll Listener also.
Firstly identify your listview's visible item count at the top of listview include the header. you can get the count by toasting or debugg the code.
in my case
my listview's normal visibleItemCount is 3 and at the place of top it is 2 (with headerview), then my code is work for me.
#Override
public void onScroll(AbsListView view, int firstVisibleItem,
int visibleItemCount, int totalItemCount) {
if(firstVisibleItem==0&&visibleItemCount==2)
{
swp.setEnabled(true);
}
else{
swp.setEnabled(false);
}
}

How to find out if ListView has scrolled to top Most position?

I have a ListView, first its scrolled down, now when we scroll up,it reach top most point. I want to detect that .Is there any way?I am developing application with api level 8.Pls help..
edit
See comments below as to why, especially on different API versions (esp later ones), this isn't a foolproof way to see if you're list is at the top (padding etc). However, it does give you a start for a solution on devices below API 14:
private boolean listIsAtTop() {
if(listView.getChildCount() == 0) return true;
return listView.getChildAt(0).getTop() == 0;
}
As far as my implementation years ago - this worked perfectly at the time.
I know this question is old, but it shows up top in Google search results. There is a new method introduced in API level 14 that gives exactly what we needed:
http://developer.android.com/reference/android/view/View.html#canScrollVertically%28int%29
For older platforms one can use similar static methods of ViewCompat in the v4 support library. See edit below.
Unlike Graeme's method, this method is immune of problems caused by the internal view reuse of ListView and/or header offset.
Edit: final solution
I've found a method in the source code of SwipeRefreshLayout that handles this. It can be rewritten as:
public boolean canScrollUp(View view) {
if (android.os.Build.VERSION.SDK_INT < 14) {
if (view instanceof AbsListView) {
final AbsListView absListView = (AbsListView) view;
return absListView.getChildCount() > 0
&& (absListView.getFirstVisiblePosition() > 0 || absListView
.getChildAt(0).getTop() < absListView.getPaddingTop());
} else {
return view.getScrollY() > 0;
}
} else {
return ViewCompat.canScrollVertically(view, -1);
}
}
You may need to add custom logic if the passed-in view is a custom view.
My friends, combining Graeme's answer with the onScroll method...
listView.setOnScrollListener(new AbsListView.OnScrollListener() {
#Override
public void onScrollStateChanged(AbsListView view, int scrollState) {
}
#Override
public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
if(firstVisibleItem == 0 && listIsAtTop()){
swipeRefreshLayout.setEnabled(true);
}else{
swipeRefreshLayout.setEnabled(false);
}
}
});
private boolean listIsAtTop() {
if(listView.getChildCount() == 0) return true;
return listView.getChildAt(0).getTop() == 0;
}
You will need to check what is the first visible position then applying Graeme's solution to see if the first visible listview item is at the top position.
Something like
lv.getFirstVisiblePosition() == 0 && (lv.getChildCount() == 0 || lv.getChildAt(0).getTop() == 0)
You can use an OnScrollListener to be notified the position 0 is now visible. Use the onScrollmethod.
This question is old but I have a solution that works perfectly and it is possible that works for someone looking for a solution.
int limitRowsBDshow = 10; //size limit your list
listViewMessages.setOnScrollListener(new AbsListView.OnScrollListener() {
int counter = 1;
int currentScrollState;
int currentFirstVisibleItem;
int currentVisibleItemCount;
int currentTotalItemCount;
#Override
public void onScrollStateChanged(AbsListView view, int scrollState) {
this.currentScrollState = scrollState;
this.isScrollCompleted();
}
#Override
public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
this.currentFirstVisibleItem = firstVisibleItem;
this.currentVisibleItemCount = visibleItemCount;
this.currentTotalItemCount = totalItemCount;
}
private void isScrollCompleted() {
if (this.currentVisibleItemCount > 0 && this.currentScrollState == SCROLL_STATE_IDLE) {
/*** detect if there's been a scroll which has completed ***/
counter++;
if (currentFirstVisibleItem == 0 && currentTotalItemCount > limitRowsBDshow - 1) {
linearLay20msgMas.setVisibility(View.VISIBLE);
}
}
}
});
This code is found a time ago (here StackOverflow). But I can not find this to mention
Graeme's answer is close but is missing something that user2036198 added, a check for getFirstVisiblePosition(). getChildAt(0) doesn't return the very first view for the very first item in the list. AbsListView implementations don't make a single view for every position and keep them all in memory. Instead, view recycling takes effect to limit the number of views instantiated at any one time.
The fix is pretty simple:
public boolean canScrollVertically(AbsListView view) {
boolean canScroll = false;
if (view != null && view.getChildCount() > 0) {
// First item can be partially visible, top must be 0 for the item
canScroll = view.getFirstVisiblePosition() != 0 || view.getChildAt(0).getTop() != 0;
}
return canScroll;
}
For best results on ICS or higher, always use ViewCompat from the v4 support library or View.canScrollVertically(). Use the above method on lower API levels as ViewCompat always returns false for canScrollVertically() and canScrollHorizontally() below ICS.
If you can extends ListView directly, then you can use the protected method called "computeVerticalScrollOffset()" inside the override method "onScrollChanged()".
With that protected method return 0, means that your ListView is now reached at top.
Code Snippet
listView = new ListView(this){
#Override
protected void onScrollChanged(int l, int t, int oldl, int oldt) {
super.onScrollChanged(l, t, oldl, oldt);
if( computeVerticalScrollOffset() == 0 ){
// Reach top
}
}
Too late but try this one it works well in RecyclerView.
-1 to check if it can scroll to top while 1 is to check if it can scroll to bottom
if (listView.canScrollVertically(-1))
listView.smoothScrollToPosition(0);
else
onBackPressed();
lstView.setOnScrollListener(new AbsListView.OnScrollListener() {
#Override
public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
//To change body of implemented methods use File | Settings | File Templates.
if (0 == firstVisibleItem){
Toast.makeText(MyActivity.this, "Scroll to Top ", Toast.LENGTH_SHORT).show();
}
}
});

Categories

Resources