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了。
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持毛票票。