Размытие или тусклый фон при активном Android PopupWindow


85

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

Я попытался применить параметры макета FLAG_BLUR_BEHINDи FLAG_DIM_BEHINDк своей деятельности, но это, похоже, просто размывает и затемняет фон, как только мое приложение запускается.

Как сделать размытие / затемнение только во всплывающих окнах?


1
Предупреждение: FLAG_BLUR_BEHIND не работает на некоторых телефонах и делает телефон очень невосприимчивым (видимо, размытие выполняется программно). Например, на Дроиде. Помимо этого, то, что сказал Макарс, - FLAG_BLUR_BEHIND необходимо применить к окну на переднем плане.
EboMike


1
Я ищу такой же ответ на этот вопрос. Есть ли способ программно затемнить представление, которое находится за всплывающим окном?
Ник

1
@Nick Как ты решил эту проблему ..
Амит,

Ответы:


108

Вопрос был о Popupwindowклассе, но все дали ответы, которые используют этот Dialogкласс. Это бесполезно, если вам нужно использовать Popupwindowкласс, потому что у Popupwindowнего нет getWindow()метода.

Я нашел решение, которое действительно работает Popupwindow. Требуется только, чтобы корень XML-файла, который вы используете для фоновой активности, был FrameLayout. Вы можете присвоить Framelayoutэлементу android:foregroundтег. Этот тег указывает на доступный для рисования ресурс, который будет размещен поверх всего действия (то есть, если Framelayout является корневым элементом в файле xml). Затем вы можете управлять непрозрачностью ( setAlpha()) переднего плана, доступного для рисования.

Вы можете использовать любой ресурс с возможностью рисования, который вам нравится, но если вам просто нужен эффект затемнения, создайте файл xml в папке с возможностью рисования с <shape>тегом в качестве root.

<?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()методе действия.

Вот код элемента xml Framelayout:

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

Вот метод Activity onCreate ():

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(невидимый). Вы должны убрать яркость активности, когда закрываете Popupwindow.

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


чтобы убедиться, что это работает на старых устройствах, вы также должны аннулировать передний план после каждого изменения, внесенного в его альфа-канал. Я добавил это к ответу. +1 за хороший ответ
user2224350

1
Я не могу придумать причину, по которой это не помечено как правильный ответ ... отличный ответ, мягко говоря.
Jayshil Dave

2
Что насчет того, чтобы иметь панель действий, вы не можете ее затемнить
BaDo

это сработало лучше всего для меня ... ответ с двумя всплывающими окнами ниже имел состояние гонки, поэтому затемненное всплывающее окно иногда было поверх реального всплывающего окна
kenyee

1
Практически идеально. Единственный недостаток - вам нужно использовать FrameLayout, поскольку для других макетов нет метода getForeground.
LiangWang 05

81

Так как 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);

Обновление Marshmallow:

В 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. Есть ли обходной путь?
Роберт

Спасибо, что указали на это. На M еще не тестировал. Обновим как можно скорее.
Маркус Рубей

@ 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
К вашему сведению, когда я использую это на более низких уровнях API (например, 15 и 20), он вылетает, потому что popup.getContentView (). GetParent () не возвращает View, поэтому его приведение приведет к сбою. В этом случае я использовал метод @ BeytanKurt как запасной вариант.
Мэтью Хорст,

30

В ваш 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>

В вашей деятельности oncreate

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

Пожалуйста, скажите мне, как сделать тусклую панель инструментов
SAndroidD

1
добавить back_dim_layout.setVisibility (View.GONE); в PopupWindow.OnDismissListener
Pulkit

конечно помог мне
Pixxu Waku

12

Еще одна хитрость - использовать 2 всплывающих окна вместо одного. Первое всплывающее окно будет просто фиктивным видом с полупрозрачным фоном, обеспечивающим эффект затемнения. Второе всплывающее окно - это ваше предполагаемое всплывающее окно.

Последовательность создания всплывающих окон: сначала покажите фиктивное всплывающее окно, а затем предполагаемое всплывающее окно.

Последовательность при разрушении: закройте предполагаемое всплывающее окно, а затем фиктивное всплывающее окно.

Лучший способ связать эти два - добавить 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 Я нашел решение этой проблемы. Сначала я показываю всплывающее окно «исчезновение», а затем, чтобы показать второе всплывающее окно, я использую myFadePopup.getContentView (). Post (... myPopup.showAtLocation ...);
Петр

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. Это также можно сделать в родных всплывающих окнах


1

Для меня работает что-то вроде ответа Абдельхака Муаму, протестированного на уровне API 16 и 27.

Вместо того, чтобы использовать popupWindow.getContentView().getParent()и преобразовывать результат в View(который вылетает на уровне API 16, потому что там он возвращает 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 к PopupWindow?
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 неправильно. Я узнал из его ответа и пытаюсь развить свою проблему. Я тоже запуталась, хороший это способ или нет.

Любые предложения также приветствуются, также извините за мой плохой английский.


0

Может быть, вам поможет это репо: BasePopup

Это мое репо, которое используется для решения различных проблем PopupWindow.

В случае использования библиотеки, если вам нужно размыть фон, просто вызовите setBlurBackgroundEnable(true).

См. Вики для получения более подробной информации. (Язык в zh-cn)

BasePopup: вики


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