Полный рабочий пример сценария трехфрагментной анимации Gmail?


128

TL; DR: Я ищу полный рабочий образец того, что я буду называть сценарием «трехфрагментной анимации Gmail». В частности, мы хотим начать с двух фрагментов, например:

два фрагмента

После некоторого события пользовательского интерфейса (например, касания чего-либо во фрагменте B) мы хотим:

  • Фрагмент A, чтобы соскользнуть с экрана влево
  • Фрагмент B переместится к левому краю экрана и уменьшится, чтобы занять место, освобожденное фрагментом A
  • Фрагмент C, чтобы проскользнуть из правой части экрана и занять место, освобожденное фрагментом B

И при нажатии кнопки BACK мы хотим, чтобы этот набор операций был отменен.

Я видел множество частичных реализаций; Я рассмотрю четыре из них ниже. Помимо того, что они неполные, у всех есть свои проблемы.


@Reto Meier предоставил этот популярный ответ на тот же основной вопрос, указав, что вы будете использовать setCustomAnimations()с расширением FragmentTransaction. Для сценария с двумя фрагментами (например, вы сначала видите только фрагмент A и хотите заменить его новым фрагментом B с использованием анимированных эффектов) я полностью согласен. Тем не мение:

  • Поскольку вы можете указать только одну «входящую» и одну «выходную» анимацию, я не понимаю, как вы будете обрабатывать все различные анимации, необходимые для сценария с тремя фрагментами.
  • В <objectAnimator>его образце кода используются жестко привязанные позиции в пикселях, и это может показаться непрактичным с учетом различных размеров экрана, но setCustomAnimations()требует ресурсов анимации, что исключает возможность определения этих вещей в Java.
  • Я в недоумении, как объект аниматоры для масштаба галстука в таких вещах , как android:layout_weightв LinearLayoutвыделение пространства на основе процента
  • Я не понимаю, как фрагмент C обрабатывается с самого начала ( GONE? android:layout_weightИз 0? Предварительно анимированных до шкалы 0? Что-то еще?)

@Roman Nurik указывает, что вы можете анимировать любое свойство , включая те, которые вы определяете сами. Это может помочь решить проблему жестко зашитых позиций за счет изобретения собственного подкласса настраиваемого менеджера компоновки. Некоторым это помогает, но остальное решение Reto меня все еще сбивает с толку.


Автор этой записи pastebin показывает некоторый дразнящий псевдокод, в основном говоря, что все три фрагмента изначально будут находиться в контейнере, а фрагмент C будет скрыт с самого начала посредством hide()операции транзакции. Затем мы show()C и hide()A, когда происходит событие пользовательского интерфейса. Однако я не понимаю, как это связано с тем, что B меняет размер. Он также основан на том факте, что вы, по-видимому, можете добавить несколько фрагментов в один и тот же контейнер, и я не уверен, является ли это надежным поведением в долгосрочной перспективе (не говоря уже о том, что оно должно сломаться findFragmentById(), хотя я могу с этим жить).


Автор этого сообщения в блоге указывает, что Gmail вообще не использует setCustomAnimations(), а вместо этого напрямую использует аниматоры объектов («вы просто меняете левое поле корневого представления + меняете ширину правого представления»). Тем не менее, это все еще двухфрагментное решение AFAICT, и реализация снова показала жесткие размеры в пикселях.


Я продолжу заниматься этим, так что я, возможно, когда-нибудь сам отвечу на этот вопрос, но я действительно надеюсь, что кто-то разработал решение из трех фрагментов для этого сценария анимации и может опубликовать код (или ссылку на него). Анимация в Android заставляет меня выдергивать волосы, и те из вас, кто видел меня, знают, что это в значительной степени бесплодное занятие.


2
Разве это не что-то вроде этого, написанное Роменом Гаем? curious-creature.org/2011/02/22/…
Фернандо Гальего

1
@ ferdy182: Он не использует фрагменты AFAICT. Визуально это правильный базовый эффект, и я, возможно, смогу использовать его как еще одну точку данных для попытки создать ответ на основе фрагментов. Спасибо!
CommonsWare

1
Подумайте: стандартное приложение электронной почты имеет аналогичное (хотя и не полностью идентичное) поведение на моем Nexus 7, которое должно быть с открытым исходным кодом.
Кристофер Орр,

4
Приложение электронной почты использует настраиваемый «ThreePaneLayout», который анимирует положение левого представления и ширину / видимость двух других представлений, каждое из которых содержит соответствующий фрагмент.
Кристофер Орр,

2
Привет, @CommonsWare, я не буду беспокоить людей своим полным ответом здесь, но был вопрос, аналогичный вашему, на который я дал очень удовлетворительный ответ. Итак, просто предлагаю вам проверить это (также проверьте комментарии): stackoverflow.com/a/14155204/906362
Budius

Ответы:


67

Загрузил свое предложение на github (работает со всеми версиями Android, хотя для этого вида анимации настоятельно рекомендуется аппаратное ускорение просмотра. Для устройств без аппаратного ускорения реализация кэширования растровых изображений должна подходить лучше)

Демонстрационное видео с анимацией находится здесь (низкая частота кадров, причина трансляции экрана. Фактическая производительность очень высокая)


Использование:

layout = new ThreeLayout(this, 3);
layout.setAnimationDuration(1000);
setContentView(layout);
layout.getLeftView();   //<---inflate FragmentA here
layout.getMiddleView(); //<---inflate FragmentB here
layout.getRightView();  //<---inflate FragmentC here

//Left Animation set
layout.startLeftAnimation();

//Right Animation set
layout.startRightAnimation();

//You can even set interpolators

Объяснение :

Создан новый настраиваемый RelativeLayout (ThreeLayout) и 2 настраиваемые анимации (MyScalAnimation, MyTranslateAnimation)

ThreeLayoutполучает вес левой панели как param, предполагая, что другой видимый вид имеет weight=1.

Таким образом new ThreeLayout(context,3)создается новое представление с тремя дочерними элементами, а левая панель занимает 1/3 всего экрана. Другой вид занимает все доступное пространство.

Он вычисляет ширину во время выполнения, более безопасная реализация заключается в том, что размеры вычисляются впервые в draw (). вместо в post ()

Анимации «Масштабирование и преобразование» фактически изменяют размер и перемещают вид, а не псевдо [масштабировать, перемещать]. Заметьте, что fillAfter(true)нигде не используется.

View2 правее View1

и

View3 находится справа_of View2

Установив эти правила, RelativeLayout позаботится обо всем остальном. Анимация меняет margins(в движении) и [width,height]масштаб

Чтобы получить доступ к каждому дочернему элементу (чтобы вы могли надуть его своим фрагментом, вы можете вызвать

public FrameLayout getLeftLayout() {}

public FrameLayout getMiddleLayout() {}

public FrameLayout getRightLayout() {}

Ниже показаны 2 анимации.


Этап 1

--- На экране ----------! ----- ВЫХ ----

[View1] [_____ _____ View2] [_____ View3_____]

Stage2

- ВЫХОД -! -------- НА экране ------

[View1] [View2] [_____ View3_____]


1
Хотя я еще не реализовал масштабный аниматор, я обеспокоен тем, что он будет хорошо работать для сплошных цветных полей и хуже работать с, скажем так, «реальным» контентом. Результаты изменения размера фрагмента B для соответствия пространству фрагмента A не должны отличаться от результатов, если фрагмент B был помещен туда изначально. Например, AFAIK, масштабный аниматор будет масштабировать размер шрифта (отрисовывая все в анимированном Viewменьшем размере ), но в Gmail / Email мы не получаем меньшие шрифты, только меньше слов.
CommonsWare

1
@CommonsWare scaleAnimation - это просто абстрактное имя. Фактически никакого масштабирования не происходит. Фактически он изменяет ширину представления. Проверить источник. LayoutResizer могло бы быть лучшим названием для этого.
weakwire

1
Это не моя интерпретация источника вокруг scaleXзначения View, хотя я могу неверно истолковывать вещи.
CommonsWare

1
@CommonsWare проверьте это github.com/weakwire/3PaneLayout/blob/master/src/com/example/… out. Единственная причина, по которой я расширяю Animation, - это получить доступ к interpolatedTime. p.width = (int) ширина; творит всю магию, и это не scaleX. Это фактические размеры изображения, которые изменяются в течение указанного времени.
weakwire 07

2
Ваше видео показывает хорошую частоту кадров, но просмотры, используемые в вашем примере, очень просты. Выполнение requestLayout () во время анимации очень затратно. Особенно, если дети сложные (например, ListView). Вероятно, это приведет к прерывистой анимации. Приложение GMail не вызывает requestLayout. Вместо этого перед началом анимации на среднюю панель помещается другой вид меньшего размера.
Тьерри-Димитри Рой

31

Хорошо, вот мое собственное решение, полученное из приложения Email AOSP, согласно предложению @Christopher в комментариях к вопросу.

https://github.com/commonsguy/cw-omnibus/tree/master/Animation/ThreePane

Решение @ weakwire напоминает мое, хотя он использует классику, Animationа не аниматоры, и он использует RelativeLayoutправила для принудительного позиционирования. С точки зрения щедрости, он, вероятно, получит награду, если только кто-нибудь еще с хитрым решением еще не отправит ответ.


В двух словах, ThreePaneLayoutв этом проекте есть LinearLayoutподкласс, предназначенный для работы в пейзаже с тремя детьми. Ширина этих дочерних элементов может быть задана в XML-макете любым желаемым способом - я показываю с использованием весов, но у вас может быть определенная ширина, заданная ресурсами измерения или чем-то еще. Третий дочерний элемент - фрагмент C в вопросе - должен иметь нулевую ширину.

package com.commonsware.android.anim.threepane;

import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.ObjectAnimator;
import android.content.Context;
import android.util.AttributeSet;
import android.view.View;
import android.widget.LinearLayout;

public class ThreePaneLayout extends LinearLayout {
  private static final int ANIM_DURATION=500;
  private View left=null;
  private View middle=null;
  private View right=null;
  private int leftWidth=-1;
  private int middleWidthNormal=-1;

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

  void initSelf() {
    setOrientation(HORIZONTAL);
  }

  @Override
  public void onFinishInflate() {
    super.onFinishInflate();

    left=getChildAt(0);
    middle=getChildAt(1);
    right=getChildAt(2);
  }

  public View getLeftView() {
    return(left);
  }

  public View getMiddleView() {
    return(middle);
  }

  public View getRightView() {
    return(right);
  }

  public void hideLeft() {
    if (leftWidth == -1) {
      leftWidth=left.getWidth();
      middleWidthNormal=middle.getWidth();
      resetWidget(left, leftWidth);
      resetWidget(middle, middleWidthNormal);
      resetWidget(right, middleWidthNormal);
      requestLayout();
    }

    translateWidgets(-1 * leftWidth, left, middle, right);

    ObjectAnimator.ofInt(this, "middleWidth", middleWidthNormal,
                         leftWidth).setDuration(ANIM_DURATION).start();
  }

  public void showLeft() {
    translateWidgets(leftWidth, left, middle, right);

    ObjectAnimator.ofInt(this, "middleWidth", leftWidth,
                         middleWidthNormal).setDuration(ANIM_DURATION)
                  .start();
  }

  public void setMiddleWidth(int value) {
    middle.getLayoutParams().width=value;
    requestLayout();
  }

  private void translateWidgets(int deltaX, View... views) {
    for (final View v : views) {
      v.setLayerType(View.LAYER_TYPE_HARDWARE, null);

      v.animate().translationXBy(deltaX).setDuration(ANIM_DURATION)
       .setListener(new AnimatorListenerAdapter() {
         @Override
         public void onAnimationEnd(Animator animation) {
           v.setLayerType(View.LAYER_TYPE_NONE, null);
         }
       });
    }
  }

  private void resetWidget(View v, int width) {
    LinearLayout.LayoutParams p=
        (LinearLayout.LayoutParams)v.getLayoutParams();

    p.width=width;
    p.weight=0;
  }
}

Однако во время выполнения, независимо от того, как вы изначально настроили ширину, управление шириной берется на себя при ThreePaneLayoutпервом использовании hideLeft()для переключения с отображения вопроса, называемого фрагментами A и B, на фрагменты B и C. ThreePaneLayout- который не имеет каких - либо конкретных связей с фрагментами - три части являются left, middleи right. В то время вы звоните hideLeft(), мы записываем размеры leftи middleи обнулять любые весовые коэффициенты , которые были использованы на любом из трех, так что мы можем полностью контролировать размеры. В момент времени hideLeft()мы устанавливаем размер rightравным исходному размеру middle.

Анимации бывают двоякими:

  • Используйте a, ViewPropertyAnimatorчтобы выполнить перевод трех виджетов слева на ширину left, используя аппаратный уровень
  • Используйте ObjectAnimatorнастраиваемое псевдо-свойство, middleWidthчтобы изменить middleширину с того, с чего оно начиналось, на исходную ширинуleft

(возможно, лучше использовать AnimatorSetи ObjectAnimatorsдля всего этого, хотя пока это работает)

(также возможно, что middleWidth ObjectAnimatorотрицает значение аппаратного уровня, так как это требует довольно непрерывного признания недействительности)

(это , безусловно , возможно , что у меня еще есть пробелы в моей анимации понимания, и что мне нравится вводные заявления)

Конечный эффект заключается в том, что он leftсоскальзывает с экрана, middleперемещается в исходное положение и размер leftи rightпереводится сразу за ним middle.

showLeft() просто переворачивает процесс с тем же набором аниматоров, только с обратными направлениями.

Действие использует a ThreePaneLayoutдля хранения пары ListFragmentвиджетов и a Button. Выбор чего-либо в левом фрагменте добавляет (или обновляет содержимое) средний фрагмент. При выборе чего-либо в среднем фрагменте устанавливается заголовок Button, плюс выполняется hideLeft()на ThreePaneLayout. Нажатие НАЗАД, если мы спрятали левую сторону, выполнит showLeft(); в противном случае BACK завершает действие. Поскольку это не используется FragmentTransactionsдля воздействия на анимацию, мы сами застряли в управлении этим «задним стеком».

Связанный выше проект использует собственные фрагменты и собственный фреймворк аниматора. У меня есть другая версия того же проекта, которая использует бэкпорт фрагментов поддержки Android и NineOldAndroids для анимации:

https://github.com/commonsguy/cw-omnibus/tree/master/Animation/ThreePaneBC

Бэкпорт отлично работает на Kindle Fire 1-го поколения, хотя анимация немного дергается, учитывая более низкие характеристики оборудования и отсутствие поддержки аппаратного ускорения. Обе реализации кажутся гладкими на Nexus 7 и других планшетах текущего поколения.

Я, конечно, открыт для идей, как улучшить это решение или другие решения, которые предлагают явные преимущества по сравнению с тем, что я сделал здесь (или тем, что использовал @weakwire).

Еще раз спасибо всем, кто внес свой вклад!


1
Здравствуй! Спасибо за решение, но я думаю, что добавление v.setLayerType () иногда немного замедляет работу. Просто удалите эти строки (и Listener тоже). Это, по крайней мере, верно для устройств Android 3+, которые я только что пробовал на Galaxy Nexus, и без этих строк он работает намного лучше
Евгений Наку,

1
«Обе реализации кажутся гладкими на Nexus 7 и других планшетах текущего поколения». Я верю тебе; но я тестирую вашу реализацию с родным ObjectAnimator на Nexus 7, и она немного отстает. Каковы были бы наиболее вероятные причины? У меня уже есть hardwareAccelerated: true в AndroidMAnifest. Я действительно не знаю, в чем может быть проблема. Я также пробовал вариант Даниэля Греча, и он отстает. Я вижу различия в анимации (его анимация основана на весах), но не понимаю, почему в обоих случаях она отставала.
Богдан Зурац 02

1
@ Андрей: "немного отстает" - я не уверен, как вы здесь используете глагол "отставание". Для меня «отставание» означает конкретную цель для сравнения. Так, например, анимация, связанная с движением пальца, может отставать от движения пальца. В этом случае все запускается касаниями, поэтому я не понимаю, от чего может отставать анимация. Предположение, что, возможно, «немного тормозит» просто означает «кажется вялым», я этого не видел, но я не утверждаю, что обладаю сильным эстетическим чутьем, и поэтому то, что могло бы показаться мне приемлемым анимацией, может быть для вас неприемлемо.
CommonsWare 02

2
@CommonsWare: Я скомпилировал ваш код с помощью 4.2, отличная работа. Когда я нажал на середину ListView, быстро нажал кнопку возврата и повторил, тогда весь макет сместился вправо. Он начал показывать столбец с дополнительным пространством слева. Это поведение было введено, потому что я не позволял ListViewзавершать первую анимацию (начатую нажатием на середину ) и нажимал кнопку «Назад». Я зафиксировал его путем замены translationXByс translationXв translateWidgetsспособе и translateWidgets(leftWidth, left, middle, right);с translateWidgets(0, left, middle, right);в showLeft()методе.
M-WaJeEh

2
Я также заменил requestLayout();на middle.requestLayout();in setMiddleWidth(int value);method. Это может не повлиять на производительность, так как я предполагаю, что графический процессор все равно выполнит полный проход макета, есть комментарии?
M-WaJeEh

13

Мы создали библиотеку под названием PanesLibrary, которая решает эту проблему. Он даже более гибкий, чем то, что предлагалось ранее, потому что:

  • Каждая панель может иметь динамический размер
  • Он позволяет использовать любое количество панелей (а не только 2 или 3)
  • Фрагменты внутри окон правильно сохраняются при изменении ориентации.

Вы можете проверить это здесь: https://github.com/Mapsaurus/Android-PanesLibrary

Вот демонстрация: http://www.youtube.com/watch?v=UA-lAGVXoLU&feature=youtu.be

Это в основном позволяет вам легко добавлять любое количество панелей с динамическим размером и прикреплять фрагменты к этим панелям. Надеюсь, что вы найдете ее полезной! :)


6

Основываясь на одном из примеров, на которые вы ссылались ( http://android.amberfog.com/?p=758 ), как насчет анимацииlayout_weight свойства, ? Таким образом, вы можете анимировать изменение веса трех фрагментов вместе, и вы получите бонус в том, что все они красиво скользят вместе:

Начните с простого макета. Поскольку мы собираемся анимировать layout_weight, нам понадобится LinearLayoutкорневой вид для трех панелей:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/container"
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="match_parent">

<LinearLayout
    android:id="@+id/panel1"
    android:layout_width="0dip"
    android:layout_weight="1"
    android:layout_height="match_parent"/>

<LinearLayout
    android:id="@+id/panel2"
    android:layout_width="0dip"
    android:layout_weight="2"
    android:layout_height="match_parent"/>

<LinearLayout
    android:id="@+id/panel3"
    android:layout_width="0dip"
    android:layout_weight="0"
    android:layout_height="match_parent"/>
</LinearLayout>

Затем демонстрационный класс:

public class DemoActivity extends Activity implements View.OnClickListener {
    public static final int ANIM_DURATION = 500;
    private static final Interpolator interpolator = new DecelerateInterpolator();

    boolean isCollapsed = false;

    private Fragment frag1, frag2, frag3;
    private ViewGroup panel1, panel2, panel3;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);

        panel1 = (ViewGroup) findViewById(R.id.panel1);
        panel2 = (ViewGroup) findViewById(R.id.panel2);
        panel3 = (ViewGroup) findViewById(R.id.panel3);

        frag1 = new ColorFrag(Color.BLUE);
        frag2 = new InfoFrag();
        frag3 = new ColorFrag(Color.RED);

        final FragmentManager fm = getFragmentManager();
        final FragmentTransaction trans = fm.beginTransaction();

        trans.replace(R.id.panel1, frag1);
        trans.replace(R.id.panel2, frag2);
        trans.replace(R.id.panel3, frag3);

        trans.commit();
    }

    @Override
    public void onClick(View view) {
        toggleCollapseState();
    }

    private void toggleCollapseState() {
        //Most of the magic here can be attributed to: http://android.amberfog.com/?p=758

        if (isCollapsed) {
            PropertyValuesHolder[] arrayOfPropertyValuesHolder = new PropertyValuesHolder[3];
            arrayOfPropertyValuesHolder[0] = PropertyValuesHolder.ofFloat("Panel1Weight", 0.0f, 1.0f);
            arrayOfPropertyValuesHolder[1] = PropertyValuesHolder.ofFloat("Panel2Weight", 1.0f, 2.0f);
            arrayOfPropertyValuesHolder[2] = PropertyValuesHolder.ofFloat("Panel3Weight", 2.0f, 0.0f);
            ObjectAnimator localObjectAnimator = ObjectAnimator.ofPropertyValuesHolder(this, arrayOfPropertyValuesHolder).setDuration(ANIM_DURATION);
            localObjectAnimator.setInterpolator(interpolator);
            localObjectAnimator.start();
        } else {
            PropertyValuesHolder[] arrayOfPropertyValuesHolder = new PropertyValuesHolder[3];
            arrayOfPropertyValuesHolder[0] = PropertyValuesHolder.ofFloat("Panel1Weight", 1.0f, 0.0f);
            arrayOfPropertyValuesHolder[1] = PropertyValuesHolder.ofFloat("Panel2Weight", 2.0f, 1.0f);
            arrayOfPropertyValuesHolder[2] = PropertyValuesHolder.ofFloat("Panel3Weight", 0.0f, 2.0f);
            ObjectAnimator localObjectAnimator = ObjectAnimator.ofPropertyValuesHolder(this, arrayOfPropertyValuesHolder).setDuration(ANIM_DURATION);
            localObjectAnimator.setInterpolator(interpolator);
            localObjectAnimator.start();
        }
        isCollapsed = !isCollapsed;
    }

    @Override
    public void onBackPressed() {
        //TODO: Very basic stack handling. Would probably want to do something relating to fragments here..
        if(isCollapsed) {
            toggleCollapseState();
        } else {
            super.onBackPressed();
        }
    }

    /*
     * Our magic getters/setters below!
     */

    public float getPanel1Weight() {
        LinearLayout.LayoutParams params = (LinearLayout.LayoutParams)     panel1.getLayoutParams();
        return params.weight;
    }

    public void setPanel1Weight(float newWeight) {
        LinearLayout.LayoutParams params = (LinearLayout.LayoutParams) panel1.getLayoutParams();
        params.weight = newWeight;
        panel1.setLayoutParams(params);
    }

    public float getPanel2Weight() {
        LinearLayout.LayoutParams params = (LinearLayout.LayoutParams) panel2.getLayoutParams();
        return params.weight;
    }

    public void setPanel2Weight(float newWeight) {
        LinearLayout.LayoutParams params = (LinearLayout.LayoutParams) panel2.getLayoutParams();
        params.weight = newWeight;
        panel2.setLayoutParams(params);
    }

    public float getPanel3Weight() {
        LinearLayout.LayoutParams params = (LinearLayout.LayoutParams) panel3.getLayoutParams();
        return params.weight;
    }

    public void setPanel3Weight(float newWeight) {
        LinearLayout.LayoutParams params = (LinearLayout.LayoutParams) panel3.getLayoutParams();
        params.weight = newWeight;
        panel3.setLayoutParams(params);
    }


    /**
     * Crappy fragment which displays a toggle button
     */
    public static class InfoFrag extends Fragment {
        @Override
        public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle     savedInstanceState) {
            LinearLayout layout = new LinearLayout(getActivity());
            layout.setLayoutParams(new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT));
            layout.setBackgroundColor(Color.DKGRAY);

            Button b = new Button(getActivity());
            b.setOnClickListener((DemoActivity) getActivity());
            b.setText("Toggle Me!");

            layout.addView(b);

            return layout;
        }
    }

    /**
     * Crappy fragment which just fills the screen with a color
     */
    public static class ColorFrag extends Fragment {
        private int mColor;

        public ColorFrag() {
            mColor = Color.BLUE; //Default
        }

        public ColorFrag(int color) {
            mColor = color;
        }

        @Override
        public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
            FrameLayout layout = new FrameLayout(getActivity());
            layout.setLayoutParams(new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT));

            layout.setBackgroundColor(mColor);
            return layout;
        }
    }
}

Также этот пример не использует FragmentTransactions для достижения анимации (скорее, он анимирует представления, к которым прикреплены фрагменты), поэтому вам нужно будет самостоятельно выполнять все транзакции backstack / fragment, но по сравнению с усилиями по обеспечению работы анимации хорошо, это не похоже на плохой компромисс :)

Ужасное видео с низким разрешением в действии: http://youtu.be/Zm517j3bFCo


1
Что ж, Gmail определенно переводит то, что я называю фрагментом A. Я бы побеспокоился, что уменьшение его веса до 0 может привести к появлению визуальных артефактов (например, TextViewс wrap_contentрастяжением себя по вертикали в процессе анимации). Однако может случиться так, что анимация веса - это то, как они изменяют размер того, что я назвал фрагментом B. Спасибо!
CommonsWare

1
Не беспокойся! Однако это хороший момент, это, вероятно, вызовет визуальные артефакты в зависимости от того, какие дочерние представления находятся во «Фрагменте A». Будем надеяться, что кто-то найдет более надежный способ сделать это
DanielGrech

5

Здесь не используются фрагменты .... Это настраиваемый макет с 3 дочерними элементами. Когда вы нажимаете на сообщение, вы компенсируете трех детей, используяoffsetLeftAndRight() и аниматором.

В JellyBean вы можете включить «Показать макет границ» в настройках «Developper Options». Когда анимация слайда завершена, вы все еще можете видеть, что левое меню все еще существует, но под средней панелью.

Это похоже на меню приложения Cyril Mottier Fly-in. , но с 3 элементами вместо 2.

Кроме того, ViewPagerтретий дочерний элемент является еще одним признаком этого поведения: ViewPagerобычно использует фрагменты (я знаю, что они не должны, но я никогда не видел другой реализации Fragment), и, поскольку вы не можете использовать Fragmentsвнутри другого, Fragment3 дочерних элемента наверное не фрагменты ....


Я никогда раньше не использовал «Показать границы макета» - это очень полезно для таких приложений с закрытым исходным кодом (спасибо!). Похоже, что то, что я описал как фрагмент A, переводится за пределы экрана (вы можете видеть его правый край, скользящий справа налево), прежде чем вернуться обратно под то, что я назвал фрагментом B. Это похоже на то TranslateAnimation, хотя я конечно, есть способы использовать аниматоров для достижения тех же целей. Придется провести над этим несколько экспериментов. Спасибо!
CommonsWare

2

В настоящее время я пытаюсь сделать что-то подобное, за исключением масштабирования фрагмента B, чтобы занять доступное пространство, и что 3 панели могут быть открыты одновременно, если есть достаточно места. Вот мое решение, но я не уверен, собираюсь ли я его придерживаться. Я надеюсь, что кто-нибудь ответит, показав правильный путь.

Вместо использования LinearLayout и анимации веса я использую RelativeLayout и анимирую поля. Я не уверен, что это лучший способ, потому что он требует вызова requestLayout () при каждом обновлении. Хотя на всех моих устройствах все работает гладко.

Итак, я анимирую макет, я не использую транзакцию фрагментов. Я вручную нажимаю кнопку «Назад», чтобы закрыть фрагмент C, если он открыт.

FragmentB используйте layout_toLeftOf / ToRightOf, чтобы он был выровнен по фрагментам A и C.

Когда мое приложение запускает событие для отображения фрагмента C, я вставляю фрагмент C и выдвигаю фрагмент A одновременно. (2 отдельные анимации). И наоборот, когда открывается фрагмент A, я одновременно закрываю C.

В портретном режиме или на меньшем экране я использую немного другой макет и перемещаю фрагмент C по экрану.

Чтобы использовать процент для ширины фрагментов A и C, я думаю, вам придется вычислить его во время выполнения ... (?)

Вот макет занятия:

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/rootpane"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent">

    <!-- FRAGMENT A -->
    <fragment
        android:id="@+id/fragment_A"
        android:layout_width="300dp"
        android:layout_height="fill_parent"
        class="com.xyz.fragA" />

    <!-- FRAGMENT C -->
    <fragment
        android:id="@+id/fragment_C"
        android:layout_width="600dp"
        android:layout_height="match_parent"
        android:layout_alignParentRight="true"
        class="com.xyz.fragC"/>

    <!-- FRAGMENT B -->
    <fragment
        android:id="@+id/fragment_B"
        android:layout_width="match_parent"
        android:layout_height="fill_parent"
        android:layout_marginLeft="0dip"
        android:layout_marginRight="0dip"
        android:layout_toLeftOf="@id/fragment_C"
        android:layout_toRightOf="@id/fragment_A"
        class="com.xyz.fragB" />

</RelativeLayout>

Анимация для перемещения FragmentC внутрь или наружу:

private ValueAnimator createFragmentCAnimation(final View fragmentCRootView, boolean slideIn) {

    ValueAnimator anim = null;

    final RelativeLayout.LayoutParams lp = (RelativeLayout.LayoutParams) fragmentCRootView.getLayoutParams();
    if (slideIn) {
        // set the rightMargin so the view is just outside the right edge of the screen.
        lp.rightMargin = -(lp.width);
        // the view's visibility was GONE, make it VISIBLE
        fragmentCRootView.setVisibility(View.VISIBLE);
        anim = ValueAnimator.ofInt(lp.rightMargin, 0);
    } else
        // slide out: animate rightMargin until the view is outside the screen
        anim = ValueAnimator.ofInt(0, -(lp.width));

    anim.setInterpolator(new DecelerateInterpolator(5));
    anim.setDuration(300);
    anim.addUpdateListener(new AnimatorUpdateListener() {

        @Override
        public void onAnimationUpdate(ValueAnimator animation) {
            Integer rightMargin = (Integer) animation.getAnimatedValue();
            lp.rightMargin = rightMargin;
            fragmentCRootView.requestLayout();
        }
    });

    if (!slideIn) {
        // if the view was sliding out, set visibility to GONE once the animation is done
        anim.addListener(new AnimatorListenerAdapter() {

            @Override
            public void onAnimationEnd(Animator animation) {
                fragmentCRootView.setVisibility(View.GONE);
            }
        });
    }
    return anim;
}
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.