Desfocar ou escurecer o fundo quando o Android PopupWindow estiver ativo

85

Eu gostaria de poder desfocar ou escurecer o fundo quando eu mostrar minha janela pop-up usando popup.showAtLocation, e desfocar / escurecer o fundo quando popup.dismissfor chamado.

Tentei aplicar parâmetros de layout FLAG_BLUR_BEHINDe FLAG_DIM_BEHINDà minha atividade, mas isso parece apenas embaçar e escurecer o fundo assim que meu aplicativo é iniciado.

Como posso desfocar / escurecer apenas com pop-ups?

k_day
fonte
1
Aviso: FLAG_BLUR_BEHIND apresenta erros em alguns telefones e faz com que o telefone não responda (aparentemente o desfoque é feito no software). No Droid, por exemplo. Fora isso, o que Macarse disse - FLAG_BLUR_BEHIND precisa ser aplicado à janela em primeiro plano.
EboMike
1
Estou procurando a mesma resposta para esta pergunta. Existe alguma maneira de escurecer programaticamente a visualização que está atrás da janela pop-up?
Nick
1
@Nick Como você resolveu esse problema ..
Amit

Respostas:

108

A pergunta era sobre a Popupwindowaula, mas todos deram respostas que usam a Dialogaula. Isso é praticamente inútil se você precisar usar a Popupwindowclasse, porque Popupwindownão tem um getWindow()método.

Eu encontrei uma solução que realmente funciona Popupwindow. Requer apenas que a raiz do arquivo xml que você usa para a atividade em segundo plano seja a FrameLayout. Você pode dar ao Framelayoutelemento uma android:foregroundtag. O que essa tag faz é especificar um recurso drawable que será colocado em camadas sobre toda a atividade (ou seja, se o Framelayout for o elemento raiz no arquivo xml). Você pode então controlar a opacidade ( setAlpha()) do drawable do primeiro plano.

Você pode usar qualquer recurso drawable que desejar, mas se quiser apenas um efeito de escurecimento, crie um arquivo xml na pasta drawable com a <shape>tag como raiz.

<?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 obter mais informações sobre o shapeelemento). Observe que não especifiquei um valor alfa na tag de cor que tornaria o item drawable transparente (por exemplo #ff000000). A razão para isso é que qualquer valor alfa codificado parece substituir quaisquer novos valores alfa que definimos por meio do setAlpha()em nosso código, então não queremos isso. No entanto, isso significa que o item drawable inicialmente será opaco (sólido, não transparente). Portanto, precisamos torná-lo transparente no onCreate()método da atividade .

Este é o código do 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>

Este é o método onCreate () da 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, o código para escurecer a atividade:

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

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

Os valores alfa vão de 0(opaco) a 255(invisível). Você deve apagar a atividade ao fechar a janela Popup.

Não incluí o código para mostrar e dispensar o Popupwindow, mas aqui está um link de como isso pode ser feito: http://www.mobilemancer.com/2011/01/08/popup-window-in-android/

uhmdown
fonte
para garantir que isso funcione em dispositivos antigos, você também deve invalidar o primeiro plano após cada alteração feita em seu canal alfa. Eu adicionei isso à resposta. +1 pela boa resposta
user2224350
1
Não consigo pensar em uma razão pela qual isso não seja marcado como a resposta certa .. resposta incrível para dizer o mínimo.
Jayshil Dave
2
Que tal em vista ter Actionbar, você não pode
escurecê-
funcionou melhor para mim ... a resposta de pop-up duplo abaixo tinha uma condição de corrida, então o pop-up esmaecido às vezes ficava sobre o pop
kenyee
1
Quase perfeito. A única desvantagem é que você tem que usar FrameLayout, pois não há método "getForeground" para outros layouts.
LiangWang
81

Desde PopupWindowapenas adiciona um Viewpara WindowManagerque você pode usar updateViewLayout (View view, ViewGroup.LayoutParams params)para atualizar o LayoutParamsdos seus PopupWindow's contentViewdepois de chamar espectáculo .. ().

Definir o sinalizador da janela FLAG_DIM_BEHINDescurecerá tudo atrás da janela. Use dimAmountpara controlar a quantidade de não ofuscante (1,0 para opaco completamente a 0,0 para sem brilho).

Lembre-se de que se você definir um plano de fundo para seu, PopupWindowele o colocará contentViewem um contêiner, o que significa que você precisa atualizá-lo.

Com fundo:

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

Sem fundo:

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

Atualização do Marshmallow:

Em M PopupWindow envolve o contentView dentro de um FrameLayout chamado mDecorView. Se você pesquisar a fonte PopupWindow, encontrará algo como createDecorView(View contentView). O objetivo principal do mDecorView é lidar com o envio de eventos e as transições de conteúdo, que são novas para M. Isso significa que precisamos adicionar mais um .getParent () para acessar o contêiner.

Com antecedentes que exigiriam uma mudança para algo como:

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

Melhor alternativa para API 18+

Uma solução menos hacky usando ViewGroupOverlay:

1) Obtenha o layout de raiz desejado

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

2) Ligue applyDim(root, 0.5f);ouclearDim()

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
fonte
2
Isso parece não funcionar mais em dispositivos com Android 6.0 Marshmallow. O layoutParams não é lançado para WindowManager.LayoutParams. Existe uma solução alternativa?
Robert
Obrigado por apontar isso. Eu não testei no M ainda. Irá atualizar o mais rápido possível.
Markus Rubey
@ stackoverflow.com/users/308153/robert Eu também tive o mesmo problema. Usei o código acima com plano de fundo.
Rizwan Sohaib
Se p for nulo, use: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
Para sua informação, quando eu uso isso em níveis de API mais baixos (por exemplo, 15 e 20), ele trava porque popup.getContentView (). GetParent () não retorna View, então lançá-lo causará um travamento. Para este caso, usei o método de @ BeytanKurt como fallback.
Matthew Horst
30

Em seu arquivo xml, adicione algo assim com largura e altura 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>

Em sua atividade ao criar

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

Finalmente, torne visível quando você mostrar sua janela pop-up e deixe-o visível quando você sair da janela pop-up.

back_dim_layout.setVisibility(View.VISIBLE);
back_dim_layout.setVisibility(View.GONE);
user2257590
fonte
1
Por favor, não deixe assinaturas como "Obrigado, Rupesh" nas postagens.
Simon Hellinger,
3
mas isso não escurecerá a barra de ação / ferramenta.
Silvia H
Por favor, diga-me como fazer a barra de ferramentas escurecer
SAndroidD
1
adicionar back_dim_layout.setVisibility (View.GONE); em PopupWindow.OnDismissListener
Pulkit
com certeza me ajudou
Pixxu Waku
12

Outro truque é usar 2 janelas pop-up em vez de uma. A primeira janela pop-up será simplesmente uma visão fictícia com fundo translúcido que fornece o efeito de escurecimento. A segunda janela pop-up é a janela pop-up pretendida.

Sequência durante a criação de janelas pop-up: Mostre a janela pop-up fictícia primeiro e depois a janela pop-up pretendida.

Sequência durante a destruição: Ignore a janela pop-up pretendida e, em seguida, a janela pop-up fictícia.

A melhor maneira de vincular esses dois é adicionar um OnDismissListener e substituir o onDismiss()método do pretendido para apagar a janela popup fictícia deles.

Código para a janela pop-up fictícia:

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 pop-up de esmaecimento para escurecer o fundo

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
fonte
Quase funciona para mim ... às vezes, há uma condição de corrida em que a janela pop-up está atrás da janela pop-up esmaecida / fictícia. Ainda não encontrei uma solução alternativa ... mesmo atrasar a exibição da janela pop-up para dar um tempo atrás da janela atrasada não ajuda :-P
kenyee
2
@kenyee Encontrei uma solução para esse problema. Eu mostro o pop-up 'fade' primeiro e, em seguida, para mostrar o segundo pop-up, estou usando myFadePopup.getContentView (). Post (... myPopup.showAtLocation ...);
Piotr
5

Eu encontrei uma solução para isso

Crie uma caixa de diálogo transparente personalizada e, dentro dessa caixa de diálogo, abra a janela pop-up:

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 a caixa de 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" />

agora você deseja descartar a caixa de diálogo quando a janela Popup for fechada. então

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

Nota: usei o NewQuickActionplugin para criar PopupWindow aqui. Também pode ser feito em janelas pop-up nativas

PC.
fonte
1

Para mim, algo como a resposta de Abdelhak Mouaamou funciona, testado na API de nível 16 e 27.

Em vez de usar popupWindow.getContentView().getParent()e lançar o resultado para View(que trava na API de nível 16 porque lá ele retorna um ViewRootImplobjeto que não é uma instância de View), eu apenas uso o .getRootView()que já retorna uma visualização, portanto, não é necessário lançar aqui.

Espero que ajude alguém :)

exemplo de trabalho completo misturado a partir de outras postagens stackoverflow, basta copiar e colar, por exemplo, no ouvinte onClick de um botão:

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

R.id.drawer_layout é o id do layout cujo brilho você deseja diminuir.

ASLAN
fonte
0

Você pode usar android:theme="@android:style/Theme.Dialog"para fazer isso. Crie uma atividade e em você AndroidManifest.xmldefina a atividade como:

    <activity android:name=".activities.YourActivity"
              android:label="@string/your_activity_label"
              android:theme="@android:style/Theme.Dialog">
Macarse
fonte
Isso não parece funcionar. Apliquei isso à minha única atividade, que está em segundo plano quando mostro uma janela pop-up que é apenas um layout inflado que tenho, e não uma atividade separada. Como aplico FLAG_BLUR_BEHIND a uma PopupWindow?
k_day
0

ok, então eu sigo a resposta do uhmdown para escurecer a atividade em segundo plano quando a janela pop está aberta. Mas isso cria problemas para mim. estava escurecendo a atividade e incluía janela pop-up (significa preto escuro em camadas na atividade e no pop-up também, não pode ser separado).

então eu tentei desta forma,

crie um arquivo dimming_black.xml para efeito de escurecimento,

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

E adicionar como backgroundem FrameLayoutcomo tag xml raiz, também colocar meus outros controles LinearLayoutcomo estelayout.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>

finalmente mostro o pop-up no meu MainActivitycom alguns parâmetros extras definidos como abaixo.

           //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: insira a descrição da imagem aqui

funciona para mim, também resolveu problema como comentado pelo BaDo . Com isso Actionbartambém pode ser esmaecido.

PS, eu não estou dizendo que o uhmdown está errado. Aprendi a responder e tento evoluir para o meu problema. Também fiquei confuso se essa é uma boa maneira ou não.

Qualquer sugestão também é apreciada, desculpe pelo meu inglês ruim.

Sahil M.
fonte
0

Talvez este repo ajude para você: BasePopup

Este é o meu repositório, que é usado para resolver vários problemas do PopupWindow.

No caso de usar a biblioteca, se você precisar desfocar o fundo, basta chamar setBlurBackgroundEnable(true).

Veja o wiki para mais detalhes. (Idioma em zh-cn)

BasePopup: wiki

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