Contoh Kerja Lengkap dari Skenario Animasi Tiga-Fragmen Gmail?

128

TL; DR: Saya mencari sampel kerja lengkap dari skenario yang akan saya sebut sebagai "animasi tiga-fragmen Gmail". Secara khusus, kami ingin memulai dengan dua fragmen, seperti ini:

dua fragmen

Setelah beberapa acara UI (misalnya, mengetuk sesuatu di Fragment B), kami ingin:

  • Fragmen A untuk menggeser layar ke kiri
  • Fragmen B untuk meluncur ke tepi kiri layar dan menyusut untuk mengambil tempat yang dikosongkan oleh Fragment A
  • Fragmen C untuk meluncur dari sisi kanan layar dan untuk mengambil tempat yang dikosongkan oleh Fragment B

Dan, pada tombol BACK tekan, kami ingin set operasi dibalik.

Sekarang, saya telah melihat banyak implementasi parsial; Saya akan mengulas empat di antaranya di bawah ini. Selain tidak lengkap, mereka semua memiliki masalah.


@Reto Meier menyumbangkan jawaban populer ini untuk pertanyaan dasar yang sama, yang menunjukkan bahwa Anda akan menggunakannya setCustomAnimations()dengan a FragmentTransaction. Untuk skenario dua fragmen (misalnya, Anda hanya melihat Fragmen A pada awalnya, dan ingin menggantinya dengan Fragmen B baru menggunakan efek animasi), saya setuju sepenuhnya. Namun:

  • Karena Anda hanya dapat menentukan satu animasi "dalam" dan satu "keluar", saya tidak dapat melihat bagaimana Anda akan menangani semua animasi berbeda yang diperlukan untuk skenario tiga-fragmen
  • Dalam <objectAnimator>kode sampelnya menggunakan posisi kabel dalam piksel, dan itu tampaknya tidak praktis mengingat berbagai ukuran layar, namun setCustomAnimations()membutuhkan sumber daya animasi, menghalangi kemungkinan mendefinisikan hal-hal ini di Jawa
  • Saya bingung bagaimana objek animator untuk skala terkait dengan hal-hal seperti android:layout_weightdalam LinearLayoutuntuk mengalokasikan ruang berdasarkan persentase
  • Saya bingung bagaimana Fragment C ditangani sejak awal ( GONE? android:layout_weightDari 0? Pra-animasi ke skala 0? Sesuatu yang lain?)

@Roman Nurik menunjukkan bahwa Anda dapat menghidupkan properti apa pun , termasuk properti yang Anda tetapkan sendiri. Itu dapat membantu menyelesaikan masalah posisi terprogram, dengan biaya menemukan subclass manajer tata letak kustom Anda sendiri. Itu membantu beberapa orang, tetapi saya masih bingung dengan sisa solusi Reto.


Penulis entri pastebin ini menunjukkan beberapa pseudocode yang menggiurkan, pada dasarnya mengatakan bahwa ketiga fragmen akan berada di dalam wadah pada awalnya, dengan Fragment C yang tersembunyi di awal melalui hide()operasi transaksi. Kami kemudian show()C dan hide()A ketika acara UI terjadi. Namun, saya tidak melihat bagaimana itu menangani fakta bahwa B berubah ukuran. Itu juga bergantung pada fakta bahwa Anda tampaknya dapat menambahkan beberapa fragmen ke wadah yang sama, dan saya tidak yakin apakah itu perilaku yang dapat diandalkan dalam jangka panjang (belum lagi harus rusak findFragmentById(), meskipun saya bisa hidup dengan itu).


Penulis posting blog ini menunjukkan bahwa Gmail tidak menggunakan setCustomAnimations()sama sekali, melainkan langsung menggunakan animator objek ("Anda hanya mengubah margin kiri tampilan root + mengubah lebar tampilan kanan"). Namun, ini masih merupakan solusi dua-fragmen AFAICT, dan implementasinya sekali lagi menunjukkan dimensi kabel dalam piksel.


Saya akan terus menghubungkan ini, jadi saya mungkin akan menjawab ini sendiri suatu hari nanti, tetapi saya benar-benar berharap bahwa seseorang telah menemukan solusi tiga-fragmen untuk skenario animasi ini dan dapat memposting kode (atau tautannya). Animasi di Android membuat saya ingin mencabut rambut saya, dan Anda yang telah melihat saya tahu bahwa ini adalah upaya yang tidak membuahkan hasil.

CommonsWare
sumber
2
Bukankah ini sesuatu yang diposting oleh Romain Guy? curious-creature.org/2011/02/22/…
Fernando Gallego
1
@ ferdy182: Dia tidak menggunakan fragmen AFAICT. Secara visual, ini adalah efek dasar yang tepat, dan saya mungkin dapat menggunakan ini sebagai titik data lain untuk mencoba membuat jawaban berbasis fragmen. Terima kasih!
CommonsWare
1
Hanya pemikiran: aplikasi Email standar memiliki perilaku yang serupa (meskipun tidak sepenuhnya identik) pada Nexus 7 saya - yang harus open source.
Christopher Orr
4
Aplikasi Email menggunakan "ThreePaneLayout" khusus yang menjiwai posisi Tampilan sebelah kiri, dan lebar / visibilitas dari dua Tampilan lainnya - yang masing-masing berisi Fragmen yang relevan.
Christopher Orr
2
hey @CommonsWare Saya tidak akan repot-repot ppl dengan seluruh jawaban saya di sini, tetapi ada pertanyaan serupa dengan Anda di mana saya memberikan jawaban yang sangat memuaskan. Jadi, sarankan Anda untuk memeriksanya (juga memeriksa komentar): stackoverflow.com/a/14155204/906362
Budius

Jawaban:

67

Mengunggah proposal saya di github (Bekerja dengan semua versi android meskipun melihat akselerasi perangkat keras sangat disarankan untuk animasi semacam ini. Untuk perangkat yang tidak dipercepat perangkat keras, implementasi caching bitmap harus sesuai dengan lebih baik)

Demo video dengan animasinya ada di sini (Laju frame rate dari screen cast. Performa sebenarnya sangat cepat)


Pemakaian:

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

Penjelasan :

Membuat RelativeLayout kustom baru (ThreeLayout) dan 2 animasi kustom (MyScalAnimation, MyTranslateAnimation)

ThreeLayoutmendapatkan bobot panel kiri sebagai param, dengan asumsi tampilan lain yang terlihat weight=1.

Sehingga new ThreeLayout(context,3)menciptakan tampilan baru dengan 3 anak dan panel kiri dengan 1/3 dari total layar. Tampilan lain menempati semua ruang yang tersedia.

Ini menghitung lebar saat runtime, implementasi yang lebih aman adalah bahwa dimensi dihitung pertama kali dalam menggambar (). alih-alih di pos ()

Skala dan Terjemahan animasi sebenarnya mengubah ukuran dan memindahkan tampilan dan bukan pseudo- [skala, pindah]. Perhatikan bahwa fillAfter(true)tidak digunakan di mana pun.

View2 adalah right_of View1

dan

View3 adalah right_of View2

Setelah menetapkan aturan ini, RelativeLayout menangani semua hal lainnya. Animasi mengubah margins(bergerak) dan [width,height]dalam skala

Untuk mengakses setiap anak (sehingga Anda dapat mengembang dengan Fragmen Anda, Anda dapat menelepon

public FrameLayout getLeftLayout() {}

public FrameLayout getMiddleLayout() {}

public FrameLayout getRightLayout() {}

Di bawah ini ditunjukkan 2 animasi


Tahap 1

--- DI Layar ----------! ----- OUT ----

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

Tahap 2

--OUT -! -------- DI Layar ------

[View1] [View2] [_____ View3_____]

lemah
sumber
1
Meskipun saya belum mengimplementasikan animator skala, saya khawatir itu akan bekerja dengan baik untuk bidang warna yang solid dan kurang baik, haruskah kita mengatakan, konten "nyata". Hasil dari Fragment B yang diubah ukurannya agar sesuai dengan ruang Fragment A seharusnya tidak berbeda daripada jika Fragment B telah diletakkan di sana semula. Sebagai contoh, AFAIK, animator skala akan menskala ukuran font (dengan menggambar segala sesuatu dalam animasi yang Viewlebih kecil), tetapi di Gmail / Email, kami tidak mendapatkan font yang lebih kecil, hanya lebih sedikit kata.
CommonsWare
1
@CommonsWare scaleAnimation hanyalah nama abstrak. Bahkan tidak terjadi penskalaan. Ini sebenarnya mengubah ukuran lebar tampilan. Periksa sumbernya. LayoutResizer mungkin nama yang lebih baik untuk itu.
weakwire
1
Itu bukan interpretasi saya tentang sumber di sekitar scaleXnilai View, meskipun saya mungkin salah mengartikan hal-hal.
CommonsWare
1
@CommonsWare periksa github.com/weakwire/3PaneLayout/blob/master/src/com/example/… ini . Satu-satunya alasan saya memperpanjang Animasi adalah untuk mendapatkan akses ke interpolatedTime. p.width = (int) lebar; melakukan semua keajaiban dan itu bukan scaleX. Dimensi tampilan aktual yang berubah selama waktu yang ditentukan.
weakwire
2
Video Anda menunjukkan framerate yang baik, tetapi tampilan yang digunakan dalam contoh Anda sangat sederhana. Melakukan requestLayout () selama animasi sangat mahal. Terutama jika anak-anak itu kompleks (ListView misalnya). Ini mungkin akan menghasilkan animasi berombak. Aplikasi GMail tidak memanggil requestLayout. Alih-alih, tampilan lain yang lebih kecil diletakkan di panel tengah sesaat sebelum animasi dimulai.
Thierry-Dimitri Roy
31

OK, ini solusi saya sendiri, berasal dari aplikasi Email AOSP, sesuai saran @ Christopher dalam komentar pertanyaan.

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

Solusi @ lemahwire mengingatkan saya, meskipun ia menggunakan klasik Animationdaripada animator, dan ia menggunakan RelativeLayoutaturan untuk menegakkan penentuan posisi. Dari sudut pandang karunia, ia mungkin akan mendapatkan karunia itu, kecuali jika orang lain dengan solusi yang lebih licin belum mengirimkan jawaban.


Singkatnya, ThreePaneLayoutdalam proyek itu adalah LinearLayoutsubkelas, yang dirancang untuk bekerja di lanskap dengan tiga anak. Lebar anak-anak itu dapat diatur dalam tata letak XML, melalui cara apa pun yang diinginkan - Saya menunjukkan menggunakan bobot, tetapi Anda dapat memiliki lebar tertentu yang ditetapkan oleh sumber daya dimensi atau apa pun. Anak ketiga - Fragmen C dalam pertanyaan - harus memiliki lebar nol.

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

Namun, pada saat runtime, tidak peduli bagaimana Anda awalnya mengatur lebar, manajemen lebar diambil alih ThreePaneLayoutsaat pertama kali Anda gunakan hideLeft()untuk beralih dari menampilkan apa yang disebut pertanyaan sebagai Fragmen A dan B ke Fragmen B dan C. Dalam terminologi ThreePaneLayout- yang tidak memiliki hubungan khusus untuk fragmen - tiga potongan left, middledan right. Pada saat Anda menelepon hideLeft(), kami merekam ukuran leftdan middledan nol bobot apa pun yang digunakan pada salah satu dari ketiganya, sehingga kami dapat sepenuhnya mengontrol ukuran. Pada titik waktu hideLeft(), kami mengatur ukuran rightmenjadi ukuran asli middle.

Animasi ada dua:

  • Gunakan ViewPropertyAnimatoruntuk melakukan terjemahan dari tiga widget ke kiri dengan lebar left, menggunakan lapisan perangkat keras
  • Gunakan ObjectAnimatorproperti pseudo-custom middleWidthuntuk mengubah middlelebar dari apa pun yang dimulai dengan lebar aslileft

(Ada kemungkinan bahwa itu adalah ide yang lebih baik untuk menggunakan AnimatorSetdan ObjectAnimatorsuntuk semua ini, meskipun ini bekerja untuk saat ini)

(Mungkin juga bahwa middleWidth ObjectAnimatormeniadakan nilai lapisan perangkat keras, karena itu memerlukan pembatalan yang cukup berkelanjutan)

(itu adalah pasti mungkin bahwa saya masih memiliki kesenjangan dalam pemahaman animasi saya, dan yang saya suka pernyataan kurung)

Efek bersihnya adalah leftslide dari layar, middleslide ke posisi asli dan ukuran left, dan rightditerjemahkan tepat di belakang middle.

showLeft() hanya membalikkan proses, dengan campuran animator yang sama, hanya dengan arah terbalik.

Aktivitas ini menggunakan a ThreePaneLayoutuntuk menampung sepasang ListFragmentwidget dan a Button. Memilih sesuatu di fragmen kiri menambahkan (atau memperbarui konten) fragmen tengah. Memilih sesuatu di fragmen tengah menetapkan keterangan Button, ditambah dijalankan hideLeft()pada ThreePaneLayout. Menekan KEMBALI, jika kita menyembunyikan sisi kiri, akan mengeksekusi showLeft(); jika tidak, KEMBALI keluar dari aktivitas. Karena ini tidak digunakan FragmentTransactionsuntuk memengaruhi animasi, kami terjebak mengelola "back stack" itu sendiri.

Proyek yang ditautkan ke atas menggunakan fragmen asli dan kerangka animator asli. Saya memiliki versi lain dari proyek yang sama yang menggunakan backport fragmen Dukungan Android dan NineOldAndroids untuk animasi:

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

Backport berfungsi dengan baik pada Kindle Fire generasi pertama, meskipun animasinya sedikit tersentak mengingat spesifikasi perangkat keras yang lebih rendah dan kurangnya dukungan akselerasi perangkat keras. Kedua implementasi tampak mulus pada Nexus 7 dan tablet generasi sekarang lainnya.

Saya tentu saja terbuka untuk ide-ide bagaimana meningkatkan solusi ini, atau solusi lain yang menawarkan keuntungan yang jelas atas apa yang saya lakukan di sini (atau apa yang digunakan @weakwire).

Sekali lagi terima kasih kepada semua orang yang telah berkontribusi!

CommonsWare
sumber
1
Hai! Terima kasih atas solusinya, tapi saya pikir, menambahkan v.setLayerType () terkadang membuat segalanya sedikit lebih lambat. Hapus saja garis-garis ini (dan juga Pendengar) Ini setidaknya berlaku untuk perangkat Android 3+ Saya baru saja mencoba Galaxy Nexus dan tanpa garis-garis ini bekerja jauh lebih baik
Evgeny Nacu
1
"Kedua implementasi tampak mulus pada Nexus 7 dan tablet generasi saat ini." Aku percaya kamu; tapi saya sedang menguji implementasi Anda dengan ObjectAnimator asli pada Nexus 7 dan agak terlambat. Apa penyebab paling mungkin? Saya sudah memiliki hardwareAccelerated: true di AndroidMAnifest. Saya benar-benar tidak tahu apa yang bisa menjadi masalah. Saya juga sudah mencoba varian DanielGrech dan tertinggal sama. Saya dapat melihat perbedaan dalam animasi (animasinya didasarkan pada bobot), tetapi saya tidak dapat melihat mengapa hal itu akan tertinggal dalam kedua kasus.
Bogdan Zurac
1
@Andrew: "agak lambat" - Saya tidak yakin bagaimana Anda menggunakan kata kerja "lag" di sini. Bagi saya, "lag" menyiratkan target konkret untuk perbandingan. Jadi, misalnya, animasi yang dikaitkan dengan gerakan menggesekkan mungkin tertinggal di belakang gerakan jari. Dalam hal ini, semuanya dipicu oleh ketukan, jadi saya tidak jelas animasi apa yang mungkin tertinggal. Menebak bahwa mungkin "sedikit keterlambatan" berarti "merasa lamban", saya belum melihat itu, tetapi saya tidak mengklaim memiliki rasa estetika yang kuat, dan apa yang mungkin menjadi animasi yang dapat diterima bagi saya mungkin tidak dapat diterima oleh Anda.
CommonsWare
2
@ CommonsWare: Saya mengkompilasi kode Anda dengan 4.2, kerja bagus. Ketika saya mengetuk bagian tengah ListViewdan menekan tombol kembali dengan cepat dan berulang-ulang maka seluruh Tata Letak melayang ke kanan. Itu mulai menunjukkan kolom ruang ekstra di sebelah kiri. Perilaku ini diperkenalkan karena saya tidak akan membiarkan animasi pertama (dimulai dengan mengetuk di tengah ListView) menyelesaikan dan menekan tombol kembali. Aku tetap dengan mengganti translationXBydengan translationXdi translateWidgetsmetode dan translateWidgets(leftWidth, left, middle, right);dengan translateWidgets(0, left, middle, right);di showLeft()metode.
M-WaJeEh
2
Saya juga diganti requestLayout();dengan middle.requestLayout();di setMiddleWidth(int value);metode. Ini mungkin tidak mempengaruhi kinerja karena saya kira GPU masih akan melakukan pass tata letak penuh, ada komentar?
M-WaJeEh
13

Kami membangun perpustakaan yang disebut PanesLibrary yang memecahkan masalah ini. Ini bahkan lebih fleksibel daripada apa yang ditawarkan sebelumnya karena:

  • Setiap panel bisa berukuran dinamis
  • Ini memungkinkan untuk sejumlah panel (bukan hanya 2 atau 3)
  • Fragmen di dalam panel dipertahankan dengan benar pada perubahan orientasi.

Anda dapat memeriksanya di sini: https://github.com/Mapsaurus/Android-PanesLibrary

Berikut ini demo: http://www.youtube.com/watch?v=UA-lAGVXoLU&feature=youtu.be

Ini pada dasarnya memungkinkan Anda untuk dengan mudah menambahkan sejumlah panel berukuran dinamis dan melampirkan fragmen ke panel itu. Semoga bermanfaat! :)

coda
sumber
6

Membangun salah satu contoh yang Anda tautkan ( http://android.amberfog.com/?p=758 ), bagaimana dengan menjiwai layout_weightproperti? Dengan cara ini, Anda dapat menganimasikan perubahan berat dari 3 fragmen bersama, DAN Anda mendapatkan bonus bahwa mereka semua meluncur bersama dengan baik:

Mulailah dengan tata letak yang sederhana. Karena kita akan menghidupkan layout_weight, kita perlu LinearLayoutsebagai tampilan root untuk 3 panel .:

<?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>

Kemudian kelas demo:

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

Juga contoh ini tidak menggunakan FragmentTransactions untuk mencapai animasi (lebih tepatnya, ia menganimasikan pandangan fragmen yang dilampirkan), jadi Anda perlu melakukan semua transaksi backstack / fragmen sendiri, tetapi dibandingkan dengan upaya membuat animasi berfungsi baik, ini sepertinya bukan trade-off yang buruk :)

Video beresolusi rendah yang mengerikan sedang beraksi: http://youtu.be/Zm517j3bFCo

DanielGrech
sumber
1
Yah, Gmail pasti melakukan terjemahan dari apa yang saya gambarkan sebagai Fragmen A. Saya akan khawatir bahwa mengurangi bobotnya menjadi 0 dapat memperkenalkan artefak visual (misalnya, TextViewdengan wrap_contentmerentangkan dirinya secara vertikal saat animasi berlangsung). Namun, mungkin yang menggerakkan bobot adalah bagaimana mereka mengubah ukuran apa yang saya sebut sebagai Fragment B. Terima kasih!
CommonsWare
1
Jangan khawatir! Tapi itu poin yang bagus, ini kemungkinan akan menyebabkan artefak visual tergantung apa pandangan anak di 'Fragment A'. Mari kita berharap seseorang menemukan cara yang lebih solid untuk melakukannya
DanielGrech
5

Ini tidak menggunakan fragmen .... Ini tata letak khusus untuk 3 anak. Ketika Anda mengklik pesan, Anda mengimbangi 3 anak menggunakan offsetLeftAndRight()dan animator.

Di dalamnya JellyBeanAnda dapat mengaktifkan "Tampilkan batas tata letak" di pengaturan "Opsi Pengembang". Ketika animasi slide selesai, Anda masih dapat melihat bahwa menu kiri masih ada, tetapi di bawah panel tengah.

Ini mirip dengan menu aplikasi Cyril Mottier Fly-in , tetapi dengan 3 elemen, bukan 2.

Selain itu, ViewPageranak-anak ketiga adalah indikasi lain dari perilaku ini: ViewPagerbiasanya menggunakan Fragmen (saya tahu mereka tidak harus melakukannya, tetapi saya belum pernah melihat implementasi yang lain Fragment), dan karena Anda tidak dapat menggunakan Fragmentsdi dalam yang lain Fragment3 anak-anak mungkin bukan fragmen ....

Thierry-Dimitri Roy
sumber
Saya tidak pernah menggunakan "Tampilkan batas tata letak" sebelumnya - itu sangat membantu untuk aplikasi sumber tertutup seperti ini (terima kasih!). Tampaknya apa yang saya gambarkan sebagai Fragment A menerjemahkan di luar layar (Anda dapat melihat tepi kanannya bergeser dari kanan ke kiri), sebelum muncul kembali di bawah apa yang saya jelaskan sebagai Fragment B. Itu terasa seperti TranslateAnimation, meskipun saya yakin ada cara menggunakan animator untuk mencapai tujuan yang sama. Saya harus menjalankan beberapa eksperimen tentang ini. Terima kasih!
CommonsWare
2

Saat ini saya mencoba melakukan sesuatu seperti itu, kecuali skala Fragment B untuk mengambil ruang yang tersedia dan bahwa 3 panel dapat dibuka pada saat yang sama jika ada cukup ruang. Ini solusi saya sejauh ini, tapi saya tidak yakin apakah saya akan tetap menggunakannya. Saya harap seseorang akan memberikan jawaban yang menunjukkan The Right Way.

Alih-alih menggunakan LinearLayout dan menjiwai bobot, saya menggunakan RelativeLayout dan menghidupkan margin. Saya tidak yakin itu cara terbaik karena memerlukan panggilan ke requestLayout () di setiap pembaruan. Ini lancar di semua perangkat saya.

Jadi, saya menghidupkan tata letak, saya tidak menggunakan fragmen transaksi. Saya menangani tombol kembali secara manual untuk menutup fragmen C jika terbuka.

FragmentB menggunakan layout_toLeftOf / ToRightOf agar tetap sejajar dengan fragmen A dan C.

Ketika aplikasi saya memicu suatu acara untuk menampilkan fragmen C, saya geser masuk fragmen C, dan saya geser keluar fragmen A pada saat yang sama. (2 animasi terpisah). Sebaliknya, ketika Fragment A terbuka, saya menutup C pada saat yang sama.

Dalam mode potret atau pada layar yang lebih kecil, saya menggunakan tata letak yang sedikit berbeda dan geser Fragment C di atas layar.

Untuk menggunakan persentase untuk lebar Fragmen A dan C, saya pikir Anda harus menghitungnya pada waktu berjalan ... (?)

Berikut ini tata letak aktivitas:

<?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>

Animasi untuk menggeser masuk atau keluar 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;
}
boblebel
sumber