ตัวอย่างการทำงานที่สมบูรณ์ของภาพเคลื่อนไหวสามมิติของ Gmail หรือไม่


128

TL; DR: ฉันกำลังมองหาตัวอย่างการทำงานที่สมบูรณ์ของสิ่งที่ฉันจะอ้างถึงว่าเป็น "ภาพเคลื่อนไหวสามมิติของ Gmail" โดยเฉพาะเราต้องการเริ่มต้นด้วยสองแฟรกเมนต์เช่นนี้:

สองชิ้น

เมื่อมีเหตุการณ์ UI บางอย่าง (เช่นแตะบางสิ่งใน Fragment B) เราต้องการ:

  • แฟรกเมนต์ A เพื่อเลื่อนหน้าจอไปทางซ้าย
  • แฟรกเมนต์ B เพื่อเลื่อนไปที่ขอบด้านซ้ายของหน้าจอและย่อขนาดเพื่อดึงตำแหน่งที่ว่างโดย Fragment A
  • แฟรกเมนต์ C เพื่อเลื่อนจากด้านขวาของหน้าจอและเพื่อจับภาพที่ว่างโดย Fragment B

และในการกดปุ่ม BACK เราต้องการให้ชุดการทำงานนั้นกลับด้าน

ตอนนี้ฉันเห็นการใช้งานบางส่วนมากมาย ฉันจะตรวจสอบสี่ของพวกเขาด้านล่าง พวกเขาต่างก็มีปัญหา


@Reto ไมเออร์มีส่วนคำตอบนี้เป็นที่นิยมกับคำถามพื้นฐานที่เหมือนกันแสดงให้เห็นว่าคุณจะใช้กับsetCustomAnimations() FragmentTransactionสำหรับสถานการณ์สองส่วน (เช่นคุณเห็นเฉพาะ Fragment A ในตอนแรกและต้องการแทนที่ด้วย Fragment B ใหม่โดยใช้เอฟเฟกต์แบบเคลื่อนไหว) ฉันอยู่ในข้อตกลงที่สมบูรณ์ อย่างไรก็ตาม:

  • เนื่องจากคุณสามารถระบุได้เพียงหนึ่งภาพเคลื่อนไหว "ใน" และ "ออก" หนึ่งภาพฉันไม่สามารถดูว่าคุณจะจัดการกับภาพเคลื่อนไหวต่าง ๆ ที่จำเป็นสำหรับสถานการณ์สามส่วนได้อย่างไร
  • <objectAnimator>ในตัวอย่างรหัสของเขาใช้ตำแหน่งยากสายพิกเซลและที่ดูเหมือนจะเป็นไปไม่ได้ที่ได้รับแตกต่างกันขนาดหน้าจอยังsetCustomAnimations()ต้องใช้ทรัพยากรเคลื่อนไหว precluding ความเป็นไปได้ของการกำหนดสิ่งเหล่านี้ในชวา
  • ฉันกำลังสูญเสียว่าอนิเมเตอร์ออบเจกต์มีความสัมพันธ์กับสิ่งต่าง ๆ เช่นandroid:layout_weightในการLinearLayoutจัดสรรพื้นที่เป็นเปอร์เซ็นต์ได้อย่างไร
  • ฉันสูญเสียวิธีจัดการกับ Fragment C ในตอนแรก ( GONE? android:layout_weightของ0ภาพเคลื่อนไหวก่อนถึงระดับ 0 เป็นอย่างอื่น?)

@Roman Nurik ชี้ให้เห็นว่าคุณสามารถเคลื่อนไหวทรัพย์สินใด ๆรวมถึงสิ่งที่คุณกำหนดเอง ที่สามารถช่วยแก้ปัญหาตำแหน่งสายแข็งได้ด้วยค่าใช้จ่ายในการประดิษฐ์คลาสย่อยตัวจัดการโครงร่างที่กำหนดเองของคุณเอง มันช่วยได้บ้าง แต่ฉันก็ยังงงกับการแก้ปัญหาของ Reto ที่เหลืออยู่


ผู้เขียนรายการ pastebin นี้จะแสดงรหัสปลอมที่เย้ายวนใจโดยพื้นฐานแล้วบอกว่าทั้งสามแฟรกเมนต์จะอยู่ในคอนเทนเนอร์ในตอนแรกโดยมีแฟรกเมนต์ C ซ่อนอยู่ตั้งแต่แรกผ่านการhide()ทำธุรกรรม จากนั้นเราจะshow()C และhide()A เมื่อเหตุการณ์ UI เกิดขึ้น อย่างไรก็ตามฉันไม่เห็นวิธีที่จัดการกับความจริงที่ว่า B เปลี่ยนขนาด นอกจากนี้ยังขึ้นอยู่กับความจริงที่ว่าคุณสามารถเพิ่มชิ้นส่วนหลายชิ้นลงในภาชนะเดียวกันและฉันไม่แน่ใจว่าพฤติกรรมที่เชื่อถือได้ในระยะยาวหรือไม่ (ไม่ต้องพูดถึงมันควรแตกfindFragmentById()แต่ฉันสามารถอยู่กับมันได้)


ผู้เขียนบทความในบล็อกนี้ระบุว่า Gmail ไม่ได้ใช้setCustomAnimations()งานเลย แต่ใช้ภาพเคลื่อนไหวของวัตถุโดยตรง ("คุณเพียงแค่เปลี่ยนระยะขอบซ้ายของมุมมองราก + เปลี่ยนความกว้างของมุมมองด้านขวา") อย่างไรก็ตามนี่ยังคงเป็นโซลูชันสองส่วน AFAICT และการใช้งานจะแสดงมิติฮาร์ดเส้นในพิกเซลอีกครั้ง


ฉันจะเสียบปลั๊กต่อไปดังนั้นฉันอาจยุติการตอบคำถามนี้สักวัน แต่ฉันหวังว่าจะมีใครบางคนทำงานแก้ปัญหาสามส่วนสำหรับสถานการณ์ภาพเคลื่อนไหวนี้และสามารถโพสต์โค้ด (หรือลิงก์) แอนิเมชั่นใน Android ทำให้ฉันอยากดึงผมออกมาและพวกคุณที่เห็นฉันรู้ว่านี่เป็นความพยายามที่ไร้ผลส่วนใหญ่


2
มันไม่ใช่สิ่งที่โพสต์โดย Romain Guy ใช่ไหม อยากรู้อยากเห็น-creature.org/2011/02/22/…
เฟอร์นันโด Gallego

1
@ ferdy182: เขาไม่ได้ใช้ชิ้นส่วน AFAICT มองเห็นว่ามันเป็นเอฟเฟกต์พื้นฐานที่เหมาะสมและฉันอาจใช้เป็นจุดข้อมูลอื่นสำหรับการพยายามสร้างคำตอบตามแฟรกเมนต์ ขอบคุณ!
CommonsWare

1
แค่คิด: แอปอีเมลมาตรฐานมีพฤติกรรมคล้ายกัน (แม้ว่าจะไม่เหมือนกัน) ใน Nexus 7 ของฉัน - ซึ่งควรเป็นโอเพนซอร์ส
Christopher Orr

4
แอปอีเมลใช้ "ThreePaneLayout" แบบกำหนดเองซึ่งเคลื่อนไหวตำแหน่งมุมมองด้านซ้ายและความกว้าง / การมองเห็นของอีกสองมุมมองซึ่งแต่ละส่วนจะมีส่วนที่เกี่ยวข้อง
Christopher Orr

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รับน้ำหนักของบานหน้าต่างด้านซ้ายเป็นพระรามสมมติว่ามุมมองที่มองเห็นอื่น ๆ weight=1ที่มี

ดังนั้นnew ThreeLayout(context,3)สร้างมุมมองใหม่โดยมีลูก 3 คนและบานหน้าต่างด้านซ้ายมี 1/3 ของหน้าจอทั้งหมด อีกมุมมองหนึ่งใช้พื้นที่ทั้งหมดที่มี

มันคำนวณความกว้างที่รันไทม์การใช้งานที่ปลอดภัยยิ่งขึ้นคือการคำนวณมิติจะถูกคำนวณเป็นครั้งแรกในรูปวาด () แทนโพสต์ ()

ขนาดภาพเคลื่อนไหวและแปลความจริงปรับขนาดและย้ายมุมมองและไม่หลอก - [ปรับขนาด, ย้าย] สังเกตfillAfter(true)ว่าไม่ได้ใช้ที่ไหน

View2 คือ right_of View1

และ

View3 คือ right_of View2

มีการตั้งกฎเหล่านี้ RelativeLayout ดูแลทุกอย่างอื่น แอนิเมชั่นจะเปลี่ยนmargins(ขณะเดินทาง) และ[width,height]ตามขนาด

ในการเข้าถึงเด็กแต่ละคน (เพื่อให้คุณสามารถขยายด้วยแฟรกเมนต์ของคุณคุณสามารถโทรหา

public FrameLayout getLeftLayout() {}

public FrameLayout getMiddleLayout() {}

public FrameLayout getRightLayout() {}

ด้านล่างนี้แสดงให้เห็นถึงภาพเคลื่อนไหว 2 ภาพ


Stage1

--- ในหน้าจอ ----------! ----- ออก ----

[View1] [_____ _____ view2] [_____ View3_____]

Stage2

--OUT -! -------- ในหน้าจอ ------

[View1] [view2] [_____ View3_____]


1
ในขณะที่ฉันยังไม่ได้นำแอนิเมชั่นสเกลมาใช้ แต่ฉันกังวลว่ามันจะทำงานได้ดีสำหรับสีที่เป็นของแข็งและไม่ค่อยดีเท่าไหร่ ผลลัพธ์ของ Fragment B ที่ถูกปรับขนาดให้พอดีกับพื้นที่ของ Fragment A ไม่ควรต่างไปจากเดิมถ้า Fragment B ถูกวางไว้ที่เดิม ตัวอย่างเช่น AFAIK แอนิเมชั่นสเกลจะปรับขนาดตัวอักษร (โดยการวาดทุกอย่างในแอนิเมชั่นที่Viewเล็กกว่า) แต่ใน Gmail / อีเมลเราไม่ได้รับฟอนต์ขนาดเล็กเพียงแค่จำนวนคำที่น้อยลง
CommonsWare

1
@CommonsWare scaleAnimation เป็นเพียงชื่อนามธรรม ในความเป็นจริงไม่มีการปรับขนาดเกิดขึ้น จริง ๆ แล้วปรับขนาดความกว้างของมุมมอง ตรวจสอบแหล่งที่มา LayoutResizer อาจเป็นชื่อที่ดีกว่า
อ่อนแอไว

1
นั่นไม่ใช่การตีความของฉันเกี่ยวกับแหล่งที่มาโดยรอบscaleXคุณค่าบนViewแม้ว่าฉันอาจตีความสิ่งต่าง ๆ ผิด
CommonsWare

1
@CommonsWare ตรวจสอบgithub.com/weakwire/3PaneLayout/blob/master/src/com/example/.. out เหตุผลเดียวที่ฉันขยาย Animation คือการเข้าถึง interpolatedTime p.width = (int) ความกว้าง; ทำทุกสิ่งมหัศจรรย์และมันไม่ได้เป็น scaleX เป็นมิติการดูที่เกิดขึ้นจริงในช่วงเวลาที่กำหนด
weakwire

2
วิดีโอของคุณแสดงอัตราเฟรมที่ดี แต่มุมมองที่ใช้ในตัวอย่างของคุณนั้นง่ายมาก การทำ RequestLayout () ระหว่างภาพเคลื่อนไหวมีค่าใช้จ่ายสูงมาก โดยเฉพาะถ้าเด็ก ๆ มีความซับซ้อน (เช่น ListView เป็นต้น) ซึ่งอาจส่งผลให้ภาพเคลื่อนไหวเปลี่ยนแปลงเร็ว แอป GMail ไม่ได้เรียกร้องเลย์เอาต์ แต่จะมีการนำมุมมองขนาดเล็กอื่นมาวางไว้ที่แผงกลางก่อนที่ภาพเคลื่อนไหวจะเริ่มขึ้น
ธีรี่ร์ - ดิมิทรีรอย

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()เพื่อสลับจากการแสดงสิ่งที่คำถามที่เรียกว่า Fragments A และ B เป็น Fragments B และ C ในคำศัพท์ของThreePaneLayout- ซึ่งไม่มีความสัมพันธ์ที่เฉพาะเจาะจงกับชิ้นส่วน - สามชิ้นleft, และmiddle rightในเวลาที่คุณติดต่อhideLeft()เราจะบันทึกขนาดleftและmiddleและไม่มีน้ำหนักใด ๆ ที่ใช้กับสามอย่างใดอย่างหนึ่งเพื่อให้เราสามารถควบคุมขนาดได้อย่างสมบูรณ์ ที่จุดในเวลาที่hideLeft()เราจะกำหนดขนาดของจะเป็นขนาดเดิมของrightmiddle

แอนิเมชันเป็นสองเท่า:

  • ใช้ a ViewPropertyAnimatorเพื่อทำการแปลสามวิดเจ็ตไปทางซ้ายตามความกว้างleftโดยใช้เลเยอร์ฮาร์ดแวร์
  • ใช้ObjectAnimatorคุณสมบัติหลอกที่กำหนดเองของmiddleWidthเพื่อเปลี่ยนmiddleความกว้างจากสิ่งที่เริ่มด้วยความกว้างดั้งเดิมของleft

(เป็นไปได้ว่ามันเป็นความคิดที่ดีกว่าที่จะใช้AnimatorSetและObjectAnimatorsสำหรับสิ่งเหล่านี้แม้ว่าจะได้ผลในขณะนี้)

(อาจเป็นไปได้ว่าmiddleWidth ObjectAnimatornegates ค่าของเลเยอร์ฮาร์ดแวร์เนื่องจากต้องมีการตรวจสอบความถูกต้องอย่างต่อเนื่อง)

(เป็นไปได้อย่างแน่นอนว่าฉันยังมีช่องว่างในความเข้าใจแอนิเมชั่นของฉันและฉันชอบข้อความเกี่ยวกับผู้ปกครอง)

ผลกระทบสุทธิเป็นที่leftสไลด์ปิดหน้าจอmiddleสไลด์ไปยังตำแหน่งเดิมและขนาดของleftและแปลในที่อยู่เบื้องหลังขวาrightmiddle

showLeft() เพียงแค่ย้อนกลับกระบวนการโดยมีอนิเมเตอร์ผสมกันเพียงทิศทางที่ตรงกันข้าม

กิจกรรมที่ใช้ThreePaneLayoutในการถือคู่ของเครื่องมือและListFragment Buttonการเลือกบางอย่างในแฟรกเมนต์ด้านซ้ายจะเพิ่ม (หรืออัปเดตเนื้อหาของ) แฟรกเมนต์กลาง เลือกสิ่งที่อยู่ในส่วนตรงกลางชุดคำอธิบายภาพของButtonบวกรันบนhideLeft() ThreePaneLayoutกลับกดถ้าเราซ่อนตัวอยู่ด้านซ้ายที่จะดำเนินการshowLeft(); มิฉะนั้น BACK จะออกจากกิจกรรม เนื่องจากสิ่งนี้ไม่ได้ใช้FragmentTransactionsสำหรับส่งผลกระทบต่ออนิเมชั่นเราจึงติดการจัดการที่ "back stack" ของเรา

โครงการที่ลิงก์ไปยังด้านบนใช้แฟรกเมนต์ดั้งเดิมและเฟรมเวิร์กแอนิเมเตอร์ ฉันมีโครงการเดียวกันอีกเวอร์ชันหนึ่งที่ใช้ชิ้นส่วนสนับสนุน Android backport และNineOldAndroidsสำหรับภาพเคลื่อนไหว:

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

Backport ใช้งานได้ดีกับ Kindle Fire รุ่นที่ 1 แม้ว่าภาพเคลื่อนไหวจะค่อนข้างกระตุกเล็กน้อยเนื่องจากรายละเอียดของฮาร์ดแวร์ที่ต่ำกว่าและขาดการรองรับการเร่งด้วยฮาร์ดแวร์ การใช้งานทั้งสองอย่างราบรื่นใน Nexus 7 และแท็บเล็ตรุ่นปัจจุบัน

แน่นอนว่าฉันเปิดรับแนวคิดในการปรับปรุงโซลูชันนี้หรือโซลูชันอื่น ๆ ที่ให้ข้อได้เปรียบที่ชัดเจนกับสิ่งที่ฉันทำที่นี่ (หรือสิ่งที่ @weakwire ใช้)

ขอบคุณอีกครั้งสำหรับทุกคนที่มีส่วนร่วม!


1
Hi! ขอบคุณสำหรับการแก้ปัญหา แต่ฉันคิดว่าการเพิ่ม v.setLayerType () บางครั้งทำให้สิ่งต่าง ๆ ช้าลงเล็กน้อย เพิ่งลบบรรทัดเหล่านี้ (และผู้ฟังด้วย) อย่างน้อยก็เป็นจริงสำหรับอุปกรณ์ Android 3+ ที่ฉันเพิ่งลองบน Galaxy Nexus และหากไม่มีบรรทัดเหล่านี้มันก็ทำงานได้ดีขึ้นมาก
Evgeny Nacu

1
"การติดตั้งทั้งสองอย่างราบรื่นใน Nexus 7 และแท็บเล็ตรุ่นปัจจุบัน" ฉันเชื่อคุณ; แต่ฉันกำลังทดสอบการใช้งานของคุณกับ ObjectAnimator ดั้งเดิมบน Nexus 7 และมันก็ใช้เวลาไม่นาน อะไรคือสาเหตุที่เป็นไปได้มากที่สุด ฉันมีฮาร์ดแวร์ที่ได้รับการเร่งความเร็ว: จริงใน AndroidMAnifest ฉันไม่รู้จริงๆว่าอะไรเป็นปัญหา ฉันได้ลองใช้ตัวแปรของ DanielGrech แล้วและมันก็ช้าเหมือนกัน ฉันเห็นความแตกต่างในอนิเมชั่น (อนิเมชั่นของเขาขึ้นอยู่กับน้ำหนัก) แต่ฉันไม่สามารถเห็นได้ว่าทำไมมันจึงล่าช้าทั้งสองกรณี
Bogdan Zurac

1
@ แอนดรูว์: "มันช้าไปหน่อย" - ฉันไม่แน่ใจว่าคุณใช้กริยา "ล้าหลัง" ที่นี่ได้อย่างไร สำหรับฉัน "ล่าช้า" หมายถึงเป้าหมายที่ชัดเจนสำหรับการเปรียบเทียบ ยกตัวอย่างเช่นภาพเคลื่อนไหวที่ผูกติดกับท่าทางการปัดอาจล่าช้าหลังการเคลื่อนไหวนิ้ว ในกรณีนี้ทุกอย่างถูกเปิดใช้งานโดยการแตะดังนั้นฉันจึงไม่ชัดเจนว่าภาพเคลื่อนไหวอาจล้าหลัง การคาดเดาว่าบางที "ล่าช้าเล็กน้อย" ก็หมายความว่า "รู้สึกเฉื่อยชา" ฉันไม่เคยเห็นมาก่อน แต่ฉันไม่ได้อ้างว่ามีความรู้สึกสวยงามและดังนั้นสิ่งที่อาจเป็นภาพเคลื่อนไหวที่ยอมรับได้สำหรับฉันอาจเป็นที่ยอมรับไม่ได้สำหรับคุณ
CommonsWare

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();ในsetMiddleWidth(int value);วิธีการ มันอาจจะไม่ส่งผลต่อประสิทธิภาพอย่างที่ฉันคิดว่า GPU จะยังคงทำรูปแบบเต็มความคิดเห็นหรือไม่?
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อสังหาริมทรัพย์ ด้วยวิธีนี้คุณสามารถเคลื่อนไหวการเปลี่ยนแปลงน้ำหนักของ 3 แฟรกเมนต์ด้วยกันและคุณจะได้รับโบนัสที่พวกมันทั้งหมดเลื่อนเข้าหากัน:

เริ่มต้นด้วยเค้าโครงที่เรียบง่าย เนื่องจากเรากำลังจะทำแอlayout_weightนิเมชั่นเราจำเป็นต้องมีLinearLayoutมุมมองรูทสำหรับ 3 พาเนล:

<?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การยืดตัวเองออกแนวตั้งเป็นเงินเคลื่อนไหว) อย่างไรก็ตามอาจเป็นได้ว่าการเคลื่อนไหวน้ำหนักเป็นวิธีที่พวกเขาปรับขนาดสิ่งที่ฉันเรียกว่า Fragment B. ขอบคุณ!
CommonsWare

1
ไม่ต้องห่วง! แม้ว่าจะเป็นจุดที่ดี แต่สิ่งนี้อาจทำให้เกิดสิ่งประดิษฐ์ทางสายตาขึ้นอยู่กับมุมมองของเด็กที่อยู่ใน 'Fragment A' ให้หวังว่าจะมีคนค้นพบวิธีที่มั่นคงกว่าในการทำมัน
DanielGrech

5

นี่ไม่ได้ใช้แฟรกเมนต์ .... มันเป็นเลย์เอาต์แบบกำหนดเองที่มีลูก 3 คน เมื่อคุณคลิกที่ข้อความคุณจะชดเชยการใช้เด็ก 3 คนoffsetLeftAndRight()กับอนิเมเตอร์

ในJellyBeanคุณสามารถเปิดใช้งาน "แสดงขอบเขตเลย์เอาต์" ในการตั้งค่า "ตัวเลือก Developper" เมื่อสไลด์แอนิเมชันเสร็จสมบูรณ์คุณจะยังเห็นว่าเมนูด้านซ้ายยังคงอยู่ที่นั่น แต่อยู่ใต้แผงตรงกลาง

มันคล้ายกับเมนูแอป Fly-in ของ Cyril Mottierแต่มี 3 องค์ประกอบแทน 2

นอกจากนี้ViewPagerลูกคนที่สามเป็นอีกตัวบ่งชี้ถึงพฤติกรรมนี้: ViewPagerมักจะใช้ Fragments (ฉันรู้ว่าพวกเขาไม่จำเป็นต้องทำ แต่ฉันไม่เคยเห็นการใช้งานแบบอื่นFragment) และเนื่องจากคุณไม่สามารถใช้FragmentsในFragmentเด็ก 3 คนอื่น อาจไม่ใช่ชิ้นส่วน ....


ฉันไม่เคยใช้ "แสดงขอบเขตของเลย์เอาต์" มาก่อน - มันมีประโยชน์มากสำหรับแอปแบบปิดแหล่งที่มาเช่นนี้ ดูเหมือนว่าสิ่งที่ฉันอธิบายว่า Fragment A กำลังแปลจากหน้าจอ (คุณสามารถดูสไลด์ขอบขวาจากขวาไปซ้าย) ก่อนที่จะผุดเข้าไปข้างใต้สิ่งที่ฉันอธิบายว่าเป็น Fragment B TranslateAnimationแม้ว่าฉันจะเป็นแบบนั้นก็ตาม แน่ใจว่ามีวิธีการใช้แอนิเมชั่นในการทำสิ่งเดียวกันให้สำเร็จ ฉันจะต้องทำการทดลองเกี่ยวกับเรื่องนี้ ขอบคุณ!
CommonsWare

2

ขณะนี้ฉันกำลังพยายามทำอะไรเช่นนั้นยกเว้นสเกลแฟรกเมนต์ B ที่จะใช้พื้นที่ว่างและบานหน้าต่างทั้ง 3 นั้นสามารถเปิดได้ในเวลาเดียวกันหากมีพื้นที่เพียงพอ นี่คือทางออกของฉันจนถึงตอนนี้ แต่ฉันไม่แน่ใจว่าฉันจะติดกับมัน ฉันหวังว่าบางคนจะให้คำตอบที่แสดงวิธีที่ถูกต้อง

แทนที่จะใช้ LinearLayout และทำให้น้ำหนักเคลื่อนไหวฉันใช้ RelativeLayout และทำให้ระยะขอบเคลื่อนไหว ฉันไม่แน่ใจว่าเป็นวิธีที่ดีที่สุดเพราะต้องมีการเรียกร้องให้ร้องขอ Layout () ในการอัปเดตแต่ละครั้ง มันราบรื่นกับทุกอุปกรณ์ของฉัน

ดังนั้นฉันทำให้โครงร่างเคลื่อนไหวฉันไม่ได้ใช้ธุรกรรมแฟรกเมนต์ ฉันจัดการปุ่มย้อนกลับด้วยตนเองเพื่อปิดแฟรกเมนต์ C หากเปิดอยู่

FragmentB ใช้ layout_toLeftOf / ToRightOf เพื่อจัดให้อยู่ในแนวเดียวกับแฟรกเมนต์ A และ C

เมื่อแอพของฉันเรียกใช้เหตุการณ์เพื่อแสดงแฟรกเมนต์ C ฉันจะเลื่อนแฟรกเมนต์ C และฉันสไลด์แฟรกเมนต์ A ในเวลาเดียวกัน (ภาพเคลื่อนไหวแยก 2) ในทางกลับกันเมื่อ Fragment A เปิดฉันปิด C ในเวลาเดียวกัน

ในโหมดแนวตั้งหรือบนหน้าจอที่เล็กกว่าฉันใช้เลย์เอาต์และเลื่อน Fragment C ที่แตกต่างออกไปเล็กน้อยบนหน้าจอ

ในการใช้เปอร์เซ็นต์สำหรับความกว้างของ Fragment 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;
}
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.