Hoàn thành mẫu làm việc của Kịch bản hoạt hình ba mảnh của Gmail?


128

TL; DR: Tôi đang tìm kiếm một mẫu hoàn chỉnh về những gì tôi sẽ gọi là kịch bản "hoạt hình ba đoạn của Gmail". Cụ thể, chúng tôi muốn bắt đầu với hai mảnh, như thế này:

hai mảnh

Theo một số sự kiện UI (ví dụ: nhấn vào thứ gì đó trong Đoạn B), chúng tôi muốn:

  • Đoạn A để trượt khỏi màn hình sang trái
  • Đoạn B để trượt sang cạnh trái của màn hình và co lại để chiếm lấy vị trí bị bỏ trống bởi Đoạn A
  • Đoạn C để trượt vào từ bên phải màn hình và chiếm lấy vị trí bị bỏ trống bởi Đoạn B

Và, trên một nút BACK, chúng tôi muốn bộ thao tác đó được đảo ngược.

Bây giờ, tôi đã thấy rất nhiều triển khai một phần; Tôi sẽ xem xét bốn trong số họ dưới đây. Ngoài việc không đầy đủ, tất cả họ đều có vấn đề của họ.


@Reto Meier đã đóng góp câu trả lời phổ biến này cho cùng một câu hỏi cơ bản, chỉ ra rằng bạn sẽ sử dụng setCustomAnimations()với a FragmentTransaction. Đối với kịch bản hai đoạn (ví dụ: ban đầu bạn chỉ nhìn thấy Đoạn A và muốn thay thế nó bằng Đoạn B mới bằng hiệu ứng hoạt hình), tôi hoàn toàn đồng ý. Tuy nhiên:

  • Vì bạn chỉ có thể chỉ định một hoạt hình "trong" và một hoạt hình "ngoài", tôi không thể thấy cách bạn sẽ xử lý tất cả các hoạt ảnh khác nhau cần thiết cho kịch bản ba đoạn
  • Các <objectAnimator>trong mã mẫu của mình sử dụng các vị trí hard-wired bằng pixel, và đó dường như là không thực tế cho thay đổi kích thước màn hình, tuy nhiên setCustomAnimations()đòi hỏi nguồn lực hoạt hình, loại trừ khả năng xác định những điều này trong Java
  • Tôi không biết làm thế nào các nhà hoạt họa đối tượng cho quy mô gắn kết với những thứ như android:layout_weighttrong mộtLinearLayout việc phân bổ không gian trên cơ sở tỷ lệ phần trăm
  • Tôi đang ở một mất mát như thế nào Fragment C được xử lý ngay từ đầu ( GONE? android:layout_weightCủa 0? Pre-hoạt hình để thang điểm từ 0? Cái gì khác?)

@Roman Nurik chỉ ra rằng bạn có thể làm động bất kỳ tài sản nào , kể cả những tài sản mà bạn tự xác định. Điều đó có thể giúp giải quyết vấn đề của các vị trí có dây cứng, với chi phí phát minh ra lớp con trình quản lý bố cục tùy chỉnh của riêng bạn. Điều đó giúp ích cho một số người, nhưng tôi vẫn gặp khó khăn với phần còn lại của giải pháp Reto.


Tác giả của mục pastebin này cho thấy một số mã giả trêu ngươi, về cơ bản nói rằng cả ba mảnh vỡ sẽ nằm trong container ban đầu, với Fragment C được ẩn ngay từ đầu thông qua một hide()hoạt động giao dịch. Chúng tôi sau đó show()C và hide()A khi sự kiện UI xảy ra. Tuy nhiên, tôi không thấy cách xử lý thực tế là B thay đổi kích thước. Nó cũng dựa vào thực tế là bạn rõ ràng có thể thêm nhiều mảnh vỡ vào cùng một thùng chứa, và tôi không chắc đó có phải là hành vi đáng tin cậy trong thời gian dài hay không (không đề cập đến việc nó có bị phá vỡ hay không findFragmentById(), mặc dù tôi có thể sống với điều đó).


Tác giả của bài đăng trên blog này chỉ ra rằng Gmail hoàn toàn không sử dụng setCustomAnimations()mà thay vào đó sử dụng trực tiếp các trình hoạt hình đối tượng ("bạn chỉ cần thay đổi lề trái của chế độ xem gốc + thay đổi độ rộng của chế độ xem bên phải"). Tuy nhiên, đây vẫn là một giải pháp hai mảnh AFAICT và việc triển khai được hiển thị một lần nữa kích thước dây cứng tính bằng pixel.


Tôi sẽ tiếp tục thực hiện điều này, vì vậy tôi có thể tự mình trả lời câu hỏi này vào một ngày nào đó, nhưng tôi thực sự hy vọng rằng ai đó đã tìm ra giải pháp ba đoạn cho kịch bản hoạt hình này và có thể đăng mã (hoặc một liên kết ở đó). Ảnh động trong Android khiến tôi muốn nhổ tóc và những người đã xem tôi biết rằng đây là một nỗ lực không có kết quả.


2
Không phải nó giống như thế này được đăng bởi Romain Guy sao? tò mò
Fernando Gallego

1
@ ferdy182: Anh ấy không sử dụng các mảnh AFAICT. Trực quan, đó là hiệu ứng cơ bản phù hợp và tôi có thể sử dụng điều này như một điểm dữ liệu khác để cố gắng tạo ra một câu trả lời dựa trên phân đoạn. Cảm ơn!
CommonsWare

1
Chỉ cần suy nghĩ: ứng dụng Email tiêu chuẩn có hành vi tương tự (mặc dù không hoàn toàn giống nhau) trên Nexus 7 của tôi - vốn phải là nguồn mở.
Christopher Orr

4
Ứng dụng Email sử dụng "ThreePaneLayout" tùy chỉnh, tạo hiệu ứng cho vị trí của Chế độ xem bên trái và độ rộng / khả năng hiển thị của hai Chế độ xem khác - mỗi Chế độ chứa Đoạn này có liên quan.
Christopher Orr

2
hey @CommonsWare Tôi sẽ không làm phiền ppl với toàn bộ câu trả lời của tôi ở đây, nhưng có một câu hỏi tương tự với bạn trong đó tôi đã đưa ra một câu trả lời rất thỏa đáng. Vì vậy, chỉ đề nghị bạn kiểm tra nó (cũng kiểm tra ý kiến): stackoverflow.com/a/14155204/906362
Budius

Câu trả lời:


67

Đã tải lên đề xuất của tôi tại github (Đang hoạt động với tất cả các phiên bản Android mặc dù khuyến khích tăng tốc phần cứng cho loại hoạt hình này. Đối với các thiết bị không tăng tốc phần cứng, việc triển khai bộ đệm ẩn bitmap sẽ phù hợp hơn)

Video demo với hình ảnh động là đây (Nguyên nhân tốc độ khung hình chậm của màn hình. Hiệu suất thực tế rất nhanh)


Sử dụng:

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

Giải thích :

Tạo một tùy chỉnh mới RelativeLayout (ThreeLayout) và 2 Ảnh động tùy chỉnh (MyScalAnimation, MyTranslateAnimation)

ThreeLayoutlấy trọng lượng của khung bên trái là param, giả sử khung nhìn hiển thị khác có weight=1.

Vì thế new ThreeLayout(context,3) tạo một chế độ xem mới với 3 con và khung bên trái có 1/3 tổng màn hình. Các quan điểm khác chiếm tất cả không gian có sẵn.

Nó tính toán độ rộng khi chạy, một cách thực hiện an toàn hơn là các phép tính được tính lần đầu tiên trong draw (). thay vì trong bài ()

Hoạt hình tỷ lệ và Dịch thực sự thay đổi kích thước và di chuyển chế độ xem và không giả - [tỷ lệ, di chuyển]. Lưu ý rằng fillAfter(true)không được sử dụng ở bất cứ đâu.

View2 là đúng_of View1

View3 là đúng_of View2

Có các quy tắc này RelativeLayout sẽ giải quyết mọi thứ khác. Ảnh động thay đổi margins(khi di chuyển) và [width,height]trên quy mô

Để truy cập từng đứa trẻ (để bạn có thể thổi phồng nó bằng Mảnh vỡ của mình, bạn có thể gọi

public FrameLayout getLeftLayout() {}

public FrameLayout getMiddleLayout() {}

public FrameLayout getRightLayout() {}

Dưới đây được thể hiện 2 hình ảnh động


Giai đoạn 1

--- Ở màn hình ----------! ----- NGOÀI ----

[View1] [_____ View2 _____] [_____ View3___]

Giai đoạn2

- KHÔNG -! -------- Trong màn hình ------

[View1] [View2] [_____ View3___]


1
Mặc dù tôi chưa triển khai một trình hoạt họa tỷ lệ, tôi lo ngại rằng nó sẽ hoạt động tốt đối với các trường màu đặc và kém hơn, chúng ta sẽ nói, nội dung "thực". Kết quả của mảnh B được thay đổi kích thước để phù hợp với không gian của mảnh A không khác gì so với mảnh vỡ B ban đầu được đặt ở đó. Ví dụ: AFAIK, một nhà hoạt họa tỷ lệ sẽ chia tỷ lệ kích thước phông chữ (bằng cách vẽ mọi thứ trong hoạt hình Viewnhỏ hơn), nhưng trong Gmail / Email, chúng tôi không nhận được các phông chữ nhỏ hơn, chỉ cần ít từ hơn.
CommonsWare

1
@CommonsWare scaleAnimation chỉ là một cái tên trừu tượng. Trong thực tế không có quy mô diễn ra. Nó thực sự thay đổi kích thước chiều rộng của khung nhìn. Kiểm tra nguồn. LayoutResizer có thể là một tên tốt hơn cho nó.
buộc

1
Đó không phải là cách giải thích của tôi về nguồn xung quanh scaleXgiá trị trên View, mặc dù tôi có thể hiểu sai mọi thứ.
CommonsWare

1
@CommonsWare kiểm tra github.com/weakwire/3PaneLayout/blob/master/src/com/example/ này ra. Lý do duy nhất tôi mở rộng Hoạt hình là để có quyền truy cập vào Thời gian nội suy. p. thong = (int) width; thực hiện tất cả các phép thuật và nó không phải là scaleX. Đó là các chế độ xem thực tế thay đổi trong thời gian được chỉ định.
buộc

2
Video của bạn cho thấy tốc độ khung hình tốt, nhưng các khung nhìn được sử dụng trong ví dụ của bạn rất đơn giản. Thực hiện một requestLayout () trong một hình ảnh động rất tốn kém. Đặc biệt là nếu trẻ em phức tạp (ví dụ như ListView). Điều này có thể sẽ dẫn đến một hình ảnh động choppy. Ứng dụng GMail không gọi requestLayout. Thay vào đó, một khung nhìn nhỏ hơn được đưa vào bảng giữa ngay trước khi hoạt ảnh bắt đầu.
Thierry-Dimitri Roy

31

OK, đây là giải pháp của riêng tôi, xuất phát từ ứng dụng Email AOSP, theo đề xuất của @ Christopher trong các bình luận của câu hỏi.

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

Giải pháp của @ poorwire gợi nhớ đến tôi, mặc dù anh ấy sử dụng cổ điển Animationthay vì hoạt hình và anh ấy sử dụng RelativeLayoutcác quy tắc để thực thi định vị. Từ quan điểm tiền thưởng, anh ta có thể sẽ nhận được tiền thưởng, trừ khi có ai đó với giải pháp lắt léo chưa đăng câu trả lời.


Tóm lại, ThreePaneLayouttrong dự án đó là một LinearLayoutlớp con, được thiết kế để làm việc trong cảnh quan với ba đứa trẻ. Độ rộng của những đứa trẻ đó có thể được đặt trong XML bố cục, thông qua bất kỳ phương tiện mong muốn nào - Tôi hiển thị bằng cách sử dụng trọng số, nhưng bạn có thể có chiều rộng cụ thể được đặt theo tài nguyên thứ nguyên hoặc bất cứ thứ gì. Đứa con thứ ba - Đoạn C trong câu hỏi - nên có chiều rộng bằng không.

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

Tuy nhiên, trong thời gian chạy, bất kể ban đầu bạn thiết lập độ rộng như thế nào, việc quản lý độ rộng được thực hiện ThreePaneLayoutngay lần đầu tiên bạn sử dụng hideLeft()để chuyển từ hiển thị câu hỏi được gọi là Đoạn A và B sang Đoạn B và C. Theo thuật ngữ của ThreePaneLayout- mà không có mối quan hệ cụ thể để mảnh - ba mảnh là left, middle, và right. Đồng thời bạn gọi hideLeft(), chúng tôi ghi lại các kích thước của leftmiddlevà không ra bất kỳ trọng lượng đã được sử dụng trên bất kỳ của ba, vì vậy chúng tôi hoàn toàn có thể kiểm soát kích cỡ. Tại thời điểm đó hideLeft(), chúng tôi đặt kích thước rightlà kích thước ban đầu của middle.

Các hình ảnh động là hai lần:

  • Sử dụng một ViewPropertyAnimatorđể thực hiện một bản dịch của ba vật dụng sang trái theo chiều rộng left, sử dụng một lớp phần cứng
  • Sử dụng một ObjectAnimatorthuộc tính giả tùy chỉnh của middleWidthđể thay đổi middleđộ rộng từ bất cứ thứ gì nó bắt đầu với chiều rộng ban đầu củaleft

(có thể là một ý tưởng tốt hơn để sử dụng một AnimatorSetObjectAnimatorscho tất cả những thứ này, mặc dù điều này hiện đang hoạt động)

(cũng có thể middleWidth ObjectAnimatorphủ nhận giá trị của lớp phần cứng, vì điều đó đòi hỏi phải có hiệu lực khá liên tục)

(điều chắc chắn là tôi vẫn còn những khoảng trống trong khả năng hiểu hoạt hình của mình và tôi thích những tuyên bố mang tính phụ huynh)

Hiệu ứng ròng là lefttrượt khỏi màn hình, middletrượt đến vị trí và kích thước ban đầu left, và rightdịch ngay phía sau middle.

showLeft() chỉ đơn giản là đảo ngược quá trình, với cùng một hỗn hợp các nhà làm phim hoạt hình, chỉ với các hướng đảo ngược.

Hoạt động sử dụng a ThreePaneLayoutđể giữ một cặp ListFragmentvật dụng và a Button. Chọn một cái gì đó trong đoạn bên trái sẽ thêm (hoặc cập nhật nội dung của) đoạn giữa. Chọn một cái gì đó trong đoạn giữa đặt chú thích của dấu Buttoncộng, cộng với thực thi hideLeft()trên ThreePaneLayout. Nhấn BACK, nếu chúng ta ẩn bên trái, sẽ thực thi showLeft(); mặt khác, BACK thoát khỏi hoạt động. Vì điều này không được sử dụng FragmentTransactionsđể ảnh hưởng đến hình ảnh động, chúng tôi bị mắc kẹt trong việc quản lý "ngăn xếp ngược" đó.

Dự án được liên kết với ở trên sử dụng các đoạn gốc và khung hoạt hình gốc. Tôi có một phiên bản khác của cùng một dự án sử dụng backport mảnh vỡ Hỗ trợ Android và NineOldAndroids cho hoạt hình:

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

Backport hoạt động tốt trên Kindle Fire thế hệ 1, mặc dù hình ảnh động hơi giật do thông số kỹ thuật phần cứng thấp hơn và thiếu hỗ trợ tăng tốc phần cứng. Cả hai triển khai đều có vẻ trơn tru trên Nexus 7 và các máy tính bảng thế hệ hiện tại khác.

Tôi chắc chắn mở cho các ý tưởng về cách cải thiện giải pháp này hoặc các giải pháp khác mang lại lợi thế rõ ràng so với những gì tôi đã làm ở đây (hoặc những gì @weakwire đã sử dụng).

Cảm ơn một lần nữa cho tất cả những người đã đóng góp!


1
Chào! Cảm ơn giải pháp, nhưng tôi nghĩ rằng, việc thêm v.setLayerType () đôi khi khiến mọi thứ chậm hơn một chút. Chỉ cần xóa các dòng này (và cả Listener nữa) Điều này ít nhất đúng với các thiết bị Android 3+ tôi đã thử trên Galaxy Nexus và không có các dòng này hoạt động tốt hơn nhiều
Evgeny Nacu

1
"Cả hai triển khai đều có vẻ trơn tru trên Nexus 7 và các máy tính bảng thế hệ hiện tại khác." Tôi tin bạn; nhưng tôi đang thử nghiệm triển khai của bạn với ObjectAnimator gốc trên Nexus 7 và nó chậm hơn một chút. Điều gì sẽ là nguyên nhân có thể xảy ra nhất? Tôi đã có phần cứngAccelerated: true trong AndroidMAnifest. Tôi thực sự không biết những gì có thể là vấn đề. Tôi cũng đã thử biến thể của DanielGrech và nó cũng chậm như vậy. Tôi có thể thấy sự khác biệt trong hoạt hình (hoạt hình của anh ấy dựa trên trọng lượng), nhưng tôi không thể hiểu tại sao nó lại bị trễ trong cả hai trường hợp.
Bogdan Zurac

1
@Andrew: "nó chậm một chút" - Tôi không chắc bạn sử dụng động từ "lag" ở đây như thế nào. Đối với tôi, "độ trễ" ngụ ý một mục tiêu cụ thể để so sánh. Vì vậy, ví dụ, một hình ảnh động gắn liền với chuyển động vuốt có thể tụt lại phía sau chuyển động của ngón tay. Trong trường hợp này, mọi thứ được kích hoạt bởi các vòi, và vì vậy tôi không rõ hoạt hình có thể bị tụt lại phía sau. Đoán rằng có lẽ "chậm một chút" chỉ đơn giản là "cảm thấy chậm chạp", tôi không thấy điều đó, nhưng tôi không khẳng định có ý thức thẩm mỹ mạnh mẽ, và vì vậy những gì có thể là một hoạt hình chấp nhận được đối với tôi.
CommonsWare

2
@CommonsWare: Tôi đã biên dịch mã của bạn với 4.2, công việc tuyệt vời. Khi tôi gõ nhẹ vào giữa ListViewvà nhấn nút quay lại nhanh chóng và lặp lại thì toàn bộ Bố cục trôi sang phải. Nó bắt đầu hiển thị thêm cột không gian bên trái. Hành vi này được giới thiệu vì tôi sẽ không để hoạt hình đầu tiên (bắt đầu bằng cách nhấn vào giữa ListView) hoàn thành và nhấn nút quay lại. Tôi cố định nó bằng cách thay thế translationXBybằng translationXtrong translateWidgetsphương pháp và translateWidgets(leftWidth, left, middle, right);với translateWidgets(0, left, middle, right);trong showLeft()phương pháp.
M-WaJeEh

2
Tôi cũng thay thế requestLayout();bằng middle.requestLayout();trong setMiddleWidth(int value);phương pháp. Nó có thể không ảnh hưởng đến hiệu suất vì tôi đoán GPU vẫn sẽ thực hiện một bố cục đầy đủ, có nhận xét nào không?
M-WaJeEh

13

Chúng tôi đã xây dựng một thư viện có tên PanesL Library để giải quyết vấn đề này. Nó thậm chí còn linh hoạt hơn những gì đã được cung cấp trước đây bởi vì:

  • Mỗi khung có thể được kích thước động
  • Nó cho phép bất kỳ số lượng tấm (không chỉ 2 hoặc 3)
  • Các mảnh vỡ bên trong các tấm được giữ lại chính xác trên các thay đổi định hướng.

Bạn có thể kiểm tra nó ở đây: https://github.com/Mapsaurus/Android-PanesL Library

Đây là bản demo: http://www.youtube.com/watch?v=UA-lAGVXoLU&feature=youtu.be

Về cơ bản, nó cho phép bạn dễ dàng thêm bất kỳ số lượng tấm có kích thước động và gắn các mảnh vỡ vào các tấm đó. Hi vọng bạn tìm được thứ hữu dụng! :)


6

Xây dựng một trong những ví dụ bạn đã liên kết đến ( http://android.amberfog.com/?p=758 ), làm thế nào về hoạt hình của layout_weighttài sản? Bằng cách này, bạn có thể làm động các thay đổi về trọng lượng của 3 mảnh cùng nhau, VÀ bạn nhận được phần thưởng mà tất cả chúng đều trượt cùng nhau:

Bắt đầu với một bố cục đơn giản. Vì chúng tôi sẽ hoạt hình layout_weight, chúng tôi cần một LinearLayoutchế độ xem gốc cho 3 bảng.:

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

Sau đó là lớp 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;
        }
    }
}

Ngoài ra, ví dụ này không sử dụng FragmentTransilities để đạt được hình ảnh động (thay vào đó, nó hoạt hình các khung nhìn mà các đoạn được đính kèm), vì vậy bạn sẽ cần phải tự mình thực hiện tất cả các giao dịch backstack / Fragment, nhưng so với nỗ lực làm cho hoạt hình hoạt động độc đáo, điều này dường như không phải là một sự đánh đổi tồi tệ :)

Video độ phân giải thấp khủng khiếp của nó đang hoạt động: http://youtu.be/Zm517j3bFCo


1
Chà, Gmail chắc chắn thực hiện một bản dịch của những gì tôi mô tả là Fragment A. Tôi lo lắng rằng việc giảm trọng lượng của nó xuống 0 có thể giới thiệu các tạo tác trực quan (ví dụ: TextViewvới việc wrap_contentkéo dài ra theo chiều dọc khi hoạt hình diễn ra). Tuy nhiên, có thể hoạt hình trọng lượng là cách họ thay đổi kích thước những gì tôi gọi là Fragment B. Cảm ơn!
CommonsWare

1
Đừng lo lắng! Tuy nhiên, đó là một điểm tốt, điều này có thể sẽ gây ra các tạo tác trực quan tùy thuộc vào quan điểm của trẻ em trong 'Đoạn A'. Hãy hy vọng ai đó tìm thấy một cách vững chắc hơn để làm điều đó
DanielGrech

5

Đây không phải là sử dụng các đoạn .... Đó là một bố cục tùy chỉnh với 3 đứa trẻ. Khi bạn bấm vào một tin nhắn, bạn bù đắp cho 3 đứa trẻ bằng cách sử dụng offsetLeftAndRight()và một hoạt họa.

Trong JellyBeanbạn có thể bật "Hiển thị giới hạn bố cục" trong cài đặt "Tùy chọn nhà phát triển". Khi hoạt hình slide hoàn thành, bạn vẫn có thể thấy menu bên trái vẫn ở đó, nhưng bên dưới bảng giữa.

Nó tương tự như menu ứng dụng Fly-in của Cyril Mottier , nhưng có 3 yếu tố thay vì 2.

Ngoài ra, những ViewPagerđứa trẻ thứ ba là một dấu hiệu khác của hành vi này: ViewPagerthường sử dụng Fragment (tôi biết chúng không phải, nhưng tôi chưa bao giờ thấy một triển khai nào khác Fragment) và vì bạn không thể sử dụng Fragmentsbên trong Fragment3 đứa trẻ khác có lẽ không phải là những mảnh vỡ ....


Tôi chưa bao giờ sử dụng "Hiển thị giới hạn bố cục" trước đây - điều này rất hữu ích cho các ứng dụng nguồn đóng như thế này (cảm ơn!). Dường như những gì tôi mô tả là Đoạn A đang dịch ngoài màn hình (bạn có thể thấy cạnh phải của nó trượt từ phải sang trái), trước khi bật lại bên dưới những gì tôi mô tả là Mảnh vỡ B. TranslateAnimationMặc dù tôi cảm thấy như vậy chắc chắn có nhiều cách sử dụng hoạt hình để thực hiện cùng một kết thúc. Tôi sẽ phải chạy một số thí nghiệm về điều này. Cảm ơn!
CommonsWare

2

Tôi hiện đang cố gắng làm một cái gì đó tương tự, ngoại trừ thang đo Fragment B để lấy không gian có sẵn và khung 3 có thể được mở cùng lúc nếu có đủ chỗ. Đây là giải pháp của tôi cho đến nay, nhưng tôi không chắc liệu tôi có gắn bó với nó không. Tôi hy vọng ai đó sẽ cung cấp một câu trả lời hiển thị The Right Way.

Thay vì sử dụng linearLayout và tạo hiệu ứng trọng lượng, tôi sử dụng RelativeLayout và làm động các lề. Tôi không chắc chắn đó là cách tốt nhất vì nó yêu cầu một cuộc gọi đến requestLayout () tại mỗi bản cập nhật. Nó mượt mà trên tất cả các thiết bị của tôi.

Vì vậy, tôi làm động bố cục, tôi không sử dụng giao dịch mảnh. Tôi xử lý nút quay lại bằng tay để đóng đoạn C nếu nó đang mở.

FragmentB sử dụng layout_toLeftOf / ToRightOf để giữ cho nó được căn chỉnh thành đoạn A và C.

Khi ứng dụng của tôi kích hoạt một sự kiện để hiển thị đoạn C, tôi trượt vào đoạn C và tôi trượt ra đoạn A cùng một lúc. (2 hình động riêng biệt). Ngược lại, khi Fragment A mở, tôi đóng C cùng một lúc.

Ở chế độ dọc hoặc trên màn hình nhỏ hơn, tôi sử dụng bố cục hơi khác và trượt Fragment C trên màn hình.

Để sử dụng tỷ lệ phần trăm cho chiều rộng của Đoạn A và C, tôi nghĩ bạn sẽ phải tính toán nó trong thời gian chạy ... (?)

Đây là cách bố trí của hoạt động:

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

Hoạt hình để trượt FragmentC vào hoặc ra:

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;
}
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.