Desenfocar o atenuar el fondo cuando Android PopupWindow está activo

85

Me gustaría poder difuminar o atenuar el fondo cuando muestro mi ventana emergente usando popup.showAtLocation, y desenfocar / atenuar el fondo cuando popup.dismissse llama.

He intentado aplicar parámetros de diseño FLAG_BLUR_BEHINDy FLAG_DIM_BEHINDa mi actividad, pero parece que esto simplemente difumina y atenúa el fondo tan pronto como se inicia mi aplicación.

¿Cómo puedo difuminar / atenuar solo con ventanas emergentes?

k_day
fuente
1
Advertencia: FLAG_BLUR_BEHIND tiene errores en algunos teléfonos y hace que el teléfono no responda (aparentemente, el desenfoque se realiza en el software). En el Droid, por ejemplo. Aparte de eso, lo que dijo Macarse: FLAG_BLUR_BEHIND debe aplicarse a la ventana en primer plano.
EboMike
1
Busco la misma respuesta a esta pregunta. ¿Hay alguna forma de atenuar mediante programación la vista que se encuentra detrás de la ventana emergente?
Nick
1
@Nick Cómo resolvió este problema ...
Amit

Respuestas:

106

La pregunta era sobre la Popupwindowclase, pero todos han dado respuestas que usan la Dialogclase. Eso es bastante inútil si necesita usar la Popupwindowclase, porque Popupwindowno tiene un getWindow()método.

Encontré una solución que realmente funciona con Popupwindow. Solo requiere que la raíz del archivo xml que usa para la actividad en segundo plano sea un FrameLayout. Puedes darle Framelayoutuna android:foregroundetiqueta al elemento . Lo que hace esta etiqueta es especificar un recurso dibujable que se superpondrá a toda la actividad (es decir, si Framelayout es el elemento raíz en el archivo xml). A continuación, puede controlar la opacidad ( setAlpha()) del dibujable de primer plano.

Puede usar cualquier recurso dibujable que desee, pero si solo desea un efecto de atenuación, cree un archivo xml en la carpeta dibujable con la <shape>etiqueta como raíz.

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

(Consulte http://developer.android.com/guide/topics/resources/drawable-resource.html#Shape para obtener más información sobre el shapeelemento). Tenga en cuenta que no especifiqué un valor alfa en la etiqueta de color que haría transparente el elemento dibujable (por ejemplo #ff000000). La razón de esto es que cualquier valor alfa codificado parece anular cualquier valor alfa nuevo que establezcamos a través setAlpha()de nuestro código, por lo que no queremos eso. Sin embargo, eso significa que el elemento dibujable inicialmente será opaco (sólido, no transparente). Por tanto, debemos hacerlo transparente en el onCreate()método de la actividad .

Aquí está el código del elemento 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>

Aquí está el método onCreate () de 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);
}

Finalmente, el código para atenuar la actividad:

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

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

Los valores alfa van de 0(opaco) a 255(invisible). Debe quitar la atenuación de la actividad cuando cierre la ventana emergente.

No he incluido código para mostrar y descartar la ventana emergente, pero aquí hay un enlace a cómo se puede hacer: http://www.mobilemancer.com/2011/01/08/popup-window-in-android/

uhmdown
fuente
Para asegurarse de que esto funcione en dispositivos antiguos, también debe invalidar el primer plano después de cada cambio realizado en su canal alfa. Agregué esto a la respuesta. +1 para la buena respuesta
user2224350
1
No puedo pensar en una razón por la que esto no esté marcado como la respuesta correcta ... respuesta impresionante por decir lo menos.
Jayshil Dave
2
¿Qué pasa con la barra de acción a la vista? No se puede atenuar
BaDo
esto funcionó mejor para mí ... la respuesta de la ventana emergente doble a continuación tenía una condición de carrera, por lo que la ventana emergente atenuada a veces estaba sobre la ventana emergente real
kenyee
1
Casi perfecto. El único inconveniente es que tienes que usar FrameLayout ya que no hay un método "getForeground" para otros diseños.
LiangWang
80

Dado que PopupWindowsólo se suma una Viewque WindowManagerse puede utilizar updateViewLayout (View view, ViewGroup.LayoutParams params)para actualizar el LayoutParamsde sus PopupWindow's contentViewdespués de llamar espectáculo .. ().

Establecer la bandera de la ventana FLAG_DIM_BEHINDatenuará todo lo que se encuentra detrás de la ventana. Úselo dimAmountpara controlar la cantidad de atenuación (1.0 para completamente opaco a 0.0 para no atenuar).

Tenga en cuenta que si establece un fondo para su, PopupWindowlo colocará contentViewen un contenedor, lo que significa que debe actualizar su padre.

Con antecedentes:

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

Sin fondo:

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

Actualización de Marshmallow:

En M, PopupWindow envuelve el contentView dentro de un FrameLayout llamado mDecorView. Si profundiza en la fuente de PopupWindow, encontrará algo como createDecorView(View contentView). El propósito principal de mDecorView es manejar el envío de eventos y las transiciones de contenido, que son nuevas para M. Esto significa que debemos agregar un .getParent () más para acceder al contenedor.

Con antecedentes que requerirían un cambio a algo como:

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

Mejor alternativa para API 18+

Una solución menos hacky usando ViewGroupOverlay:

1) Obtenga el diseño raíz deseado

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

2) Llame applyDim(root, 0.5f);oclearDim()

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();
}
Markus Rubey
fuente
2
Esto parece que ya no funciona en dispositivos con Android 6.0 Marshmallow. El layoutParams no se envía a WindowManager.LayoutParams. ¿Existe alguna solución?
Robert
Gracias por mencionarlo. Todavía no lo he probado en M. Se actualizará lo antes posible.
Markus Rubey
@ stackoverflow.com/users/308153/robert También tuve el mismo problema. Usé el código anterior con fondo.
Rizwan Sohaib
Si p es nulo, use esto: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); } }); }
Beytan Kurt
1
FYI Cuando uso esto en niveles de API más bajos (por ejemplo, 15 y 20), se bloquea porque popup.getContentView (). GetParent () no devuelve View, por lo que la transmisión causará un bloqueo. Para este caso utilicé el método de @ BeytanKurt como alternativa.
Matthew Horst
29

En su archivo xml agregue algo como esto con ancho y alto como '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>

En tu actividad oncreate

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

Finalmente, haz que sea visible cuando muestres tu ventana emergente y haz que desaparezca cuando salgas de la ventana emergente.

back_dim_layout.setVisibility(View.VISIBLE);
back_dim_layout.setVisibility(View.GONE);
user2257590
fuente
1
No deje firmas como "Gracias, Rupesh" en las publicaciones.
Simon Hellinger
3
pero esto no atenuará la barra de acciones / herramientas.
Silvia H
Por favor, dime cómo se atenúa la barra de herramientas de acción
SAndroidD
agregar back_dim_layout.setVisibility (View.GONE); en PopupWindow.OnDismissListener
Pulkit
Seguro que me ayudó
Pixxu Waku
12

Otro truco consiste en utilizar 2 ventanas emergentes en lugar de una. La primera ventana emergente será simplemente una vista ficticia con fondo translúcido que proporciona el efecto de atenuación. La segunda ventana emergente es su ventana emergente prevista.

Secuencia al crear ventanas emergentes: muestra la ventana emergente ficticia primero y luego la ventana emergente deseada.

Secuencia al destruir: descarte la ventana emergente deseada y luego la ventana emergente ficticia.

La mejor manera de vincular estos dos es agregar un OnDismissListener y anular el onDismiss()método de la intención de atenuar la ventana emergente ficticia de su.

Código para la ventana emergente ficticia:

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>

Mostrar ventana emergente de atenuación para atenuar el fondo

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;
}
Abhishek Nandi
fuente
Casi funciona para mí ... a veces, hay una condición de carrera en la que la ventana emergente está detrás de la ventana emergente tenue / ficticia. Aún no he encontrado una solución ... incluso retrasar la visualización de la ventana emergente para darle tiempo detrás de la retrasada no ayuda
:-P
2
@kenyee He encontrado una solución para este problema. Primero muestro la ventana emergente 'fade', y luego, para mostrar la segunda ventana emergente, estoy usando myFadePopup.getContentView (). Post (... myPopup.showAtLocation ...);
Piotr
5

He encontrado una solución para esto

Cree un diálogo transparente personalizado y dentro de ese diálogo abra la ventana emergente:

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 para el diálogo (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" />

ahora desea cerrar el cuadro de diálogo cuando se cierra la ventana emergente. entonces

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

Nota: he usado NewQuickAction complemento para crear PopupWindow aquí. También se puede hacer en ventanas emergentes nativas

ORDENADOR PERSONAL.
fuente
1

Para mí, algo como la respuesta de Abdelhak Mouaamou funciona, probado en los niveles de API 16 y 27.

En lugar de usar popupWindow.getContentView().getParent()y enviar el resultado a View(que se bloquea en el nivel de API 16 porque allí devuelve un ViewRootImplobjeto que no es una instancia de View), solo uso.getRootView() que ya devuelve una vista, por lo que no se requiere conversión allí.

Espero que ayude a alguien :)

ejemplo de trabajo completo codificado de otras publicaciones de stackoverflow, simplemente cópielo y péguelo, por ejemplo, en el oyente onClick de un botón:

// 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);
    }
}
chrisi1698
fuente
1
findViewById(R.id.drawer_layout).setAlpha((float) 0.7);

R.id.drawer_layout es la identificación del diseño cuyo brillo desea atenuar.

Devbrat Anuragi
fuente
0

Puede utilizar android:theme="@android:style/Theme.Dialog"para hacer eso. Cree una actividad y AndroidManifest.xmldefina la actividad como:

    <activity android:name=".activities.YourActivity"
              android:label="@string/your_activity_label"
              android:theme="@android:style/Theme.Dialog">
Macarse
fuente
Esto no parece funcionar. Apliqué a esto a mi única actividad, que está en segundo plano cuando muestro una ventana emergente que es solo un diseño inflado que tengo, y no una actividad separada. ¿Cómo aplico FLAG_BLUR_BEHIND a una ventana emergente?
k_day
0

ok, sigo la respuesta de uhmdown para atenuar la actividad de fondo cuando la ventana emergente está abierta. Pero me crea un problema. estaba atenuando la actividad e incluía una ventana emergente (significa que el negro atenuado en capas tanto en la actividad como en la ventana emergente también, no se puede separar).

así que lo intenté de esta manera

cree un archivo dimming_black.xml para el efecto de atenuación,

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

Y añadir backgrounden FrameLayoutcomo etiqueta XML raíz, también poner mis otros controles en LinearLayoutcomo estolayout.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>

por fin muestro una ventana emergente en mi MainActivitycon algunos parámetros adicionales establecidos como se muestra a continuación.

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

Resultado: ingrese la descripción de la imagen aquí

Funciona para mí, también problema resuelto según lo comentado por BaDo . Con esto Actionbartambién se puede atenuar.

Ps, no estoy diciendo que uhmdown's esté mal. Aprendí de su respuesta y trato de evolucionar para mi problema. También confundí si esta es una buena manera o no.

Cualquier sugerencia también es apreciada y lo siento por mi mal inglés.

Sahil M.
fuente
0

Quizás este repositorio te ayude: BasePopup

Este es mi repositorio, que se utiliza para resolver varios problemas de PopupWindow.

En el caso de usar la biblioteca, si necesita desenfocar el fondo, simplemente llame setBlurBackgroundEnable(true).

Consulte la wiki para obtener más detalles. (Idioma en zh-cn)

BasePopup: wiki

razerdp
fuente
-1

Este código funciona

        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);
Abdelhak Mouaamou
fuente