ฉันจะรักษาโหมดใหญ่พิเศษในกล่องโต้ตอบได้อย่างไร


104

ฉันจะรักษาโหมดใหญ่พิเศษใหม่ได้อย่างไรเมื่อกิจกรรมของฉันแสดงกล่องโต้ตอบที่กำหนดเอง

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

วิดีโอต่อไปนี้อธิบายปัญหาได้ดีขึ้น (ดูที่ด้านล่างของหน้าจอเมื่อ NavBar ปรากฏขึ้น): http://youtu.be/epnd5ghey8g

ฉันจะหลีกเลี่ยงพฤติกรรมนี้ได้อย่างไร?

รหัส

พ่อของกิจกรรมทั้งหมดในใบสมัครของฉัน:

public abstract class ImmersiveActivity extends Activity {

    @SuppressLint("NewApi")
    private void disableImmersiveMode() {
        if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.KITKAT) {
            getWindow().getDecorView().setSystemUiVisibility(
                    View.SYSTEM_UI_FLAG_FULLSCREEN
            );
        }
    }

    @SuppressLint("NewApi")
    private void enableImmersiveMode() {
        if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.KITKAT) {
            getWindow().getDecorView().setSystemUiVisibility(
                      View.SYSTEM_UI_FLAG_LAYOUT_STABLE 
                    | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION 
                    | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN 
                    | View.SYSTEM_UI_FLAG_FULLSCREEN 
                    | View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY 
                    | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION
            );
        }
    }


    /**
     * Set the Immersive mode or not according to its state in the settings:
     * enabled or not.
     */
    protected void updateSystemUiVisibility() {
        // Retrieve if the Immersive mode is enabled or not.
        boolean enabled = getSharedPreferences(Util.PREF_NAME, 0).getBoolean(
                "immersive_mode_enabled", true);

        if (enabled) enableImmersiveMode();
        else disableImmersiveMode();
    }

    @Override
    public void onResume() {
        super.onResume();
        updateSystemUiVisibility();
    }

    @Override
    public void onWindowFocusChanged(boolean hasFocus) {
        super.onWindowFocusChanged(hasFocus);
        updateSystemUiVisibility();
    }

}


กล่องโต้ตอบที่กำหนดเองทั้งหมดของฉันเรียกวิธีนี้ว่าonCreate(. . .)เมธอด:

/**
 * Copy the visibility of the Activity that has started the dialog {@link mActivity}. If the
 * activity is in Immersive mode the dialog will be in Immersive mode too and vice versa.
 */
@SuppressLint("NewApi")
private void copySystemUiVisibility() {
    if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.KITKAT) {
        getWindow().getDecorView().setSystemUiVisibility(
                mActivity.getWindow().getDecorView().getSystemUiVisibility()
        );
    }
}


แก้ไข - การแก้ปัญหา (ขอบคุณ Beaver6813 ดูคำตอบของเขาสำหรับรายละเอียดเพิ่มเติม):

กล่องโต้ตอบที่กำหนดเองทั้งหมดของฉันจะแทนที่วิธีการแสดงด้วยวิธีนี้:

/**
 * An hack used to show the dialogs in Immersive Mode (that is with the NavBar hidden). To
 * obtain this, the method makes the dialog not focusable before showing it, change the UI
 * visibility of the window like the owner activity of the dialog and then (after showing it)
 * makes the dialog focusable again.
 */
@Override
public void show() {
    // Set the dialog to not focusable.
    getWindow().setFlags(WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE,
            WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE);

    copySystemUiVisibility();

    // Show the dialog with NavBar hidden.
    super.show();

    // Set the dialog to focusable again.
    getWindow().clearFlags(WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE);
}

คุณจะแสดงกล่องโต้ตอบอย่างไร? คุณใช้ DialogFragments หรือไม่
Tadeas Kriz

ฉันไม่ได้ใช้ DialogFragments แต่เป็นคลาสย่อยของ Dialog ที่กำหนดเอง developer.android.com/reference/android/app/Dialog.htmlฉันแสดงกล่องโต้ตอบเพียงแค่เรียกวิธีการ show ()
VanDir

เมื่อกล่องโต้ตอบปรากฏขึ้นบน WindowsFocusChanged จะถูกเรียก ค่าของการเปิดใช้งานเมื่อกล่องโต้ตอบปรากฏขึ้นคืออะไร? เป็นเรื่องจริงหรือมีบางอย่างผิดพลาดและเป็นเท็จ?
Kevin van Mierlo

คุณหมายถึงเมธอด onWindowFocusChanged (boolean hasFocus) ของคลาส Dialog (ไม่ใช่ของคลาส Activity) ใช่หรือไม่ ในกรณีนี้ค่าสถานะ "hasFocus" จะเป็นจริง
VanDir

5
มีใครใช้โหมดใหญ่พิเศษกับไดอะล็อกแฟรกเมนต์บ้างไหม
gbero

คำตอบ:


167

หลังจากการวิจัยจำนวนมากเกี่ยวกับปัญหานี้มีการแก้ไขแฮ็กสำหรับสิ่งนี้ซึ่งเกี่ยวข้องกับการแยกคลาสDialogออกจากกันเพื่อค้นหา แถบการนำทางจะแสดงเมื่อมีการเพิ่มหน้าต่างโต้ตอบลงใน Window Manager แม้ว่าคุณจะตั้งค่าการมองเห็น UI ก่อนที่จะเพิ่มไปยังผู้จัดการ ในตัวอย่าง Android Immersiveมีความเห็นว่า:

// * Uses semi-transparent bars for the nav and status bars
// * This UI flag will *not* be cleared when the user interacts with the UI.
// When the user swipes, the bars will temporarily appear for a few seconds and then
// disappear again.

ฉันเชื่อว่านั่นคือสิ่งที่เราเห็นที่นี่ (ว่าการโต้ตอบกับผู้ใช้กำลังถูกทริกเกอร์เมื่อมีการเพิ่มมุมมองหน้าต่างใหม่ที่โฟกัสได้ให้กับผู้จัดการ)

เราจะแก้ไขปัญหานี้ได้อย่างไร? ทำให้กล่องโต้ตอบไม่สามารถโฟกัสได้เมื่อเราสร้างขึ้น (ดังนั้นเราจึงไม่เรียกใช้การโต้ตอบกับผู้ใช้) จากนั้นทำให้สามารถโฟกัสได้หลังจากที่แสดง

//Here's the magic..
//Set the dialog to not focusable (makes navigation ignore us adding the window)
dialog.getWindow().setFlags(WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE, WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE);

//Show the dialog!
dialog.show();

//Set the dialog to immersive
dialog.getWindow().getDecorView().setSystemUiVisibility(
context.getWindow().getDecorView().getSystemUiVisibility());

//Clear the not focusable flag from the window
dialog.getWindow().clearFlags(WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE);

เห็นได้ชัดว่านี่ไม่เหมาะ แต่ดูเหมือนว่าจะเป็นจุดบกพร่องของ Android พวกเขาควรตรวจสอบว่า Window มีการตั้งค่าที่สมจริงหรือไม่

ผมได้ปรับปรุงรหัสการทดสอบการทำงานของฉัน (ให้อภัยสกปรก hacky) เพื่อGithub ฉันได้ทดสอบตัวจำลอง Nexus 5 แล้วมันอาจจะระเบิดด้วยอะไรก็ได้ที่น้อยกว่า KitKat แต่สำหรับการพิสูจน์แนวคิดเท่านั้น


3
ขอบคุณสำหรับโครงการตัวอย่าง แต่ไม่ได้ผล ดูว่าเกิดอะไรขึ้นใน Nexus 5 ของฉัน: youtu.be/-abj3V_0zcU
VanDir

ฉันได้อัปเดตคำตอบของฉันหลังจากการตรวจสอบปัญหาแล้วมันเป็นปัญหาที่ยาก! ทดสอบกับเครื่องจำลอง Nexus 5 หวังว่าจะใช้งานได้
Beaver6813

2
ฉันได้รับ 'requestFeature () ต้องถูกเรียกก่อนเพิ่มเนื้อหา' (ฉันคิดว่ามันขึ้นอยู่กับคุณสมบัติที่ใช้งานในกิจกรรม) วิธีแก้ไข: เลื่อน dialog.show () ขึ้นหนึ่งบรรทัดเพื่อให้ show () ถูกเรียกก่อนคัดลอก SystemUiVisibility (แต่หลังจากตั้งค่าไม่สามารถโฟกัสได้) จากนั้นก็ยังใช้งานได้โดยไม่ต้องกระโดดแถบนำทาง
arberg

5
@ Beaver6813 ไม่ทำงานเมื่อใช้ EditText และซอฟต์คีย์บอร์ดปรากฏใน DialogFragment คุณมีข้อเสนอแนะในการจัดการหรือไม่
androidcodehunter

1
สวัสดีบีเวอร์ 6813. ไม่ทำงานเมื่อฉันมีข้อความแก้ไขในกล่องโต้ตอบ มีวิธีแก้ไหม ??
Navin Gupta

33

สำหรับข้อมูลของคุณขอบคุณคำตอบของ @ Beaver6813 ฉันสามารถทำงานนี้ได้โดยใช้ DialogFragment

ในเมธอด onCreateView ของ DialogFragment ของฉันฉันเพิ่งเพิ่มสิ่งต่อไปนี้:

    getDialog().getWindow().setFlags(WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE, WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE);
    getDialog().getWindow().getDecorView().setSystemUiVisibility(getActivity().getWindow().getDecorView().getSystemUiVisibility());

    getDialog().setOnShowListener(new DialogInterface.OnShowListener() {
        @Override
        public void onShow(DialogInterface dialog) {
            //Clear the not focusable flag from the window
            getDialog().getWindow().clearFlags(WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE);

            //Update the WindowManager with the new attributes (no nicer way I know of to do this)..
            WindowManager wm = (WindowManager) getActivity().getSystemService(Context.WINDOW_SERVICE);
            wm.updateViewLayout(getDialog().getWindow().getDecorView(), getDialog().getWindow().getAttributes());
        }
    });

5
สิ่งนี้ใช้ได้กับรูปแบบ Builder ใน onCreateDialog () หรือไม่ ฉันยังไม่ได้ให้แฮ็คนี้ทำงานกับ DialogFragments ของฉันกล่องโต้ตอบทำให้แอปของฉันขัดข้องโดยต้องเรียก "requestFeature () ก่อนเพิ่มเนื้อหา"
IAmKale

1
นี่เป็นวิธีที่ยอดเยี่ยมและเป็นทางออกเดียวสำหรับการใช้งาน DialogFragments ของฉัน ขอบคุณมาก.
se22as

เยี่ยมมาก! มันทำงานได้อย่างสมบูรณ์ พยายามหลายสิ่งหลายอย่างตอนนี้โหมดดื่มด่ำสมบูรณ์แบบยังคงดำเนินต่อไป
Peterdk

16

หากคุณต้องการใช้onCreateDialog ()ให้ลองใช้คลาสนี้ มันใช้ได้ดีสำหรับฉัน ...

public class ImmersiveDialogFragment extends DialogFragment {

    @Override
    public Dialog onCreateDialog(Bundle savedInstanceState) {

        AlertDialog alertDialog = new AlertDialog.Builder(getActivity())
                .setTitle("Example Dialog")
                .setMessage("Some text.")
                .create();

        // Temporarily set the dialogs window to not focusable to prevent the short
        // popup of the navigation bar.
        alertDialog.getWindow().addFlags(WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE);

        return alertDialog;

    }

    public void showImmersive(Activity activity) {

        // Show the dialog.
        show(activity.getFragmentManager(), null);

        // It is necessary to call executePendingTransactions() on the FragmentManager
        // before hiding the navigation bar, because otherwise getWindow() would raise a
        // NullPointerException since the window was not yet created.
        getFragmentManager().executePendingTransactions();

        // Hide the navigation bar. It is important to do this after show() was called.
        // If we would do this in onCreateDialog(), we would get a requestFeature()
        // error.
        getDialog().getWindow().getDecorView().setSystemUiVisibility(
            getActivity().getWindow().getDecorView().getSystemUiVisibility()
        );

        // Make the dialogs window focusable again.
        getDialog().getWindow().clearFlags(
            WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
        );

    }

}

หากต้องการแสดงกล่องโต้ตอบให้ทำดังต่อไปนี้ในกิจกรรมของคุณ ...

new ImmersiveDialogFragment().showImmersive(this);

มีความคิดเกี่ยวกับวิธีหยุด NavBar ที่แสดงเมื่อเมนู ActionBar ปรากฏขึ้นหรือไม่?
William

ฉันยังคงได้รับ NPE เนื่องจาก getDialog () คืนค่า null คุณจัดการอย่างไร
Anton Shkurenko

8

การรวมคำตอบที่นี่ฉันได้สร้างคลาสนามธรรมที่ใช้ได้ในทุกกรณี:

public abstract class ImmersiveDialogFragment extends DialogFragment {

    @Override
    public void setupDialog(Dialog dialog, int style) {
        super.setupDialog(dialog, style);

        // Make the dialog non-focusable before showing it
        dialog.getWindow().setFlags(
                WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE,
                WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE);
    }

    @Override
    public void show(FragmentManager manager, String tag) {
        super.show(manager, tag);
        showImmersive(manager);
    }

    @Override
    public int show(FragmentTransaction transaction, String tag) {
        int result = super.show(transaction, tag);
        showImmersive(getFragmentManager());
        return result;
    }

    private void showImmersive(FragmentManager manager) {
        // It is necessary to call executePendingTransactions() on the FragmentManager
        // before hiding the navigation bar, because otherwise getWindow() would raise a
        // NullPointerException since the window was not yet created.
        manager.executePendingTransactions();

        // Copy flags from the activity, assuming it's fullscreen.
        // It is important to do this after show() was called. If we would do this in onCreateDialog(),
        // we would get a requestFeature() error.
        getDialog().getWindow().getDecorView().setSystemUiVisibility(
                getActivity().getWindow().getDecorView().getSystemUiVisibility()
        );

        // Make the dialogs window focusable again
        getDialog().getWindow().clearFlags(
                WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
        );
    }
}

ตั้งแต่ androidx วิธีการsetupDialogกลายเป็น API ที่ถูก จำกัด เราจะจัดการกับสิ่งนั้นได้อย่างไรยกเว้นไม่สนใจมัน
มิ่งขวัญปาน

5

นอกจากนี้ยังใช้ได้กับการขี่เมธอด onDismiss ของส่วนโต้ตอบของคุณ และภายในวิธีการนั้นจะเรียกวิธีการของกิจกรรมที่แนบมาอีกครั้งตั้งค่าสถานะเต็มหน้าจอ

@Override
    public void onDismiss(DialogInterface dialog) {
        super.onDismiss(dialog);
        Logger.e(TAG, "onDismiss");
        Log.e("CallBack", "CallBack");
        if (getActivity() != null &&
                getActivity() instanceof LiveStreamingActivity) {
            ((YourActivity) getActivity()).hideSystemUI();
        }
    }

และในกิจกรรมของคุณให้เพิ่มวิธีนี้:

public void hideSystemUI() {
        // Set the IMMERSIVE flag.
        // Set the content to appear under the system bars so that the content
        // doesn't resize when the system bars hide and show.
        getWindow().getDecorView().setSystemUiVisibility(
                View.SYSTEM_UI_FLAG_LAYOUT_STABLE
                        | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
                        | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
                        | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION // hide nav bar
                        | View.SYSTEM_UI_FLAG_FULLSCREEN // hide status bar
                        | View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY);
    }

1
ความคิดเห็นที่มีประโยชน์มากที่สุดเช่นการทำงานที่ดีด้วยAlertDialog.Builderและ.setOnDismissListener()
Tomash

4

ในขณะที่คุณกำลังสร้าง DialogFragment ของคุณเองคุณต้องการเพียงแค่แทนที่วิธีนี้

@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
    Dialog dialog = super.onCreateDialog(savedInstanceState);

    dialog.getWindow().setFlags(WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE,
            WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE);

    return dialog;
}

ไม่เหมาะจริงๆเพราะคุณจะไม่สามารถกดอะไรก็ได้ในกล่องโต้ตอบ :(
slott

การใช้สิ่งนี้กล่องโต้ตอบของฉันไม่ตอบสนอง แต่ถ้าคุณยืนยันว่ามันใช้งานได้ฉันจะให้อีกครั้ง
slott

@slott คุณต้องล้างWindowManager.LayoutParams.FLAG_NOT_FOCUSABLEกล่องโต้ตอบ after ที่แสดง
4emodan

2

ฉันรู้ว่านี่เป็นโพสต์เก่า แต่คำตอบของฉันอาจช่วยคนอื่นได้

ด้านล่างนี้คือการแก้ไขแฮ็กสำหรับเอฟเฟกต์ที่สมจริงในกล่องโต้ตอบ:

public static void showImmersiveDialog(final Dialog mDialog, final Activity mActivity) {
        //Set the dialog to not focusable
        mDialog.getWindow().setFlags(WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE, WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE);
        mDialog.getWindow().getDecorView().setSystemUiVisibility(setSystemUiVisibility());

        mDialog.setOnShowListener(new DialogInterface.OnShowListener() {
            @Override
            public void onShow(DialogInterface dialog) {
                //Clear the not focusable flag from the window
                mDialog.getWindow().clearFlags(WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE);

                //Update the WindowManager with the new attributes
                WindowManager wm = (WindowManager) mActivity.getSystemService(Context.WINDOW_SERVICE);
                wm.updateViewLayout(mDialog.getWindow().getDecorView(), mDialog.getWindow().getAttributes());
            }
        });

        mDialog.getWindow().getDecorView().setOnSystemUiVisibilityChangeListener(new View.OnSystemUiVisibilityChangeListener() {
            @Override
            public void onSystemUiVisibilityChange(int visibility) {
                if ((visibility & View.SYSTEM_UI_FLAG_FULLSCREEN) == 0) {
                    mDialog.getWindow().getDecorView().setSystemUiVisibility(setSystemUiVisibility());
                }

            }
        });
    }

    public static int setSystemUiVisibility() {
        return View.SYSTEM_UI_FLAG_LAYOUT_STABLE
                | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
                | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
                | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION
                | View.SYSTEM_UI_FLAG_FULLSCREEN
                | View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY;
    }
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.