Android PopupWindow用法解析
PopupWindow使用
PopupWindow这个类用来实现一个弹出框,可以使用任意布局的View作为其内容,这个弹出框是悬浮在当前activity之上的。
PopupWindow使用Demo
这个类的使用,不再过多解释,直接上代码吧。
比如弹出框的布局:
<?xmlversion="1.0"encoding="utf-8"?> <LinearLayoutxmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="wrap_content" android:background="#FFBBFFBB" android:orientation="vertical"> <TextView android:layout_width="match_parent" android:layout_height="wrap_content" android:padding="10dp" android:text="HelloMyWindow" android:textSize="20sp"/> <Button android:id="@+id/button1" android:layout_width="match_parent" android:layout_height="wrap_content" android:padding="10dp" android:text="Button" android:textSize="20sp"/> </LinearLayout>
Activity的布局中只有一个按钮,按下后会弹出框,Activity代码如下:
packagecom.example.hellopopupwindow; importandroid.os.Bundle; importandroid.app.Activity; importandroid.content.Context; importandroid.util.Log; importandroid.view.LayoutInflater; importandroid.view.MotionEvent; importandroid.view.View; importandroid.view.View.OnClickListener; importandroid.view.View.OnTouchListener; importandroid.view.ViewGroup.LayoutParams; importandroid.widget.Button; importandroid.widget.PopupWindow; importandroid.widget.Toast; publicclassMainActivityextendsActivity{ privateContextmContext=null; @Override protectedvoidonCreate(BundlesavedInstanceState){ super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); mContext=this; Buttonbutton=(Button)findViewById(R.id.button); button.setOnClickListener(newView.OnClickListener(){ @Override publicvoidonClick(Viewview){ showPopupWindow(view); } }); } privatevoidshowPopupWindow(Viewview){ //一个自定义的布局,作为显示的内容 ViewcontentView=LayoutInflater.from(mContext).inflate( R.layout.pop_window,null); //设置按钮的点击事件 Buttonbutton=(Button)contentView.findViewById(R.id.button1); button.setOnClickListener(newOnClickListener(){ @Override publicvoidonClick(Viewv){ Toast.makeText(mContext,"buttonispressed", Toast.LENGTH_SHORT).show(); } }); finalPopupWindowpopupWindow=newPopupWindow(contentView, LayoutParams.WRAP_CONTENT,LayoutParams.WRAP_CONTENT,true); popupWindow.setTouchable(true); popupWindow.setTouchInterceptor(newOnTouchListener(){ @Override publicbooleanonTouch(Viewv,MotionEventevent){ Log.i("mengdd","onTouch:"); returnfalse; //这里如果返回true的话,touch事件将被拦截 //拦截后PopupWindow的onTouchEvent不被调用,这样点击外部区域无法dismiss } }); //如果不设置PopupWindow的背景,无论是点击外部区域还是Back键都无法dismiss弹框 //我觉得这里是API的一个bug popupWindow.setBackgroundDrawable(getResources().getDrawable( R.drawable.selectmenu_bg_downward)); //设置好参数之后再show popupWindow.showAsDropDown(view); } }
弹出框的布局中有一个TextView和一个Button,Button点击后显示Toast,如图:
第一次实现的时候遇到了问题,就是弹出框不会在按下Back键的时候消失,点击弹框外区域也没有正常消失,搜索了一下,都说只要设置背景就好了。
然后我就找了个图片,果然弹框能正常dismiss了(见注释)。
PopupWindow源码分析
为了解答一下上面的问题,看看源码(最新APILevel19,Android4.4.2)。
1.显示方法
显示提供了两种形式:
showAtLocation()显示在指定位置,有两个方法重载:
publicvoidshowAtLocation(Viewparent,intgravity,intx,inty) publicvoidshowAtLocation(IBindertoken,intgravity,intx,inty)
showAsDropDown()显示在一个参照物View的周围,有三个方法重载:
publicvoidshowAsDropDown(Viewanchor) publicvoidshowAsDropDown(Viewanchor,intxoff,intyoff) publicvoidshowAsDropDown(Viewanchor,intxoff,intyoff,intgravity)
最后一种带Gravity参数的方法是API19新引入的。
弹出的方法中首先需要preparePopup(),最后再invokePopup()。
prepare的方法中可以看到有无背景的分别:
/** *<p>PreparethepopupbyembeddinginintoanewViewGroupifthe *backgrounddrawableisnotnull.Ifembeddingisrequired,thelayout *parameters'heightismnodifiedtotakeintoaccountthebackground's *padding.</p> * *@parampthelayoutparametersofthepopup'scontentview */ privatevoidpreparePopup(WindowManager.LayoutParamsp){ if(mContentView==null||mContext==null||mWindowManager==null){ thrownewIllegalStateException("Youmustspecifyavalidcontentviewby" +"callingsetContentView()beforeattemptingtoshowthepopup."); } if(mBackground!=null){ finalViewGroup.LayoutParamslayoutParams=mContentView.getLayoutParams(); intheight=ViewGroup.LayoutParams.MATCH_PARENT; if(layoutParams!=null&& layoutParams.height==ViewGroup.LayoutParams.WRAP_CONTENT){ height=ViewGroup.LayoutParams.WRAP_CONTENT; } //whenabackgroundisavailable,weembedthecontentview //withinanotherviewthatownsthebackgrounddrawable PopupViewContainerpopupViewContainer=newPopupViewContainer(mContext); PopupViewContainer.LayoutParamslistParams=newPopupViewContainer.LayoutParams( ViewGroup.LayoutParams.MATCH_PARENT,height ); popupViewContainer.setBackgroundDrawable(mBackground); popupViewContainer.addView(mContentView,listParams); mPopupView=popupViewContainer; }else{ mPopupView=mContentView; } mPopupViewInitialLayoutDirectionInherited= (mPopupView.getRawLayoutDirection()==View.LAYOUT_DIRECTION_INHERIT); mPopupWidth=p.width; mPopupHeight=p.height; }
2.背景是否为空对Touch事件的影响
如果有背景,则会在contentView外面包一层PopupViewContainer之后作为mPopupView,如果没有背景,则直接用contentView作为mPopupView。
而这个PopupViewContainer是一个内部私有类,它继承了FrameLayout,在其中重写了Key和Touch事件的分发处理:
@Override publicbooleandispatchKeyEvent(KeyEventevent){ if(event.getKeyCode()==KeyEvent.KEYCODE_BACK){ if(getKeyDispatcherState()==null){ returnsuper.dispatchKeyEvent(event); } if(event.getAction()==KeyEvent.ACTION_DOWN &&event.getRepeatCount()==0){ KeyEvent.DispatcherStatestate=getKeyDispatcherState(); if(state!=null){ state.startTracking(event,this); } returntrue; }elseif(event.getAction()==KeyEvent.ACTION_UP){ KeyEvent.DispatcherStatestate=getKeyDispatcherState(); if(state!=null&&state.isTracking(event)&&!event.isCanceled()){ dismiss(); returntrue; } } returnsuper.dispatchKeyEvent(event); }else{ returnsuper.dispatchKeyEvent(event); } } @Override publicbooleandispatchTouchEvent(MotionEventev){ if(mTouchInterceptor!=null&&mTouchInterceptor.onTouch(this,ev)){ returntrue; } returnsuper.dispatchTouchEvent(ev); } @Override publicbooleanonTouchEvent(MotionEventevent){ finalintx=(int)event.getX(); finalinty=(int)event.getY(); if((event.getAction()==MotionEvent.ACTION_DOWN) &&((x<0)||(x>=getWidth())||(y<0)||(y>=getHeight()))){ dismiss(); returntrue; }elseif(event.getAction()==MotionEvent.ACTION_OUTSIDE){ dismiss(); returntrue; }else{ returnsuper.onTouchEvent(event); } }
由于PopupView本身并没有重写Key和Touch事件的处理,所以如果没有包这个外层容器类,点击Back键或者外部区域是不会导致弹框消失的。
补充Case:弹窗不消失,但是事件向下传递
如上所述:
设置了PopupWindow的background,点击Back键或者点击弹窗的外部区域,弹窗就会dismiss.
相反,如果不设置PopupWindow的background,那么点击back键和点击弹窗的外部区域,弹窗是不会消失的.
那么,如果我想要一个效果,点击外部区域,弹窗不消失,但是点击事件会向下面的activity传递,比如下面是一个WebView,我想点击里面的链接等.
研究了半天,说是要给Window设置一个Flag,WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
看了源码,这个Flag的设置与否是由一个叫mNotTouchModal的字段控制,但是设置该字段的set方法被标记为@hide。
所以要通过反射的方法调用:
/** *Setwhetherthiswindowistouchmodalorifoutsidetoucheswillbesent *to *otherwindowsbehindit. * */ publicstaticvoidsetPopupWindowTouchModal(PopupWindowpopupWindow, booleantouchModal){ if(null==popupWindow){ return; } Methodmethod; try{ method=PopupWindow.class.getDeclaredMethod("setTouchModal", boolean.class); method.setAccessible(true); method.invoke(popupWindow,touchModal); } catch(Exceptione){ e.printStackTrace(); } }
然后在程序中:
UIUtils.setPopupWindowTouchModal(popupWindow,false);
该popupWindow外部的事件就可以传递给下面的Activity了。
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持毛票票。