Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

3 pane layout fragment animation is stuttering

The implementation info: I have implemented a 3 pane layout following CommonsWare response posted on his own question over here: Complete Working Sample of the Gmail Three-Fragment Animation Scenario?

As a general idea, I have the layout comprised of following levels (1 through 3):

  1. MainActivity
  2. SlidingMenu (Side Drawer UI pattern) fragment hiding on the left side and a ContentFragment as the fragment that houses the 3 Pane Layout.
  3. Inside the ContentFragment: LeftListFragment (rows with 3 TextViews each), MiddleListFragment (rows with 8 TextViews each), DetailFragment.

LeftListFragment and MiddleListFragment use CursorLoaders to load data from a ContentProvider inside each list. DetailFragment also calls for a cursor with data when needed. So I didn't even implement custom Adapters (much nicer design this way). Then I have added the 3 Pane layout + animations. As far as working, it works as intended, no problems there. Animation time is 500ms.

The problem: The animations stutter a little. A few dropped frames. Both when the Left and Middle are visible and I click on a Middle list item to open a Detail; and also when I tap the Back button to see again Left and Middle lists (when nothing is actually loading).

What I've tried:

  1. Removed the code that loads the fragment in DetailView. I just tap an item in MiddleFragment and the animation begins, without any Detail actually loading. Still stutters. Also, when hitting back, nothing loads and it still stutters, so I presume the loaders/cursors are not the cause of this.
  2. I used dumpsys gfxinfo to see the average time in ms to compute each frame. Indeed, the avg time for a computation is 18ms, which is above the 16ms threshold. So does this mean the stuttering is because of the time it takes to draw again the lists, when animating ? If so, why ? I mean... I don't have any images at all inside row views. And I couldn't screw up the Adapters code, because I haven't written any...
  3. Reducing the Animation time from 500ms to 200ms. It still stutters if you watch it really carefully, it's just faster.

EDIT: I switched from rightPaneWidth to leftPaneWidth below (yes, that removes the re-dimensioning animation) and the stuttering is now gone. The list still slides to the left side, but it just doesn't get smaller in width. So if there is no more stuttering now, does that mean there is a problem with the ObjectAnimator in my code ?

ObjectAnimator.ofInt(this, "middleWidth", rightPaneWidth, leftPaneWidth)
                    .setDuration(ANIM_DURATION).start();

Thanks for your time !

Code for 3 Pane Layout:

package com.xyz.view.widget;

import android.animation.ObjectAnimator;
import android.annotation.SuppressLint;
import android.annotation.TargetApi;
import android.content.Context;
import android.util.AttributeSet;
import android.view.View;
import android.view.ViewPropertyAnimator;
import android.widget.LinearLayout;


public class ThreePaneLayout extends LinearLayout
{
    private View leftView = null;
    private View middleView = null;
    private View rightView = null;

private static final int ANIM_DURATION = 500;
private int leftPaneWidth = -1;
private int rightPaneWidth = -1;


// -------------------------------------------------------------------------------------------
// --------------   Constructor
// -------------------------------------------------------------------------------------------


public ThreePaneLayout(Context context, AttributeSet attrs)
{
    super(context, attrs);
    setOrientation(HORIZONTAL);
}

@Override
public void onFinishInflate()
{
    super.onFinishInflate();
    leftView = getChildAt(0);
    middleView = getChildAt(1);
    rightView = getChildAt(2);
}


// -------------------------------------------------------------------------------------------
// --------------   Public methods
// -------------------------------------------------------------------------------------------


public View getLeftView()
{
    return leftView;
}

public View getMiddleView()
{
    return middleView;
}

public View getRightView()
{
    return rightView;
}

@SuppressLint("NewApi")
public void hideLeft()
{
    if (leftPaneWidth == -1)
    {

        leftPaneWidth = leftView.getWidth();
        rightPaneWidth = middleView.getWidth();
        resetWidget(leftView, leftPaneWidth);
        resetWidget(middleView, rightPaneWidth);
        resetWidget(rightView, rightPaneWidth);
        requestLayout();
    }
    translateWidgets(-1 * leftPaneWidth, leftView, middleView, rightView);
    ObjectAnimator.ofInt(this, "middleWidth", rightPaneWidth, leftPaneWidth)
                    .setDuration(ANIM_DURATION).start();
}

@SuppressLint("NewApi")
public void showLeft()
{
    translateWidgets(leftPaneWidth, leftView, middleView, rightView);
    ObjectAnimator.ofInt(this, "middleWidth", leftPaneWidth, rightPaneWidth)
                    .setDuration(ANIM_DURATION)
                    .start();
}


// -------------------------------------------------------------------------------------------
// --------------   Private methods
// -------------------------------------------------------------------------------------------


private void setMiddleWidth(int value)
{
    middleView.getLayoutParams().width = value;
    requestLayout();
}

@TargetApi(12)
private void translateWidgets(int deltaX, View... views)
{
    for (final View view : views)
    {
        ViewPropertyAnimator viewPropertyAnimator = view.animate();
        viewPropertyAnimator.translationXBy(deltaX)
                            .setDuration(ANIM_DURATION);
    }
  }

  private void resetWidget(View view, int width)
  {
      LinearLayout.LayoutParams layoutParams = (LinearLayout.LayoutParams)view.getLayoutParams();  
      layoutParams.width = width;
      layoutParams.weight = 0;
  }
}

XML for the ContentFragment:

    <?xml version="1.0" encoding="utf-8"?>
<com.xyz.view.widget.ThreePaneLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/fragment_content_three_pane_layout"
    android:layout_width="match_parent"
    android:layout_height="match_parent" >

<FrameLayout
    android:id="@+id/fragment_content_framelayout_left"
    android:layout_width="0dp"
    android:layout_height="match_parent"
    android:layout_weight="3" />

<FrameLayout
    android:id="@+id/fragment_content_framelayout_middle"
    android:layout_width="0dp"
    android:layout_height="match_parent"
    android:layout_weight="7" />

<FrameLayout
    android:id="@+id/fragment_content_framelayout_right"
    android:layout_width="0dp"
    android:layout_height="match_parent" />
</com.xyz.view.widget.ThreePaneLayout>
like image 641
Bogdan Zurac Avatar asked Dec 27 '22 10:12

Bogdan Zurac


1 Answers

The problem is not ObjectAnimator, but rather that your application is simply doing too much on every frame of the animation. Specifically, you are animating layout params and requesting layout on every frame. Layout is powerful and useful... but can be quite expensive in any but the most trivial of view hierarchies. It's important to avoid expensive operations per-frame during animations, and layout falls into that "expensive" category. Sliding things around is fine (translationX/Y), fading things in/out is good (alpha), but actually laying things out on every frame? Just say no.

like image 140
Chet Haase Avatar answered Jan 09 '23 07:01

Chet Haase