Android Jittery Scrolling Gallery

This article is part 7 of 11 in the series Android Development

Recently I had a problem with an Android app in which I used a Gallery to scroll horizontally between some items. It worked pretty well, but all of a sudden, without changing the Gallery code at all, it started to jitter weirdly when I scrolled the Gallery.

To be more precise, when I touched the Gallery and slowly dragged my finger to the side, the items would at first follow my finger, only to jump back to their original position. And the moment I moved my finger again, they would follow the gesture again.

If you’ve seen this behaviour in your own code, the following may help.

First of all, you’re probably not using the Gallery wrong. What happened in my case is that I had other views in the Activity, some of which received regular updates, like showing a countdown. If you’ve got any views like that in your Activity, chances are you’re in the same situation I was.

I ended up looking through the Gallery code, and noticed that in it’s onLayout() function it actually clears all its child views, and re-adds the visible ones. At first sight, that seems like a great thing to do, right?

The only problem is that if any other view in same ViewGroup is invalidate()ed, the whole group gets invalidated. I’m not sure that should happen, given this documentation here:

Drawing is handled by walking the tree and rendering each view that intersects the the invalid region. Because the tree is traversed in-order, this means that parents will draw before (i.e., behind) their children, with siblings drawn in the order they appear in the tree. If you set a background drawable for a View, then the View will draw it for you before calling back to its onDraw() method.

Note that the framework will not draw views that are not in the invalid region.

To force a view to draw, call invalidate().

Unfortunately, the Android documentation appears to be a bit vague on how, exactly, the invalid region is defined; I’d normally understand this to be any view or view group “behind” the invalidated view in terms of z-order.

Unless the invalidated view is opaque, in which case there’s probably no need to invalidate other views. But I was updating a TextView, and those are rarely opaque.

As you can see, I’m not entirely sure what is going wrong here, and where. What I do know, though, is that it doesn’t really make sense for Gallery to recreate all its child views while you’re scrolling around in it. And that, at least, is easily avoided.

All you need to do is subclass Gallery and override two functions1:

private long mLastScrollEvent;
 
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b)
{
  long now = SystemClock.uptimeMillis();
  if (Math.abs(now - mLastScrollEvent) > 250) {
    super.onLayout(changed, l, t, r, b);
  }
}
 
@Override
public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX,
    float distanceY)
{
  mLastScrollEvent = SystemClock.uptimeMillis();
  return super.onScroll(e1, e2, distanceX, distanceY);
}

What happens, simple, is that when a scroll event is detected, the current timestamp is remembered. If an attempt to call onLayout() is made within a short timespan, the re-layout is ignored.

It’s a hack, yes, but it’s solved the problem. Note that the 250msec I’m waiting here is probably way too generous; on my Nexus One, touch events get fired every ca. 20msec or less if I’m scrolling.

I hope this helps anyone. I’m hoping even more that future versions of Android fix the issue in a less hackish fashion.

Finally, if you want to follow progress on this issue, just go to the bugreport.

  1. You may need to override more because you’re subclassing a View, but that’s not really part of this problem. []

  • mm

    Hi,

    Your solution seems to move my gallery smoother, but sometimes images out of the screen don’t load when I scroll to them, so I see an empty space. Anyway, thanks it was quite helpful.

    • http://www.unwesen.de/ unwesen

      You’re welcome!

      Maybe that’s why they rebuild the entire list of child views in the original code? I haven’t seen anything that *should* cause your problem, but admittedly I wasn’t looking for that :(

  • Powellzer

    Yes it is a hack, but isn’t everything in android?  Thank you very much for diagnosing and documenting this, it saved me from a rather large headache.  I came across this issue when attempting to load images from the web into the each of the Gallery’s views.  Doing this in an AsyncTask and using ImageView.setImageBitmap() method in the onPostExecute() seems to invalidate that given view, causing the Gallery to redraw and jitter.

    • http://www.unwesen.de/ unwesen

      I’m glad it’s of use to you!

      • Powellzer

        Still had a bit of jitter when a user flings the gallery at a specific velocity.  By overriding the onFling method in the gallery I was able to adjust the wait time proportional to the velocity of the fling.  The below solution worked well for me (again in a gallery with 5-10 web requested images).

        private float mFlingMultiplier;
        @Override
        public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
        mFlingMultiplier = Math.abs(velocityX/100);
        return super.onFling(e1, e2, velocityX, velocityY);
        }
        Changing the conditional redraw in onLayout() to: if (Math.abs(now – mLastScrollEvent) > 500 * mFlingMultiplier) {    super.onLayout(changed, l, t, r, b);  }
        Also in onScroll() you should set the mFlingMultiplier = 1 so that the onLayout is not effected by previous fling events when scrolling.

        Again thanks for your help with diagnosing this bug.  This fix now has my app running much smoother.

        • http://www.unwesen.de/ unwesen

          That’s really cool, thanks :)

  • Anthony

    Ingenious solution for a nasty problem.  It’s possible a simple ‘skipLayout’ flag in onLayout() of your Coverflow  may do the trick, but after a lot of effort, the solution posted here is the best work around I’ve seen yet for our case. 

    • http://www.unwesen.de/ unwesen

      Thanks! I’m glad it works for you!

  • Morten Slott Hansen

    I still had some jitter issues with this solution. My solution was similar to yours – but only override onLayout.

    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
    int viewsOnScreen = getLastVisiblePosition() – getFirstVisiblePosition();
    if(viewsOnScreen <= 0)
    super.onLayout(changed, l, t, r, b);

    }

    • Dw86

      worked a treat for me, elegant and less hacky

      • http://www.unwesen.de/ unwesen

        So, what exactly is the logic here?

        (last – first) > 0 if there is content to be displayed. So when there’s no content to be displayed, you call onLayout() – does that mean it’s called before child views are added, or what?

        It seems to work. I wouldn’t call it less hacky, though, because it depends on the point in time when children are added – which isn’t under our control.

        Definitely more elegant, though.

    • Sowjanya Yerramneni

      Hi,
      I am new to Android and…
      I am having the image scroll issue where the image suddenly jerks while scrolling up the moment the previous image comes into view. I am using a list view and the images are displayed using an arrayadapter. Please advise as I have no method onLayout to override.

  • Abc

    This was so useful man. I have been struggling with this for a while now and this has been holding a release for us. You saved my a$$ man.

    Cheers

    • http://www.unwesen.de/ unwesen

      Glad this helped!

  • Pingback: Android Gallery Transition Between Images | PHP Developer Resource

  • Juan Acevedo

    Good idea, but I also added a check in onScroll:
    if (Math.abs(distanceX) > 2.5){
    return super.onScroll(e1, e2, distanceX, distanceY);
    }else{
    return false;
    }

    This works as a high pass filter and removes little noise created by your finger touching the screen. I found distances under 2.5 to cause it to jitter also.