Розмиття або затемнення фону, коли активне вікно Android PopupWindow


85

Я хотів би мати можливість або розмити, або затемнити фон, коли я показую своє спливаюче вікно, використовуючи popup.showAtLocation, і розмити / затемнити фон при виклику popup.dismiss.

Я спробував застосувати параметри макета FLAG_BLUR_BEHINDта FLAG_DIM_BEHINDсвою діяльність, але, здається, це просто розмиває та затемнює фон, як тільки додаток запускається.

Як я можу зробити розмиття / затемнення лише за допомогою спливаючих вікон?


1
Слово попередження: FLAG_BLUR_BEHIND не працює на деяких телефонах і робить телефон дуже нечутливим (очевидно, розмиття відбувається в програмному забезпеченні). Наприклад, на Droid. Крім цього, те, що сказав Макарсе - FLAG_BLUR_BEHIND потрібно застосувати до вікна на передньому плані.
EboMike


1
Я шукаю ту ж відповідь на це питання. Чи можу я якимось чином програмно затемнити вигляд, який знаходиться за спливаючим вікном?
Нік

1
@Nick Як ти вирішив цю проблему ..
Amit

Відповіді:


106

Питання стосувалось Popupwindowкласу, але всі давали відповіді, які використовують Dialogклас. Це майже марно, якщо вам потрібно використовувати Popupwindowклас, оскільки Popupwindowне має getWindow()методу.

Я знайшов рішення, яке насправді працює Popupwindow. Потрібно лише, щоб кореневим файлом xml, який ви використовуєте для фонової діяльності, було FrameLayout. Ви можете дати Framelayoutелементу android:foregroundтег. Що робить цей тег, це вказати ресурс, який можна малювати, який буде накладений поверх усієї діяльності (тобто, якщо Framelayout є кореневим елементом у файлі xml). Потім ви можете контролювати непрозорість ( setAlpha()) переднього плану, що малюється.

Ви можете використовувати будь-який вподобаний ресурс для малювання, але якщо вам просто потрібен ефект затемнення, створіть файл xml у папці, що малюється, з <shape>тегом як кореневий.

<?xml version="1.0" encoding="utf-8"?>
<shape
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:shape="rectangle" >
    <solid android:color="#000000" />
</shape>

(Див. Http://developer.android.com/guide/topics/resources/drawable-resource.html#Shape для отримання додаткової інформації про shapeелемент). Зверніть увагу, що в кольоровому тегу я не вказав альфа-значення, яке робило б елемент, що малюється, прозорим (наприклад #ff000000). Причиною цього є те, що будь-яке жорстко закодоване значення альфа, здається, замінює будь-які нові значення альфа, які ми встановлюємо за допомогою setAlpha()в нашому коді, тому ми цього не хочемо. Однак це означає, що предмет, що малюється, спочатку буде непрозорим (суцільним, непрозорим). Тож нам потрібно зробити його прозорим у onCreate()методі діяльності.

Ось код елемента Framelayout xml:

<FrameLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/mainmenu"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:foreground="@drawable/shape_window_dim" >
...
... your activity's content
...
</FrameLayout>

Ось метод onCreate () Activity:

public void onCreate( Bundle savedInstanceState)
{
  super.onCreate( savedInstanceState);

  setContentView( R.layout.activity_mainmenu);

  //
  // Your own Activity initialization code
  //

  layout_MainMenu = (FrameLayout) findViewById( R.id.mainmenu);
  layout_MainMenu.getForeground().setAlpha( 0);
}

Нарешті, код для затемнення активності:

layout_MainMenu.getForeground().setAlpha( 220); // dim

layout_MainMenu.getForeground().setAlpha( 0); // restore

Значення альфа переходять від 0(непрозорий) до 255(невидимий). Вам слід розмити активність, коли ви закриваєте спливаюче вікно.

Я не включив код для показу та відхилення спливаючого вікна, але ось посилання на те, як це можна зробити: http://www.mobilemancer.com/2011/01/08/popup-window-in-android/


щоб переконатися, що це працює на старих пристроях, вам також доведеться анулювати передній план після кожного внесення змін до його альфа-каналу. Я додав це до відповіді. +1 за приємну відповідь
user2224350

1
Я не можу думати про причину, яка це не позначена як правильна відповідь .. дивовижна відповідь, щонайменше.
Jayshil Dave

2
Що стосується того, що на панелі дій є панель дій, не можна її
затьмарити

це найкраще спрацювало для мене ... відповідь на подвійне спливаюче вікно мало умови перегонів, тому затемнене спливаюче вікно іноді перевищувало реальне спливаюче вікно
kenyee

1
Майже ідеально. Єдиним недоліком є ​​те, що ви повинні використовувати FrameLayout, оскільки для інших макетів немає методу "getForeground".
LiangWang

79

Так як PopupWindowтільки додає Viewдо WindowManagerвам можна використовувати updateViewLayout (View view, ViewGroup.LayoutParams params)для оновлення LayoutParamsваших PopupWindow«з contentViewпісля виклику шоу .. ().

Встановлення прапору вікна FLAG_DIM_BEHINDзатемнить усе за вікном. Використовуйте dimAmountдля керування величиною затемнення (1,0 для абсолютно непрозорого до 0,0 для без затемнення).

Майте на увазі, що якщо ви встановите фон для свого, PopupWindowвін перенесе ваш contentViewу контейнер, а це означає, що вам потрібно оновити його батьківський.

З фоном:

PopupWindow popup = new PopupWindow(contentView, width, height);
popup.setBackgroundDrawable(background);
popup.showAsDropDown(anchor);

View container = (View) popup.getContentView().getParent();
WindowManager wm = (WindowManager) getSystemService(Context.WINDOW_SERVICE);
WindowManager.LayoutParams p = (WindowManager.LayoutParams) container.getLayoutParams();
// add flag
p.flags |= WindowManager.LayoutParams.FLAG_DIM_BEHIND;
p.dimAmount = 0.3f;
wm.updateViewLayout(container, p);

Без фону:

PopupWindow popup = new PopupWindow(contentView, width, height);
popup.setBackgroundDrawable(null);
popup.showAsDropDown(anchor);

WindowManager wm = (WindowManager) getSystemService(Context.WINDOW_SERVICE);
WindowManager.LayoutParams p = (WindowManager.LayoutParams) contentView.getLayoutParams();
// add flag
p.flags |= WindowManager.LayoutParams.FLAG_DIM_BEHIND;
p.dimAmount = 0.3f;
wm.updateViewLayout(contentView, p);

Оновлення зефіру:

На M PopupWindow обгортає contentView всередині FrameLayout, що називається mDecorView. Якщо ви заринете у джерело PopupWindow, ви знайдете щось на зразок createDecorView(View contentView). Основна мета mDecorView - обробляти відправлення подій та переходи вмісту, які є новими для M. Це означає, що нам потрібно додати ще один .getParent () для доступу до контейнера.

З фоном, який вимагав би змінити щось на зразок:

View container = (View) popup.getContentView().getParent().getParent();

Краща альтернатива API 18+

Менш хакерське рішення з використанням ViewGroupOverlay:

1) Отримайте бажаний кореневий макет

ViewGroup root = (ViewGroup) getWindow().getDecorView().getRootView();

2) Телефонуйте applyDim(root, 0.5f);абоclearDim()

public static void applyDim(@NonNull ViewGroup parent, float dimAmount){
    Drawable dim = new ColorDrawable(Color.BLACK);
    dim.setBounds(0, 0, parent.getWidth(), parent.getHeight());
    dim.setAlpha((int) (255 * dimAmount));

    ViewGroupOverlay overlay = parent.getOverlay();
    overlay.add(dim);
}

public static void clearDim(@NonNull ViewGroup parent) {
    ViewGroupOverlay overlay = parent.getOverlay();
    overlay.clear();
}

2
Здається, це більше не працює на пристроях з ОС Android 6.0 Marshmallow. LayoutParams не відтворює WindowManager.LayoutParams. Чи є обхідне рішення?
Роберт

Дякуємо, що вказали на це. Я ще не тестував його на М. Оновить якомога швидше.
Маркус Рубі

@ stackoverflow.com/users/308153/robert У мене теж була така сама проблема. Я використовував вищезгаданий код із фоном.
Різван Сохайб

Якщо p не має значення, використовуйте це:if (p != null) { //same } else { popupView.addOnLayoutChangeListener(new View.OnLayoutChangeListener() { public void onLayoutChange(View v, int left, int top, int right, int bottom, int oldLeft, int oldTop, int oldRight, int oldBottom) { v.removeOnLayoutChangeListener(this); WindowManager.LayoutParams p = (WindowManager.LayoutParams) v.getLayoutParams(); p.flags = WindowManager.LayoutParams.FLAG_DIM_BEHIND; p.dimAmount = 0.3f; wm.updateViewLayout(v, p); } }); }
Бейтан Курт

1
FYI Коли я використовую це на нижчих рівнях API (наприклад, 15 і 20), він аварійно завершує роботу, оскільки popup.getContentView (). GetParent () не повертає Перегляд, тому лиття спричинить збій. У цьому випадку я використав метод @ BeytanKurt як запасний варіант.
Метью Горст

29

У вашому файлі xml додайте щось на зразок цього із шириною та висотою як 'match_parent'.

<RelativeLayout
        android:id="@+id/bac_dim_layout"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:background="#C0000000"
        android:visibility="gone" >
</RelativeLayout>

У вашій діяльності потворіть

//setting background dim when showing popup
back_dim_layout = (RelativeLayout) findViewById(R.id.share_bac_dim_layout);

Нарешті, зробіть видимим, коли ви показуєте своє спливаюче вікно, і зробіть його видимим, коли ви вийдете із спливаючого вікна.

back_dim_layout.setVisibility(View.VISIBLE);
back_dim_layout.setVisibility(View.GONE);

1
Будь ласка, не залишайте підписів на кшталт "Дякую, Рупеш" у публікаціях.
Саймон Хеллінгер,

3
але це не затьмарить панель дій / інструментів.
Silvia H

Plz, скажіть мені, як діяти на панелі інструментів
неясно

додати back_dim_layout.setVisibility (View.GONE); in PopupWindow.OnDismissListener
Pulkit 02

впевнений, допоміг мені
Pixxu Waku

12

Ще одна хитрість полягає у використанні 2 спливаючих вікон замість одного. Перше спливаюче вікно буде просто фіктивним видом із напівпрозорим фоном, що забезпечує затемнений ефект. Друге спливаюче вікно - це передбачуване спливаюче вікно.

Послідовність під час створення спливаючих вікон: 1-е показати фіктивне спливаюче вікно, а потім передбачуване спливаюче вікно.

Послідовність під час знищення: Відхиліть передбачуване спливаюче вікно, а потім фіктивне спливаюче вікно.

Найкращий спосіб зв’язати ці два - це додати OnDismissListener і замінити onDismiss()метод передбачуваного знищення фіктивного спливаючого вікна від їхнього.

Код для фіктивного спливаючого вікна:

fadepopup.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical" 
    android:id="@+id/fadePopup"
    android:background="#AA000000">
</LinearLayout>

Показувати спливаюче спливаюче вікно, щоб затемнити фон

private PopupWindow dimBackground() {

    LayoutInflater inflater = (LayoutInflater) EPGGRIDActivity.this
            .getSystemService(Context.LAYOUT_INFLATER_SERVICE);
    final View layout = inflater.inflate(R.layout.fadepopup,
            (ViewGroup) findViewById(R.id.fadePopup));
    PopupWindow fadePopup = new PopupWindow(layout, windowWidth, windowHeight, false);
    fadePopup.showAtLocation(layout, Gravity.NO_GRAVITY, 0, 0);
    return fadePopup;
}

У мене майже працює ... іноді буває перегоновий стан, коли спливаюче вікно знаходиться позаду спливаючого вікна тьмяного / фіктивного. Ще не знайшов обхідного шляху ... навіть затримка показу спливаючого вікна, щоб дати йому час за затриманим, не допомагає :-P
kenyee

2
@kenyee Я знайшов вирішення цієї проблеми. Я спочатку показую спливаюче вікно 'fade', а потім для того, щоб показати друге спливаюче вікно, я використовую myFadePopup.getContentView (). Post (... myPopup.showAtLocation ...);
Piotr

5

Я знайшов рішення для цього

Створіть власне прозоре діалогове вікно, і всередині цього діалогового вікна відкрийте спливаюче вікно:

dialog = new Dialog(context, android.R.style.Theme_Translucent_NoTitleBar);
emptyDialog = LayoutInflater.from(context).inflate(R.layout.empty, null);

/* blur background*/
WindowManager.LayoutParams lp = dialog.getWindow().getAttributes();  
lp.dimAmount=0.0f;  
dialog.getWindow().setAttributes(lp);  
dialog.getWindow().addFlags(WindowManager.LayoutParams.FLAG_BLUR_BEHIND); 
dialog.setContentView(emptyDialog);
dialog.setCanceledOnTouchOutside(true);
dialog.setOnShowListener(new OnShowListener()
{
    @Override
    public void onShow(DialogInterface dialogIx)
    {
        mQuickAction.show(emptyDialog); //open the PopupWindow here
    }
});
dialog.show();

xml для діалогового вікна (R.layout.empty):

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
     android:layout_height="match_parent" android:layout_width="match_parent"
     style="@android:style/Theme.Translucent.NoTitleBar" />

тепер ви хочете закрити діалогове вікно, коли спливаюче вікно закривається. тому

mQuickAction.setOnDismissListener(new OnDismissListener()
{
    @Override
    public void onDismiss()
    {
        if(dialog!=null)
        {
            dialog.dismiss(); // dismiss the empty dialog when the PopupWindow closes
            dialog = null;
        }
    }
});

Примітка: Я використовував NewQuickActionплагін для створення PopupWindow тут. Це також можна зробити на рідній Popup Windows


1

Для мене працює щось на зразок відповіді Абдельхака Муааму, перевіреного на рівнях API 16 та 27.

Замість використання popupWindow.getContentView().getParent()та передачі результату до View(який збій на рівні 16 API викликає там, що він повертає ViewRootImplоб'єкт, який не є екземпляром View), я просто використовую те, .getRootView()що вже повертає подання, тому ніякого кастингу там не потрібно.

Сподіваюся, це комусь допоможе :)

повний робочий приклад, скрембований з інших публікацій stackoverflow, просто скопіюйте його, наприклад, у прослуховувач onClick кнопки:

// inflate the layout of the popup window
LayoutInflater inflater = (LayoutInflater)getSystemService(LAYOUT_INFLATER_SERVICE);
if(inflater == null) {
    return;
}
//View popupView = inflater.inflate(R.layout.my_popup_layout, null); // this version gives a warning cause it doesn't like null as argument for the viewRoot, c.f. /programming/24832497 and /programming/26404951
View popupView = View.inflate(MyParentActivity.this, R.layout.my_popup_layout, null);

// create the popup window
final PopupWindow popupWindow = new PopupWindow(popupView,
        LinearLayout.LayoutParams.WRAP_CONTENT,
        LinearLayout.LayoutParams.WRAP_CONTENT,
        true // lets taps outside the popup also dismiss it
        );

// do something with the stuff in your popup layout, e.g.:
//((TextView)popupView.findViewById(R.id.textview_popup_helloworld))
//      .setText("hello stackoverflow");

// dismiss the popup window when touched
popupView.setOnTouchListener(new View.OnTouchListener() {
        @Override
        public boolean onTouch(View v, MotionEvent event) {
            popupWindow.dismiss();
            return true;
        }
});

// show the popup window
// which view you pass in doesn't matter, it is only used for the window token
popupWindow.showAtLocation(view, Gravity.CENTER, 0, 0);
//popupWindow.setOutsideTouchable(false); // doesn't seem to change anything for me

View container = popupWindow.getContentView().getRootView();
if(container != null) {
    WindowManager wm = (WindowManager)getSystemService(Context.WINDOW_SERVICE);
    WindowManager.LayoutParams p = (WindowManager.LayoutParams)container.getLayoutParams();
    p.flags = WindowManager.LayoutParams.FLAG_DIM_BEHIND;
    p.dimAmount = 0.3f;
    if(wm != null) {
        wm.updateViewLayout(container, p);
    }
}

1
findViewById(R.id.drawer_layout).setAlpha((float) 0.7);

R.id.drawer_layout - це ідентифікатор макета, для якого потрібно зменшити яскравість.


0

Ви можете використовувати android:theme="@android:style/Theme.Dialog"для цього. Створіть діяльність і укажіть AndroidManifest.xmlїї як:

    <activity android:name=".activities.YourActivity"
              android:label="@string/your_activity_label"
              android:theme="@android:style/Theme.Dialog">

Здається, це не робить фокусу. Я застосував це до своєї єдиної діяльності, яка знаходиться у фоновому режимі, коли я показую спливаюче вікно, яке є лише завищеним макетом у мене, а не окремим заняттям. Як застосувати FLAG_BLUR_BEHIND до спливаючого вікна?
k_day

0

Гаразд, тому я слідую відповіді uhmdown щодо затемнення фонової активності, коли спливаюче вікно відкрито. Але це створює для мене проблему. це затьмарювала активність і включала спливаюче вікно (означає затемнений чорний шар, як на активність, так і на спливаюче вікно, їх не можна розділяти).

так що я спробував таким чином,

створити файл dimming_black.xml для ефекту затемнення,

 <?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
    android:shape="rectangle">
    <solid android:color="#33000000" />
</shape>

І додайте як backgroundу FrameLayoutкореневому тегу xml, а також помістіть мої інші елементи керування LinearLayoutтаким чиномlayout.xml

<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    android:background="@drawable/ff_drawable_black">

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="vertical"
        android:layout_gravity="bottom"
        android:background="@color/white">

        // other codes...
    </LinearLayout>

</FrameLayout>

нарешті, я показую спливаюче вікно з моїм MainActivityдодатковим параметром, встановленим нижче.

           //instantiate popup window
            popupWindow = new PopupWindow(viewPopup, LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.MATCH_PARENT, true);

            //display the popup window
            popupWindow.showAtLocation(layout_ff, Gravity.BOTTOM, 0, 0);

Результат: введіть тут опис зображення

це працює для мене, також вирішена проблема, як прокоментував BaDo . За допомогою цього Actionbarтакож можна затемнити.

Ps, я не кажу, що uhmdown's помилковий. я навчився формувати його відповідь і намагаюся розвивати свою проблему. Я також заплутався, хороший це спосіб чи ні.

Будь-які пропозиції також вдячні також вибачте за мою погану англійську.


0

Можливо, це репо допоможе вам: BasePopup

Це мій репо, який використовується для вирішення різних проблем PopupWindow.

У випадку використання бібліотеки, якщо вам потрібно розмити фон, просто зателефонуйте setBlurBackgroundEnable(true).

Докладніше див. У вікі.. Мова на zh-cn)

BasePopup: wiki


-1

Цей код працює

        pwindo = new PopupWindow(layout, ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT, true);
        pwindo.showAtLocation(layout, Gravity.CENTER, 0, 0);
        pwindo.setOutsideTouchable(false);

        View container = (View) pwindo.getContentView().getParent();
        WindowManager wm = (WindowManager) getSystemService(Context.WINDOW_SERVICE);
        WindowManager.LayoutParams p = (WindowManager.LayoutParams) container.getLayoutParams();
        p.flags = WindowManager.LayoutParams.FLAG_DIM_BEHIND;
        p.dimAmount = 0.3f;
        wm.updateViewLayout(container, p);
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.