Làm cách nào để duy trì Chế độ nhập vai trong Hộp thoại?


104

Làm cách nào để duy trì Chế độ nhập vai mới khi các hoạt động của tôi hiển thị Hộp thoại tùy chỉnh?

Tôi đang sử dụng mã bên dưới để duy trì Chế độ nhập vai trong Hộp thoại, nhưng với giải pháp đó, NavBar xuất hiện trong chưa đầy một giây khi tôi khởi động Hộp thoại tùy chỉnh của mình, sau đó nó biến mất.

Video sau giải thích rõ hơn về sự cố (xem ở cuối màn hình khi NavBar xuất hiện): http://youtu.be/epnd5ghey8g

Làm cách nào để tránh hành vi này?

CODE

Cha đẻ của tất cả các hoạt động trong ứng dụng của tôi:

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

}


Tất cả các Hộp thoại tùy chỉnh của tôi đều gọi phương thức này trong onCreate(. . .)phương thức của chúng :

/**
 * 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()
        );
    }
}


CHỈNH SỬA - GIẢI PHÁP (nhờ Beaver6813, hãy xem câu trả lời của anh ấy để biết thêm chi tiết):

Tất cả các hộp thoại tùy chỉnh của tôi sẽ ghi đè phương thức hiển thị theo cách này:

/**
 * 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);
}

Làm thế nào để bạn hiển thị các hộp thoại? Bạn có sử dụng DialogFragment không?
Tadeas Kriz

Tôi không sử dụng DialogFragment nhưng các lớp con Dialog tùy chỉnh. developer.android.com/reference/android/app/Dialog.html Tôi hiển thị các hộp thoại chỉ bằng cách gọi phương thức show () của chúng.
VanDir

Khi hộp thoại xuất hiện onWindowFocusChanged đang được gọi. Giá trị của được bật khi hộp thoại xuất hiện là gì? Nó là đúng hay đã xảy ra sự cố và nó là sai?
Kevin van Mierlo

Ý của bạn là phương thức onWindowFocusChanged (boolean hasFocus) của lớp Dialog (chứ không phải của lớp Activity)? Trong trường hợp này, cờ "hasFocus" là true.
VanDir

5
Có ai đã sử dụng chế độ nhập vai với các đoạn hội thoại không?
gbero

Câu trả lời:


167

Sau rất nhiều nghiên cứu về vấn đề này, có một bản sửa lỗi đáng tiếc cho vấn đề này, liên quan đến việc chia nhỏ lớp Dialog để tìm. Thanh điều hướng được hiển thị khi cửa sổ hộp thoại được thêm vào Trình quản lý cửa sổ ngay cả khi bạn đặt chế độ hiển thị giao diện người dùng trước khi thêm nó vào trình quản lý. Trong ví dụ về Android Immersive, người ta nhận xét rằng:

// * 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.

Tôi tin rằng đó là những gì chúng ta đang thấy ở đây (tương tác với người dùng đang được kích hoạt khi chế độ xem cửa sổ mới, có thể tập trung, được thêm vào trình quản lý).

Làm thế nào chúng ta có thể giải quyết vấn đề này? Làm cho Hộp thoại không thể lấy tiêu điểm khi chúng tôi tạo nó (vì vậy chúng tôi không kích hoạt tương tác với người dùng) và sau đó làm cho nó có thể lấy tiêu điểm sau khi được hiển thị.

//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);

Rõ ràng đây không phải là lý tưởng nhưng nó có vẻ là một lỗi của Android, họ nên kiểm tra xem Window có thiết lập nhập vai hay không.

Tôi đã cập nhật mã thử nghiệm làm việc của mình (tha thứ cho sự lộn xộn của hack) lên Github . Tôi đã thử nghiệm trên trình giả lập Nexus 5, nó có thể sẽ nổ tung với bất kỳ thứ gì kém hơn KitKat nhưng chỉ dành cho bằng chứng về khái niệm.


3
Cảm ơn vì dự án mẫu nhưng nó không hoạt động. Hãy xem điều gì sẽ xảy ra trong Nexus 5 của tôi: youtu.be/-abj3V_0zcU
VanDir

Tôi đã cập nhật câu trả lời của mình sau một số cuộc điều tra về vấn đề này, đó là một câu trả lời khó! Đã thử nghiệm trên trình giả lập Nexus 5, hy vọng cái này hoạt động.
Beaver6813

2
Tôi nhận được 'requestFeature () phải được gọi trước khi thêm nội dung' (Tôi nghĩ rằng nó phụ thuộc vào các tính năng đang hoạt động trên hoạt động). Giải pháp: Di chuyển hộp thoại.show () lên một dòng để hiển thị () được gọi trước khi sao chép SystemUiVisibility (nhưng sau khi thiết lập không thể lấy tiêu điểm). Sau đó, nó vẫn hoạt động mà không cần nhảy thanh điều hướng.
arberg

5
@ Beaver6813 nó không hoạt động khi EditText được sử dụng và bàn phím mềm xuất hiện trong DialogFragment. Bạn có đề nghị này để xử lý nó.
androidcodehunter Tháng

1
Xin chào Beaver6813. Nó không hoạt động khi tôi có một văn bản chỉnh sửa trong hộp thoại. Có giải pháp nào không ??
Navin Gupta

33

Đối với thông tin của bạn, nhờ câu trả lời của @ Beaver6813, tôi đã có thể làm cho điều này hoạt động bằng DialogFragment.

trong phương thức onCreateView của DialogFragment của tôi, tôi vừa thêm phần sau:

    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
Điều này có hoạt động với mẫu Builder trong onCreateDialog () không? Tôi chưa được hack này để làm việc với DialogFragments tôi, hộp thoại bị treo ứng dụng của tôi với "requestFeature () phải được gọi trước khi thêm nội dung"
IAmKale

1
Đây là một giải pháp tuyệt vời và là giải pháp duy nhất để tôi sử dụng DialogFragment. Cảm ơn bạn rất nhiều.
se22as

Tuyệt quá! Nó hoạt động hoàn hảo. Đã thử rất nhiều thứ, bây giờ hoàn hảo tiếp tục chế độ nhập vai.
Peterdk

wow, làm việc như một sự quyến rũ. cảm ơn
Abdul

16

Nếu bạn muốn sử dụng onCreateDialog () , hãy thử lớp này. Nó hoạt động khá tốt đối với tôi ...

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

    }

}

Để hiển thị hộp thoại, hãy làm như sau trong hoạt động của bạn ...

new ImmersiveDialogFragment().showImmersive(this);

Bất kỳ ý tưởng nào về cách dừng NavBar được hiển thị khi menu ActionBar được hiển thị?
William

Tôi vẫn nhận được NPE, vì getDialog () trả về null. Làm thế nào mà bạn quản lý?
Anton Shkurenko

8

Kết hợp các câu trả lời ở đây, tôi đã tạo một lớp trừu tượng hoạt động trong mọi trường hợp:

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

kể từ khi androidx, phương thức setupDialog trở thành một api bị hạn chế, làm cách nào chúng ta có thể giải quyết điều đó ngoại trừ việc bỏ qua nó?
Mingkang Pan

5

Điều này cũng hoạt động trên phương thức onDismiss của đoạn hộp thoại của bạn. Và bên trong phương thức đó, hãy gọi phương thức của hoạt động mà nó được gắn vào một lần nữa thiết lập các cờ toàn màn hình.

@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();
        }
    }

Và trong hoạt động của bạn, hãy thêm phương pháp này:

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
Nhận xét hữu ích nhất, vì nó hoạt động tuyệt vời với AlertDialog.Builder.setOnDismissListener()
tomash

4

Khi bạn đang tạo DialogFragment của riêng mình, bạn chỉ cần ghi đè phương thức này.

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

Không thực sự lý tưởng vì bạn sẽ không thể nhấn bất kỳ thứ gì bên trong hộp thoại :(
slott

Sử dụng điều này, hộp thoại của tôi trở nên không phản hồi. Nhưng nếu bạn khẳng định rằng nó hoạt động, tôi sẽ thử một lần nữa.
slott

@slott bạn cần để xóa WindowManager.LayoutParams.FLAG_NOT_FOCUSABLEsau khi hộp thoại hiện
4emodan

2

Tôi biết đây là bài viết cũ, nhưng câu trả lời của tôi có thể giúp ích cho những người khác.

Dưới đây là bản sửa lỗi hack cho hiệu ứng Nhập vai trong Hộp thoại:

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