1. ホーム
  2. android

[解決済み] Gmailの3分割アニメーションシナリオの完全動作サンプル?

2022-07-20 20:24:16

質問

TL;DR:を探しています。 完全な動作サンプル Gmail の 3 つのフラグメントのアニメーションと呼ぶシナリオの完全なサンプルを探しています。具体的には、次のような 2 つのフラグメントから開始したいと思います。

何らかのUIイベント(例:フラグメントBの何かをタップした)が発生したときに

  • フラグメント A を画面左側にスライドさせる
  • フラグメント B が画面の左端に移動し、フラグメント A が移動した場所を占めるように縮小する。
  • フラグメント C が画面右側からスライドして、フラグメント B の位置に移動する。

そして、BACKボタンが押されたら、その一連の動作を反転させるようにします。

さて、私は多くの部分的な実装を見てきました。以下にそのうちの 4 つをレビューします。不完全であることに加え、どれも問題があります。


レトマイヤーの投稿 この人気のある答え を使い、同じ基本的な質問に対して setCustomAnimations()FragmentTransaction . 2つのフラグメントのシナリオ(例えば、最初はフラグメントAしか見えず、アニメーション効果を使って新しいフラグメントBに置き換えたい)については、私は完全に同意します。しかし

  • 1 つの "in"、および 1 つの "out"アニメーションしか指定できないので、3 つのフラグメント シナリオに必要なすべての異なるアニメーションをどのように処理するのかがわかりません。
  • その <objectAnimator> はピクセル単位で固定された位置を使用しており、さまざまな画面サイズを考慮すると実用的ではありません。 setCustomAnimations() はアニメーション リソースを必要とし、Java でこれらのことを定義する可能性を排除しています。
  • スケール用のオブジェクトのアニメーターが、以下のようなものとどのように結びつくのか、途方に暮れています。 android:layout_weight の中に LinearLayout パーセント単位でスペースを割り当てるための
  • フラグメントCが最初にどのように処理されるのか、途方に暮れています( GONE ? android:layout_weight0 ?スケール0にあらかじめアニメーション化されている?)

@Roman Nurik さんの指摘によると を使用すると、任意のプロパティをアニメーション化できます。 をアニメーション化することができます。これは、独自のカスタムレイアウトマネージャーのサブクラスを作成する代償として、ハードワイヤードの位置の問題を解決するのに役立ちます。これは多少役立ちますが、Reto のソリューションの残りの部分については、まだ困惑しています。


の作者は このpastebinのエントリ を見ると、基本的に 3 つのフラグメントはすべてコンテナ内に存在し、フラグメント C は最初に hide() トランザクション操作によって隠されます。次に show() C と hide() AをUIイベントが発生したときに表示します。しかし、Bのサイズが変わることをどう処理するのかがわからない。また、同じコンテナに複数のフラグメントを追加できるという事実にも依存しており、それが長期的に信頼できる動作なのかどうかもわかりません(言うまでもなく、それは findFragmentById() を壊すことは言うまでもありません。)


の作者は このブログの記事 は、Gmail が setCustomAnimations() をまったく使用せず、代わりにオブジェクト アニメーターを直接使用していることがわかります ("ルート ビューの左マージンを変更 + 右ビューの幅を変更するだけです")。しかし、これはまだ AFAICT の 2 分割のソリューションであり、示された実装は再び寸法をピクセルでハードワイヤーしています。


しかし、誰かがこのアニメーション シナリオのための 3 分割ソリューションを開発し、コード (またはそのリンク) を投稿できることを強く望んでいます。Android でのアニメーションは、私の髪を引っ張り出したくなりますし、私を見た人は、これがほとんど実りのない努力であることを知っています。

どのように解決するのですか?

OK、質問のコメントにある@Christopherの提案に従って、メールAOSPアプリから派生した私自身の解決策です。

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

Weakwire のソリューションは、私のソリューションと似ていますが、彼は古典的な Animation を使い、アニメーターではなく RelativeLayout ルールを使って位置決めを強制しています。懸賞金の観点からは、より巧妙な解決策を持つ他の誰かがまだ答えを投稿しない限り、彼はおそらく懸賞金を得ることになるでしょう。


一言で言えば ThreePaneLayout をそのプロジェクトでは LinearLayout のサブクラスで、3 つの子要素を持つランドスケープで動作するように設計されています。これらの子の幅は、レイアウト XML で好きなように設定できます -- 私はウェイトを使っていますが、ディメンションリソースなどで特定の幅を設定することも可能です。3番目の子(質問ではフラグメントC)は幅が0であるべきです。

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 の用語では -- フラグメントとの特別な結びつきはありませんが -- この3つのピースは left , middle そして right . を呼び出した時点で hideLeft() のサイズを記録します。 leftmiddle の3つに使われていた重みをゼロにすることで、サイズを完全に制御することができます。の時点では hideLeft() の時点で right の元のサイズになるように middle .

アニメーションは2つあります。

  • を使う。 ViewPropertyAnimator の幅だけ左に移動させます。 left ハードウェア レイヤーを使用して
  • を使用します。 ObjectAnimator のカスタム擬似プロパティで middleWidth を変更するために middle の幅を変更し、元の幅である left

(を使用するのが良いという可能性もあります)。 AnimatorSetObjectAnimators を使うこともできますが、今のところこれで大丈夫です。)

(また middleWidth ObjectAnimator がハードウェア層の値を否定することも可能ですが、その場合はかなり継続的な無効化が必要なので)

(それは 間違いなく 私はまだアニメーションの理解力に欠けている可能性があり、また、私は括弧つきの文章が好きなのです)

正味のところ left が画面の外にスライドすることです。 middle の元の位置とサイズにスライドします。 left であり right の右後ろに翻訳されます。 middle .

showLeft() は、同じアニメーターの組み合わせで、ただ方向を逆にしただけのものです。

このアクティビティでは ThreePaneLayout のペアを保持するために ListFragment ウィジェットと Button . 左のフラグメントで何かを選択すると、中央のフラグメントが追加 (または内容の更新) されます。中央のフラグメントで何かを選択すると、ウィジェットのキャプションが設定されます。 Button のキャプションを設定し、さらに hideLeft() を実行します。 ThreePaneLayout . BACK を押すと、左側を隠した場合、実行されるのは showLeft() を実行します。そうでなければ、BACKはアクティビティを終了します。これは FragmentTransactions を使用しないので、私たちは自分自身でそのバックスタックを管理することになります。

上記のリンク先のプロジェクトでは、ネイティブ フラグメントとネイティブ アニメーター フレームワークを使用しています。同じプロジェクトの別のバージョンでは、Android Support fragments バックポートおよび NineOldAndroids をアニメーションに使用しています。

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

バックポートは第 1 世代 Kindle Fire で問題なく動作しますが、ハードウェア スペックが低く、ハードウェア アクセラレーション サポートがないため、アニメーションは少しぎこちなくなります。両方の実装は、Nexus 7 やその他の現世代のタブレットでスムーズに動作するようです。

このソリューションをどのように改善するか、または私がここで行ったこと (あるいは @weakwire が使用したこと) よりも明確な利点を提供する他のソリューションについてのアイデアをお待ちしています。

貢献してくれたみなさん、本当にありがとうございました!