Анимация фрагмента 3-х панелей заикается

Информация о реализации: Я реализовал 3-х панельный макет после ответа CommonsWare, размещенного по его собственному вопросу: Полный рабочий пример сценария анимации с тремя фрагментами Gmail

Как общая идея, у меня есть макет, состоящий из следующих уровней (от 1 до 3):

  • MainActivity
  • SlidingMenu (паттерн пользовательского интерфейса боковой панели), скрывающийся с левой стороны, и ContentFragment в качестве фрагмента, в котором размещается макет 3-х панелей.
  • Внутри ContentFragment: LeftListFragment (строки с 3 текстовыми полями), MiddleListFragment (строки с 8 текстовыми полями каждый), DetailFragment.

LeftListFragment и MiddleListFragment используйте CursorLoaders для загрузки данных из ContentProvider внутри каждого списка. DetailFragment также вызывает курсор с данными при необходимости. Поэтому я даже не реализовал пользовательский Adapters (гораздо более приятный дизайн). Затем я добавил 3 панели макета + анимации. Что касается работы, он работает по назначению, никаких проблем нет. Animation время составляет 500 мс.

Проблема: Анимации немного заикаются. Несколько отброшенных кадров. И когда видны Left и Middle, и я нажимаю на элемент Middle list, чтобы открыть Detail; а также когда я нажимаю кнопку "Назад", чтобы снова увидеть списки "Левый" и "Средний" (когда ничего не загружается).

Что я пробовал:

  • Удален код, загружающий фрагмент в DetailView. Я просто нажимаю элемент в MiddleFragment и начинается анимация, без какой-либо детализации, фактически загружаемой. Все еще заикается. Кроме того, при ударе назад ничего не загружается, и он все еще заикается, поэтому я полагаю, что загрузчики/курсоры не являются причиной этого.
  • Я использовал dumpsys gfxinfo, чтобы увидеть среднее время в мс для вычисления каждого кадра. Действительно, среднее время для вычисления составляет 18 мс, что выше порога 16 мс. Значит ли это, что заикание происходит из-за времени, необходимого для повторного рисования списков при анимации? Если да, то почему? Я имею в виду... У меня нет изображений вообще внутри строк. И я не мог испортить код Adapters, потому что я не написал ничего...
  • Сокращение времени Animation от 500 мс до 200 мс. Он все еще заикается, если вы смотрите внимательно, это происходит быстрее.

EDIT: Я переключился с rightPaneWidth на leftPaneWidth ниже (да, это удаляет анимацию переразмера), и заикание теперь исчезло. Список по-прежнему скользит влево, но он просто не становится меньше по ширине. Итак, если больше не заикается, значит ли это, что в моем коде есть проблема с ObjectAnimator?

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

Спасибо за ваше время!

Код для 3-х панельных макетов:

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 для 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>

Ответ 1

Проблема не в ObjectAnimator, а в том, что ваше приложение просто делает слишком много для каждого кадра анимации. В частности, вы анимируете параметры макета и запрашиваете макет на каждом фрейме. Макет является мощным и полезным... но может быть довольно дорогостоящим в любой, но самой тривиальной иерархии представлений. Важно избегать дорогостоящих операций за кадр во время анимации, а макет попадает в эту "дорогую" категорию. Раздвижные вещи прекрасны (translationX/Y), угасание вещей в/из хорошо (альфа), но на самом деле прокладывает вещи на каждом кадре? Просто скажите "нет".

Ответ 2

Я закончил полностью удаление ObjectAnimator... Конечно, у него все еще есть анимация слайдов, но в то же время не выполняется плавное изменение размера. Не то, чтобы это было действительно гладкое...

В любом случае, если кто-то действительно придумает реальное решение этой проблемы, не стесняйтесь делиться. Спасибо.

package com.anfuddle.view.widget;

import android.annotation.TargetApi;
import android.content.Context;
import android.os.Build;
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 rootWidth = -1;
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;
}

@TargetApi(Build.VERSION_CODES.HONEYCOMB)
public void hideLeftAndMiddle()
{
    if (leftPaneWidth == -1)
    {
        rootWidth = getWidth();
        leftPaneWidth = leftView.getWidth();
        rightPaneWidth = middleView.getWidth();
    }
    resetWidget(leftView, leftPaneWidth);
    resetWidget(middleView, rightPaneWidth);
    resetWidget(rightView, rootWidth);
    requestLayout();

    translateWidgets(-1 * rootWidth, leftView, middleView, rightView);
}

@TargetApi(Build.VERSION_CODES.HONEYCOMB)
public void hideLeft()
{
    if (leftPaneWidth == -1)
    {
        leftPaneWidth = leftView.getWidth();
        rightPaneWidth = middleView.getWidth();
    }
    resetWidget(leftView, leftPaneWidth);
    resetWidget(middleView, leftPaneWidth);
    resetWidget(rightView, rightPaneWidth);
    requestLayout();
    translateWidgets(-1 * leftPaneWidth, leftView, middleView, rightView);
}

@TargetApi(Build.VERSION_CODES.HONEYCOMB)
public void showLeftAndMiddle()
{
    translateWidgets(rootWidth, leftView, middleView, rightView);
}

@TargetApi(Build.VERSION_CODES.HONEYCOMB)
public void showLeft()
{
    resetWidget(leftView, leftPaneWidth);
    resetWidget(middleView, rightPaneWidth);
    resetWidget(rightView, rightPaneWidth);
    requestLayout();
    translateWidgets(leftPaneWidth, leftView, middleView, rightView);
}


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


@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;
  }
}