1

I'm using a 3rd party drawer that allows bottom- and top-edge drawers (https://gist.github.com/patrickfav/6284130 MultipleOrientationSlidingDrawer).

When I animate opening of the drawer, I get display tearing but only under particular circumstances. When the drawer header is clicked, animation is smooth and beautiful.

I have a RecyclerView in the main window. If users click on an entry in the list view, the drawer slides open showing details of the entry selected (click on a track, slide the media controls up from the bottom). Part of the drawer animation is to fade the main window contents to black as the drawer opens. So there's heavy overdraw going on. Oddly, when the animation is triggered from clicking in the RecyclerView, I get tearing of the display during animation (serious flickering, occurring about 2/3 of the way down from the top of the screen).

I'm looking for advice as to how to proceed: how to debug, or theories as to what might be causing flickering of the display during animation.

Here's what I've tried so far.

Initially, I though this was caused by RippleDrawable animations in the RecyclerView entries (both on the press and an activition change that is used to indicate selection. To prevent this, I've tried to cancel the ripple animations by calling recylerView.jumpDrawablesToCurrentState() at various points (start of animation, as well as during every animation update). As far as I can tell, the RippleDrawable animations have been effectively cancelled. But the display ripping still occurs. Things are a bit complicated. State change animations don't get triggered until after a dispatch of some kind occurs; but the call to startAnimation also doesn't appear to occur until after drawable state changes are complete. So I think I've done this right. Just in case, I've tried calling recyclerView.jumpDrawablesToCurrentState during each animation pass, which certinly should cancel the drawable animations. Still no joy.

I have deferred responses to the click itself until the drawer open animation completes, so there are no major background or foreground operations going on while the animation occurs. Also no audio running, no service activity to speak of. Profiling indicates that there's no code running that isn't related to drawing while the animation runs.

Since everything happens relatively quickly, it's hard to tell exactly what's happening, other than that there's serious flickering going on. Since there's an animation involved, there's no way to break to the debugger in the middle of the animation to see what's going on. Once I break to the debugger, the next redraw will be fully-open state, since the animation positions derive from the current device uptimeMillis().

If I profile the animation, I see an awful lot of text layout operations, which kind of suggest that the recycler view entries might be constantly performing layout during the animation.. Which doesn't really make sense. There's no reason I can think of for layout to occur. It could just be that the text calls are related to onDraw operation. But I mention this because what I did see in profiling seems a bit odd. The majority of CPU time during the animation seems to be spent performing text measurement operations, presumably for content in the RecyclerView entries.

There is heavy overdraw occurring. The drawer itself has a large bitmap containing album artwork, And it's possible that I may have stacked up semi-opaque layers and backgrounds. e.g. the RippleDrawable backgrounds on individual entries in the RecyclerView could very well cause a complete overdraw pass for most of the screen. In addition, there is a complete overdraw pass for the view that dims out the background content as the drawer animates into open position. That being said, the drawer animates beautifully when the animation is triggered by clicking the header of the drawer. Just not when the click that starts the animation comes from the RecyclerView. So I don't think overdraw is really the problem.

For what its worth, the location of display ripping seems to be independent of where the selection and press occurs. Clicking first or last entries in the RecyclerView doesn't affect where the display-ripping occurs. ADB shell dumpsys SurfaceFlinger indicates that hardware composition is being performed on three layers (status bar, navigaton bar, and main activity). So the drawer itself is not using SurfaceFlinger composition (which would, if it were happening at least point in the general direction of what's causing tearing of the display). However, I can't really get the SurfaceFlinger state while the animation is running. It's possible that hardware compositing is being used during the animation. It sure looks like display ripping, and I can't think of why ripping should occur if SurfaceFLinger isn't doing the compositing.

I'm completely mystified as to what the problem might be, or approaches to try and debug this. Any suggestions as to general debugging approach, or suggestions as to what the cause of the problem might would be much appreciated.

Robin Davies
  • 7,547
  • 1
  • 35
  • 50

1 Answers1

0

On what layer is the described tearing happening? Is it on the drawer itself, or on the layout behind the drawer?

Either way, I have a couple of suggestions:

  1. Overdrawing can be glitchy at times, especially with complicated layouts - I have run into issues with it specifically on GoogleMaps Fragments. Something that resolved this in my case was creating a simple transparent overlay to cast the overdraw on, like so:

    <FrameLayout for drawer....>
    
    <View
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="@android:color/transparent" />
    
    <LinearLayout w/ fragments etc..../>
    
    </FrameLayout>
    
  2. Drawer Animations are notoriously bad for their intended purpose... they will lag like crazy during layout transitions. I know you said you made sure the drawer animation finished before doing anything, but that is something worth double-checking. I personally put all of my navigation code in the OnDrawerClosed() method for this reason, and use the itemSelected() code to set a flag and close the drawer.

Demonsoul
  • 791
  • 4
  • 11
  • Tearing occurs on the drawer itself, in the middle of the album artwork bitmap (if that matters). Item 1. is pretty interesting. Any theories as to why that would make a difference? There must be something going on in the RecyclerView after the onClick event occurs. ANd there probably is. Refreshing drawables for instance occurs in a posted event. As does relayout, and drawing. The previous and current selected items have also been invalidated, so rebinding will also happen in a dispatched event after the click. Getting clear of all that is not exactly easy though. – Robin Davies Dec 18 '15 at 16:55
  • Yeah, I can certainly see why you're so mystified by this problem; this sounds like one of those strange Android quirks that are really tough to debug. Unfortunately I'm not aware of any easy way to debug weird UI occurrences myself... personally I would just try to simplify the layout as much as possible and isolate the issue through trial and error... remove the overdraw, replace the bitmap with a more generic view, remove the (seemingly) unaffected elements of the drawer etc. Sorry I couldn't be of more assistance. – Demonsoul Dec 18 '15 at 18:19