ฉันจะป้องกันไม่ให้แถบสถานะและแถบนำทางเคลื่อนไหวในระหว่างการเปลี่ยนภาพเคลื่อนไหวของฉากกิจกรรมได้อย่างไร


128

ประการแรกพื้นหลังแถบสถานะของฉันถูกตั้งค่าเป็นสีน้ำตาลเข้มและพื้นหลังแถบนำทางของฉันเป็นสีดำเริ่มต้น ฉันใช้ธีมแสงวัสดุ

ฉันกำลังเริ่มกิจกรรมใหม่โดยใช้ActivityOptions.makeSceneTransitionAnimationการเปลี่ยนค่าเริ่มต้นและฉันสังเกตเห็นว่าทั้งสถานะและแถบการนำทางจะจางเป็นสีขาวสั้น ๆ แล้วกลับไปเป็นสีที่ถูกต้อง

ตามเอกสาร :

เพื่อให้ได้ผลเต็มที่ของการเปลี่ยนแปลงคุณต้องเปิดใช้งานการเปลี่ยนเนื้อหาของหน้าต่างทั้งในกิจกรรมการโทรและที่เรียก มิฉะนั้นกิจกรรมการโทรจะเริ่มการเปลี่ยนการออก แต่จากนั้นคุณจะเห็นการเปลี่ยนหน้าต่าง (เช่นขนาดหรือจาง)

ฉันใช้getWindow().requestFeature(Window.FEATURE_CONTENT_TRANSITIONS);ทั้งกิจกรรมการโทรและการโทร

ในทำนองเดียวกันถ้าฉันเปลี่ยนการเปลี่ยน Enter เป็นสไลด์ทั้งสถานะและแถบนำทางชั่วครู่จะมีการเปลี่ยนสไลด์ที่มีพื้นหลังสีขาว

ฉันจะป้องกันไม่ให้แถบสถานะและแถบนำทางเคลื่อนไหวในระหว่างการเปลี่ยนภาพเคลื่อนไหวของฉากกิจกรรมได้อย่างไร

คำตอบ:


218

มีสองวิธีที่คุณสามารถใช้ที่ฉันทราบเพื่อป้องกันไม่ให้แถบการนำทาง / สถานะเคลื่อนไหวในระหว่างการเปลี่ยนแปลง:

แนวทาง # 1: ไม่รวมแถบสถานะและแถบนำทางจากทางออกเริ่มต้นของหน้าต่าง / เข้าสู่การเปลี่ยนเลือน

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

Transition fade = new Fade();
fade.excludeTarget(android.R.id.statusBarBackground, true);
fade.excludeTarget(android.R.id.navigationBarBackground, true);
getWindow().setExitTransition(fade);
getWindow().setEnterTransition(fade);

การเปลี่ยนแปลงนี้สามารถประกาศได้ในธีมของกิจกรรมโดยใช้ XML (เช่นในres/transition/window_fade.xmlไฟล์ของคุณเอง):

<?xml version="1.0" encoding="utf-8"?>
<fade xmlns:android="http://schemas.android.com/apk/res/android">
    <targets>
        <target android:excludeId="@android:id/statusBarBackground"/>
        <target android:excludeId="@android:id/navigationBarBackground"/>
    </targets>
</fade>

แนวทาง # 2: เพิ่มแถบสถานะและแถบนำทางเป็นองค์ประกอบที่ใช้ร่วมกัน

วิธีนี้สร้างขึ้นจากคำตอบของ klmprt ซึ่งเกือบจะได้ผลสำหรับฉัน ... แม้ว่าฉันจะยังต้องทำการปรับเปลี่ยนสองสามอย่าง

ในกิจกรรมการโทรของฉันฉันใช้รหัสต่อไปนี้เพื่อเริ่มกิจกรรม:

View statusBar = findViewById(android.R.id.statusBarBackground);
View navigationBar = findViewById(android.R.id.navigationBarBackground);

List<Pair<View, String>> pairs = new ArrayList<>();
if (statusBar != null) {
  pairs.add(Pair.create(statusBar, Window.STATUS_BAR_BACKGROUND_TRANSITION_NAME));
}
if (navigationBar != null) {
  pairs.add(Pair.create(navigationBar, Window.NAVIGATION_BAR_BACKGROUND_TRANSITION_NAME));
}
pairs.add(Pair.create(mSharedElement, mSharedElement.getTransitionName()));

Bundle options = ActivityOptions.makeSceneTransitionAnimation(activity, 
        pairs.toArray(new Pair[pairs.size()])).toBundle();
startActivity(new Intent(context, NextActivity.class), options);

จนถึงตอนนี้นี่ก็เหมือนกับที่ klmprt แนะนำในคำตอบของเขา อย่างไรก็ตามฉันจำเป็นต้องเพิ่มรหัสต่อไปนี้ในonCreate()วิธีการของกิจกรรมที่เรียกว่าของฉันเพื่อป้องกันไม่ให้แถบสถานะและแถบนำทาง "กะพริบ" ในระหว่างการเปลี่ยนแปลง:

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_next);

    // Postpone the transition until the window's decor view has
    // finished its layout.
    postponeEnterTransition();

    final View decor = getWindow().getDecorView();
    decor.getViewTreeObserver().addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() {
        @Override
        public boolean onPreDraw() {
            decor.getViewTreeObserver().removeOnPreDrawListener(this);
            startPostponedEnterTransition();
            return true;
        }
    });
}

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


14
ความคิดใด ๆ ที่ทำให้ findViewById (android.R.id.navigationBarBackground) คืนค่า null บน Lollipop? ฉันใช้ appcompat-v7
radzio

3
แนวทาง # 2 ทำงานได้ แต่ยังคงมีการสั่นไหว (ในกิจกรรม) เมื่อการเปลี่ยนแปลงเกิดขึ้น แถบสถานะและแถบนำทางไม่กะพริบอีกต่อไป
Akshat

16
@alex สิ่งนี้ทำงานได้เกือบสมบูรณ์สำหรับฉันจนกระทั่งเราเปลี่ยนไปใช้ไลบรารีการสนับสนุนใหม่ด้วยandroid.support.design.widget.TabLayoutและandroid.support.design.widget.AppBarLayout - ตอนนี้การกะพริบกลับมาแล้ว
Noa Drach

6
android.R.id.navigationBarBackground สามารถให้ NPE บนอุปกรณ์ Samsung หรือ HTC เนื่องจากไม่มีแถบนำทางบนหน้าจอ ตรวจสอบค่าว่างก่อนเพิ่มเป็นองค์ประกอบที่ใช้ร่วมกัน
บอย

8
ฉันกำลังลองใช้วิธีแก้ปัญหานี้บน Nexus 6 กับ android nougat แต่ทั้งสองวิธีไม่ได้ผลสำหรับฉัน
Pardeep Kr

4

ป้องกันการเปลี่ยนกิจกรรมโดยสิ้นเชิงจากการรบกวนการเปลี่ยนองค์ประกอบที่แบ่งใช้:

ในกิจกรรมการออกให้เรียก getWindow (). setExitTransition (null);

ในกิจกรรมการป้อนเรียก getWindow (). setEnterTransition (null);

จากhttps://stackoverflow.com/a/34907685/967131

ฉันสงสัยว่านี่อาจมีผลข้างเคียง แต่ไม่รู้แน่ชัด มันตายง่ายและใช้ได้ผล

ป้องกันไม่ให้องค์ประกอบบางอย่างกะพริบ:

ฉันเริ่มต้นด้วยคำตอบของ Alex Lockwoodและทำการทดลองเล็กน้อยเพื่อพยายามทำให้มันได้ผล หลักของมันถูกต้องแม้ว่าฉันไม่ต้องการรหัสที่เขาแนะนำสำหรับกิจกรรมการรับ แต่ฉันพบปัญหาบางอย่างโดยเรียกมันใน Fragment (แทนที่จะเป็นกิจกรรม) และโดยการตั้งค่าแถบเครื่องมือเป็นแถบการดำเนินการ

โอ้เศษเสี้ยว? ฉันเห็นความคิดเห็นมากมายที่พยายามดึงข้อมูลอ้างอิงไปยังแถบสถานะและแถบนำทางเป็นโมฆะ สิ่งเดียวกันก็เกิดขึ้นกับฉันเช่นกันจนกระทั่งฉันตระหนักว่าฉันจะไม่พบสิ่งเหล่านั้นในเค้าโครงของ Fragment ... พวกมันอยู่เหนือระดับนั้น ดังนั้นรหัสด้านล่างเพื่อรับมุมมองการตกแต่งจากกิจกรรมและค้นหาสิ่งนั้น จากนั้นฉันก็พบว่าพวกเขาไม่มีปัญหา

ในท้ายที่สุดฉันได้พัฒนาวิธียูทิลิตี้นี้:

public static Bundle transitionOptions(Activity activity, int transitionViewResId, int transitionNameResId) {
   if (VERSION.SDK_INT < VERSION_CODES.LOLLIPOP) {
       return null;
   }

   View decorView = activity.getWindow().getDecorView();
   View statusBar = decorView.findViewById(android.R.id.statusBarBackground);
   View navigationBar = decorView.findViewById(android.R.id.navigationBarBackground);
   View appBarLayout = decorView.findViewById(**R.id.appbarlayout**);
   View transitionView = decorView.findViewById(transitionViewResId);
   String transitionName = activity.getString(transitionNameResId);

   List<Pair<View, String>> pairs = new ArrayList<>();
   pairs.add(Pair.create(statusBar, Window.STATUS_BAR_BACKGROUND_TRANSITION_NAME));
   pairs.add(Pair.create(navigationBar, Window.NAVIGATION_BAR_BACKGROUND_TRANSITION_NAME));
   if (appBarLayout != null) {
       pairs.add(Pair.create(appBarLayout, activity.getString(**R.string.transition_appbarlayout**)));
   }
   pairs.add(Pair.create(transitionView, transitionName));
   //noinspection unchecked - we're not worried about the "unchecked" conversion of List<Pair> to Pair[] here
   return ActivityOptionsCompat.makeSceneTransitionAnimation(activity, pairs.toArray(new Pair[pairs.size()]))
           .toBundle();
}

หมายเหตุR.string.transition_appbarlayoutและR.id.appbarlayout รหัสเหล่านี้เป็นรหัสโดยพลการตราบใดที่ตรงกับสิ่งที่รหัสของคุณใช้ ใน XML ของฉันฉันจัดวางแถบการดำเนินการที่กำหนดเองดังนี้ (แก้ไขลงไปที่ข้อมูลสำคัญ):

<?xml version="1.0" encoding="utf-8"?>
<android.support.design.widget.AppBarLayout
    android:id="**@+id/appbarlayout**"
    android:transitionName="**@string/transition_appbarlayout**">

    <android.support.v7.widget.Toolbar
        android:id="@+id/toolbar"/>
</android.support.design.widget.AppBarLayout>

หากคุณไม่ใช้ Toolbar เช่นนี้ส่วนนั้นสามารถลบออกจากวิธียูทิลิตี้ได้

จากนั้นคุณจะเรียกมันใน Fragment ของคุณดังนี้:

startActivity(intent, UIUtils.transitionOptions(getActivity(),
                        R.id.**my_view**,
                        R.string.**transition_my_view**));

ใช้ค่าใดก็ได้ที่คุณต้องการตราบเท่าที่ตรงกับ XML ของคุณ

ซึ่งจะป้องกันไม่ให้แถบสถานะแถบเครื่องมือและแถบนำทาง (ปุ่มย้อนกลับ / บ้าน / แอปล่าสุด) กะพริบระหว่างการเปลี่ยน ส่วนที่เหลือของการเปลี่ยนแปลงกิจกรรมเป็นเรื่องปกติ

ในกรณีของฉันธีมแอพของเรามีandroid:windowBackgroundสีน้ำเงิน สิ่งนี้ทำให้เกิดแฟลชสีน้ำเงินในช่วงการเปลี่ยนภาพซึ่งค่อนข้างน่าผิดหวัง แต่แทนที่จะทำการเปลี่ยนแปลงที่ส่งผลกระทบต่อแอปทั้งหมดในตอนนี้ฉันกำลังใช้ตัวเลือกแรกที่รวดเร็วและสกปรก


3

คุณต้องแบ่งปันใน ActivityOptions.makeSceneTransitionAnimation.

เช่น:

ActivityOptions.makeSceneTransitionAnimation(... Pair.create(activity.findViewById(android.R.id.window_status_bar), Window.STATUS_BAR_BACKGROUND_TRANSITION_NAME) 

(ขออภัย psuedo ฉันไม่มีค่า android.R.id ที่แน่นอนในมือ)

คุณสามารถเรียกใช้การเปลี่ยนแปลงที่เหมาะสมหลังจากแบ่งปันมุมมองแล้ว


สิ่งนี้ได้ผลสำหรับคุณหรือไม่? ฉันลองแล้วแถบสถานะและแถบนำทางยังคงกะพริบเป็นสีขาวระหว่างการเปลี่ยน
rlay3

ใช่มันได้ผลสำหรับฉัน แน่ใจไหมว่าไม่มีมุมมองอื่นที่อาจซ้อนทับกันชั่วคราว คุณได้ลองตั้งค่าการเปลี่ยนกิจกรรมง่ายๆในสภาพแวดล้อมแบบ 'แซนด์บ็อกซ์' เพื่อแยกปัญหาหรือไม่
klmprt

@klmprt ฉันคิดว่าคุณต้องเลื่อนการเปลี่ยนเข้าสู่ระบบเพื่อให้มันใช้งานได้ ... ฉันยังไม่สามารถป้องกันไม่ให้แถบสถานะ / การนำทางเคลื่อนไหวได้เพียงแค่แบ่งปันมุมมอง ฉันเดาว่าคุณต้องรอให้มุมมองการตกแต่งของหน้าต่างเสร็จสิ้นการจัดวางก่อนที่คุณจะปล่อยให้การเปลี่ยนแปลงเข้าสู่เริ่มต้น
Alex Lockwood

@klmprt สิ่งที่คำตอบของฉันยังไม่ได้กล่าวถึงคือวิธีการป้องกันไม่ให้สีพื้นหลังของ Action Bar เคลื่อนไหวในระหว่างการเปลี่ยนแปลง หากทั้งสองกิจกรรมมีสีพื้นหลังของแถบการดำเนินการร่วมกันสีพื้นหลังของแถบการกระทำจะปรากฏเป็นภาพเคลื่อนไหวเมื่อแถบการกระทำของกิจกรรมการโทรค่อยๆจางหายไปและแถบการกระทำของกิจกรรมที่เรียกว่าค่อยๆจางหายไปคุณมีความคิดที่จะหลีกเลี่ยงสิ่งนี้หรือไม่ ปัญหา?
Alex Lockwood

@klmprt อันที่จริงฉันคิดว่ามีทางออกที่ดีกว่านี้ คุณสามารถยกเว้นพื้นหลังของแถบการนำทาง / สถานะเป็นเป้าหมายในการออกจากหน้าต่างเริ่มต้น / เข้าสู่การเปลี่ยนแบบเลือน ดูคำตอบที่อัปเดตของฉันสำหรับรายละเอียด
Alex Lockwood

3

เท่าที่ฉันเข้าใจสิ่งนี้เกิดจากการทับซ้อนของการเปลี่ยนแปลงกิจกรรม เพื่อเอาชนะปัญหานี้ฉันได้ใช้ค่าต่อไปนี้ในonCreate()วิธีการของทั้งสองกิจกรรม:

getWindow().setAllowEnterTransitionOverlap(false);
getWindow().setAllowReturnTransitionOverlap(false);

3

ฉันเพิ่งมีปัญหาเดียวกันนี้และคำตอบดูเหมือนจะไม่มีส่วนสำคัญของปริศนา โปรดจำไว้ว่าในการเปลี่ยนแปลงองค์ประกอบที่ใช้ร่วมกันทุกอย่างที่เกิดขึ้นในกิจกรรมปลายทาง

ในการลบเอฟเฟกต์การกะพริบให้เพิ่มสิ่งต่อไปนี้ในกิจกรรมที่ถูกเรียก:

Fade fade = new Fade();
fade.excludeTarget(android.R.id.statusBarBackground, true);
fade.excludeTarget(android.R.id.navigationBarBackground, true);

getWindow().setEnterTransition(fade);
getWindow().setExitTransition(fade);

สิ่งนี้จะช่วยแก้ปัญหาของคุณได้!


สวัสดี นี่ไม่เหมือนกับ Approach # 1 ในคำตอบอันดับต้น ๆ ใช่หรือไม่?
rlay3

@ rlay3 ไม่ใช่ทั้งหมด เท่าที่ฉันสามารถบอกได้เขาไม่เคยพูดถึงความจริงที่ว่าความต้องการนี้ถูกกำหนดไว้ในกิจกรรมปลายทางเท่านั้น
LukeWaggoner

เฮ้ @LukeWaggoner คุณช่วยฉันได้ไหม: stackoverflow.com/questions/50189286/…
blackHawk

คุณต้องยกเว้นแถบสถานะและแถบนำทางในทั้งสองกิจกรรมผู้โทรและผู้เรียก
Giovanni Di Gregorio


0

นี่คือวิธีที่ฉันทำ ฉันแบ่งปันทั้งStatus BarและNavigation BarในSharedElementTransitionพร้อมกับImageView:

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
  View imageView = view.findViewById(R.id.iv);
  Resources resources = view.getResources();
  imageView.setTransitionName(resources.getString(R.string.transition_image_thumbnail));

  Pair<View, String> p1 = Pair.create(imageView, resources.getString(R.string.transition_image_thumbnail));

  Window window = getActivity().getWindow();

  View navigationBar = getActivity().findViewById(android.R.id.navigationBarBackground);
  View statusBar = getActivity().findViewById(android.R.id.statusBarBackground);

  Pair<View, String> p2 = Pair.create(statusBar, statusBar.getTransitionName());
  Pair<View, String> p3 = Pair.create(navigationBar, navigationBar.getTransitionName());

  ActivityOptionsCompat options = ActivityOptionsCompat.makeSceneTransitionAnimation(getActivity(),
          p1, p2, p3);

  ActivityCompat.startActivity(getActivity(), intent, options.toBundle());
} else {
  startActivity(intent);
}
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.