在本文中,我们将为您详细介绍androidPopupWindow点击外部和返回键消失的解决方法的相关知识,并且为您解答关于popupwindow点击外部不消失的疑问,此外,我们还会提供一些关于andr
在本文中,我们将为您详细介绍android PopupWindow点击外部和返回键消失的解决方法的相关知识,并且为您解答关于popupwindow点击外部不消失的疑问,此外,我们还会提供一些关于android PopupWindow、android popupwindow 大小问题、Android Popupwindow 拖动、Android PopupWindow 的使用的有用信息。
本文目录一览:- android PopupWindow点击外部和返回键消失的解决方法(popupwindow点击外部不消失)
- android PopupWindow
- android popupwindow 大小问题
- Android Popupwindow 拖动
- Android PopupWindow 的使用
android PopupWindow点击外部和返回键消失的解决方法(popupwindow点击外部不消失)
刚接手PopupWindow的时候,我们都可能觉得很简单,因为它确实很简单,不过运气不好的可能就会踩到一个坑:
点击PopupWindow最外层布局以及点击返回键PopupWindow不会消失
新手在遇到这个问题的时候可能会折腾半天,最后通过强大的网络找到一个解决方案,那就是跟PopupWindow设置一个背景
popupWindow.setBackgroundDrawable(drawable),这个drawable随便一个什么类型的都可以,只要不为空。
Demo地址:SmartPopupWindow_jb51.rar
下面从源码(我看的是android-22)上看看到底发生了什么事情导致返回键不能消失弹出框:
先看看弹出框显示的时候代码showAsDropDown,里面有个preparePopup方法。
public void showAsDropDown(View anchor,int xoff,int yoff,int gravity) { if (isShowing() || mContentView == null) { return; } registerForScrollChanged(anchor,xoff,yoff,gravity); mIsShowing = true; mIsDropdown = true; WindowManager.LayoutParams p = createPopupLayout(anchor.getwindowToken()); preparePopup(p); updateAboveAnchor(findDropDownPosition(anchor,p,gravity)); if (mHeightMode < 0) p.height = mLastHeight = mHeightMode; if (mWidthMode < 0) p.width = mLastWidth = mWidthMode; p.windowAnimations = computeAnimationResource(); invokePopup(p); }
再看preparePopup方法
/** * <p>Prepare the popup by embedding in into a new ViewGroup if the * background drawable is not null. If embedding is required,the layout * parameters' height is modified to take into account the background's * padding.</p> * * @param p the layout parameters of the popup's content view */ private void preparePopup(WindowManager.LayoutParams p) { if (mContentView == null || mContext == null || mWindowManager == null) { throw new IllegalStateException("You must specify a valid content view by " + "calling setContentView() before attempting to show the popup."); } if (mBackground != null) { final ViewGroup.LayoutParams layoutParams = mContentView.getLayoutParams(); int height = ViewGroup.LayoutParams.MATCH_PARENT; if (layoutParams != null && layoutParams.height == ViewGroup.LayoutParams.WRAP_CONTENT) { height = ViewGroup.LayoutParams.WRAP_CONTENT; } // when a background is available,we embed the content view // within another view that owns the background drawable PopupViewContainer popupViewContainer = new PopupViewContainer(mContext); PopupViewContainer.LayoutParams listParams = new PopupViewContainer.LayoutParams( ViewGroup.LayoutParams.MATCH_PARENT,height ); popupViewContainer.setBackground(mBackground); popupViewContainer.addView(mContentView,listParams); mPopupView = popupViewContainer; } else { mPopupView = mContentView; } mPopupView.setElevation(mElevation); mPopupViewInitialLayoutDirectionInherited = (mPopupView.getRawLayoutDirection() == View.LAYOUT_DIRECTION_INHERIT); mPopupWidth = p.width; mPopupHeight = p.height; }
上面可以看到mBackground不为空的时候,会PopupViewContainer作为mContentView的Parent,下面看看PopupViewContainer到底干了什么
private class PopupViewContainer extends FrameLayout { private static final String TAG = "PopupWindow.PopupViewContainer"; public PopupViewContainer(Context context) { super(context); } @Override protected int[] onCreateDrawableState(int extraSpace) { if (mAboveAnchor) { // 1 more needed for the above anchor state final int[] drawableState = super.onCreateDrawableState(extraSpace + 1); View.mergeDrawableStates(drawableState,ABOVE_ANCHOR_STATE_SET); return drawableState; } else { return super.onCreateDrawableState(extraSpace); } } @Override public boolean dispatchKeyEvent(KeyEvent event) { // 这个方法里面实现了返回键处理逻辑,会调用dismiss if (event.getKeyCode() == KeyEvent.KEYCODE_BACK) { if (getKeydispatcherState() == null) { return super.dispatchKeyEvent(event); } if (event.getAction() == KeyEvent.ACTION_DOWN && event.getRepeatCount() == 0) { KeyEvent.dispatcherState state = getKeydispatcherState(); if (state != null) { state.startTracking(event,this); } return true; } else if (event.getAction() == KeyEvent.ACTION_UP) { KeyEvent.dispatcherState state = getKeydispatcherState(); if (state != null && state.isTracking(event) && !event.isCanceled()) { dismiss(); return true; } } return super.dispatchKeyEvent(event); } else { return super.dispatchKeyEvent(event); } } @Override public boolean dispatchTouchEvent(MotionEvent ev) { if (mTouchInterceptor != null && mTouchInterceptor.onTouch(this,ev)) { return true; } return super.dispatchTouchEvent(ev); } @Override public boolean onTouchEvent(MotionEvent event) { // 这个方法里面实现点击消失逻辑 final int x = (int) event.getX(); final int y = (int) event.getY(); if ((event.getAction() == MotionEvent.ACTION_DOWN) && ((x < 0) || (x >= getWidth()) || (y < 0) || (y >= getHeight()))) { dismiss(); return true; } else if (event.getAction() == MotionEvent.ACTION_OUTSIDE) { dismiss(); return true; } else { return super.onTouchEvent(event); } } @Override public void sendAccessibilityEvent(int eventType) { // clinets are interested in the content not the container,make it event source if (mContentView != null) { mContentView.sendAccessibilityEvent(eventType); } else { super.sendAccessibilityEvent(eventType); } } }
看到上面红色部分的标注可以看出,这个内部类里面封装了处理返回键退出和点击外部退出的逻辑,但是这个类对象的构造过程中(preparePopup方法中)却有个mBackground != null的条件才会创建
而mBackground对象在setBackgroundDrawable方法中被赋值,看到这里应该就明白一切了。
/** * Specifies the background drawable for this popup window. The background * can be set to {@code null}. * * @param background the popup's background * @see #getBackground() * @attr ref android.R.styleable#PopupWindow_popupBackground */ public void setBackgroundDrawable(Drawable background) { mBackground = background; // 省略其他的 }
setBackgroundDrawable方法除了被外部调用,构造方法中也会调用,默认是从系统资源中取的
/** * <p>Create a new,empty,non focusable popup window of dimension (0,0).</p> * * <p>The popup does not provide a background.</p> */ public PopupWindow(Context context,AttributeSet attrs,int defStyleAttr,int defStyleRes) { mContext = context; mWindowManager = (WindowManager)context.getSystemService(Context.WINDOW_SERVICE); final TypedArray a = context.obtainStyledAttributes( attrs,R.styleable.PopupWindow,defStyleAttr,defStyleRes); final Drawable bg = a.getDrawable(R.styleable.PopupWindow_popupBackground); mElevation = a.getDimension(R.styleable.PopupWindow_popupElevation,0); mOverlapAnchor = a.getBoolean(R.styleable.PopupWindow_overlapAnchor,false); final int animstyle = a.getResourceId(R.styleable.PopupWindow_popupAnimationStyle,-1); mAnimationStyle = animstyle == R.style.Animation_PopupWindow ? -1 : animstyle; a.recycle(); setBackgroundDrawable(bg); }
有些版本没有,android6.0版本preparePopup如下:
/** * Prepare the popup by embedding it into a new ViewGroup if the background * drawable is not null. If embedding is required,the layout parameters' * height is modified to take into account the background's padding. * * @param p the layout parameters of the popup's content view */ private void preparePopup(WindowManager.LayoutParams p) { if (mContentView == null || mContext == null || mWindowManager == null) { throw new IllegalStateException("You must specify a valid content view by " + "calling setContentView() before attempting to show the popup."); } // The old decor view may be transitioning out. Make sure it finishes // and cleans up before we try to create another one. if (mDecorView != null) { mDecorView.cancelTransitions(); } // When a background is available,we embed the content view within // another view that owns the background drawable. if (mBackground != null) { mBackgroundView = createBackgroundView(mContentView); mBackgroundView.setBackground(mBackground); } else { mBackgroundView = mContentView; } mDecorView = createDecorView(mBackgroundView); // The background owner should be elevated so that it casts a shadow. mBackgroundView.setElevation(mElevation); // We may wrap that in another view,so we'll need to manually specify // the surface insets. final int surfaceInset = (int) Math.ceil(mBackgroundView.getZ() * 2); p.surfaceInsets.set(surfaceInset,surfaceInset,surfaceInset); p.hasManualSurfaceInsets = true; mPopupViewInitialLayoutDirectionInherited = (mContentView.getRawLayoutDirection() == View.LAYOUT_DIRECTION_INHERIT); mPopupWidth = p.width; mPopupHeight = p.height; }
这里实现返回键监听的代码是mDecorView = createDecorView(mBackgroundView),这个并没有受到那个mBackground变量的控制,所以这个版本应该没有我们所描述的问题,感兴趣的可以自己去尝试一下
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持编程小技巧。
android PopupWindow
1
PopupWindow window = new PopupWindow(activity);//的到对象
2
window.setBackgroundDrawable(activity.getResources().getDrawable(R.drawable.configlistviewbg));//设置背景
//设置PopupWindow显示和隐藏时的动画
window.setAnimationStyle(R.style.AnimationFade);
View view=LayoutInflater.from(activity).inflate(joker.housemananger.activity.R.layout.addhouseactivity_popuwindow, null,true);//将popu的布局文件实例出
long wid=activity.getWindowManager().getDefaultDisplay().getWidth(); //的到屏幕的宽
long hd=activity.getWindowManager().getDefaultDisplay().getHeight();//得到屏幕的高
View call=view.findViewById(R.id.addhouse_popuwindow_bt_callphone);//以下设置 布局文件上控件的监听器,因为这个类就继承了OnclickListener接口就直接this了
call.setOnClickListener(this);
View mess=view.findViewById(R.id.addhouse_popuwindow_bt_message);
mess.setOnClickListener(this);
View cancel=view.findViewById(R.id.addhouse_popuwindow_bt_cancel);
cancel.setOnClickListener(this);
window.setWidth(Integer.parseInt(wid+""));//设置popupWindow高度
window.setHeight(Integer.parseInt(hd/2+"")-100);//设置高度
window.setContentView(view);//设置popupwindows的内容 样子
//设置PopupWindow外部区域是否可触摸
window.setOutsideTouchable(false);
window.setFocusable(true); //设置 popupwindows获得焦点,以此来响应 返回键
3
windows.showAsDropDown(View) //显示windows,从上面向下显示
windows.showAsDropDown(View,0,0) //同第一个,加上两个偏移量,横纵轴的偏移
windows.showAtLocation(view, Gravity.BOTTOM, 0, 0); //设置popupwindow出现的位置,参1 设置出现在哪个view内,参2出现的位置,冲对面展开,参3参4 横纵轴的偏移
4
popupwindow.dismiss(); //关闭popupwindows
5 popupwindow 的动画 style 的 item 的 syle 的值
<item name="android:windowEnterAnimation">@anim/anim_down_in</item>
<item name="android:windowExitAnimation">@anim/anim_down_out</item>
android popupwindow 大小问题
由于popupWindow 的视图是从xml 中渲染的,关于popupwindow的大小碰到了一个问题:跟想象中的大小不一样。于是网上查了一点资料,没找到啥干货,不过在这篇文章中讲了一点。
http://www.jcodecraeer.com/a/anzhuokaifa/androidkaifa/2014/0702/1627.html
new PopupWindow(pop_view, ViewGroup.LayoutParams.WRAP_CONTENT,ViewGroup.LayoutParams.WRAP_CONTENT);
这里的WRAP_CONTENT可以换成fill_parent 也可以是具体的数值,它是指PopupWindow的大小,也就是contentview的大小,注意popupwindow根据这个大小显示你的View,如果你的View本身是从xml得到的,那么xml的第一层view的大小属性将被忽略。相当于popupWindow的width和height属性直接和第一层View相对应。
popupWindow 设置为WRAP_CONTENT ,我想得到的是一个宽150dip 高80dip的popupwindow,需要额外加一层。LinearLayout ,这个LinearLayout 的layout_width和layout_height为任意值。而我们真正想显示的View 放在第二层,并且 android:layout_width="150.0dip" android:layout_height="80.0dip"
这样就可以解决大小问题。
但是我们的pop还需要控制显示位置,要控制这个pop 相对于某个view 来显示,可以使用以下方式。
int[] location = new int[2];
v.getLocationOnScreen(location);//获得view 的位置
pw.getContentView().measure(0,0);
int width = pw.getContentView().getMeasuredWidth();
int height = pw.getContentView().getMeasuredHeight();//这三行代码在pop 显示之前获得它的大小,由于pop 是设置的wrap_content
pw.showAtLocation(v, Gravity.NO_GRAVITY,location[0]-width,location[1]-(height-v.getHeight())/2);//显示在view 的左中位置。
后记:项目算是告一段落了,也学到了不少东西。
Android Popupwindow 拖动
关于View的拖动你们应该比较了解了,好比对一个控件IamgeView拖动,或者一个视图View拖动,实现方式也很容易,继承OnTouchListener接口,而后重写onTouch方法,在触屏事件进行处理便可。可是Popupwindow如何实现拖动呢,咱们都知道它和普通的View不同,由于它不是继承于View类的,可是它的实现倒是和View密切相关的,由于咱们都知道Android视图的显示都是由View来处理的,因此必定离不开它。从Popupwindow的实现就能够看出来,ide
import com.android.internal.R; import android.content.Context; import android.content.res.Resources; import android.content.res.TypedArray; import android.graphics.PixelFormat; import android.graphics.Rect; import android.graphics.drawable.Drawable; import android.graphics.drawable.StateListDrawable; import android.os.Build; import android.os.IBinder; import android.util.AttributeSet; import android.view.Gravity; import android.view.KeyEvent; import android.view.MotionEvent; import android.view.View; import android.view.View.OnTouchListener; import android.view.ViewGroup; import android.view.ViewTreeObserver; import android.view.ViewTreeObserver.OnScrollChangedListener; import android.view.WindowManager; import java.lang.ref.WeakReference;上面是它的导包状况,基本上不是和View相关,就是和绘图相关。所以关于Popupwindow的拖动这一块,也和View有联系。首先看一下它的API,看一看有没有和View移动、变化相关的方法,果真在最后有几个update()方法,以下:
update()方法用来更新Popupwindow的位置和大小的,那么问题就好解决了。看代码:布局
package com.example.drag_and_drop_movablepopupwindow; import android.support.v7.app.ActionBaractivity; import android.graphics.Color; import android.os.Bundle; import android.view.Gravity; import android.view.LayoutInflater; import android.view.Menu; import android.view.MenuItem; import android.view.MotionEvent; import android.view.View; import android.view.View.OnTouchListener; import android.view.ViewGroup.LayoutParams; import android.widget.Button; import android.widget.PopupWindow; import android.widget.TextView; public class MainActivity extends ActionBaractivity { private Button btnopenPopup; private int mCurrentX; private int mCurrentY; private PopupWindow mPopup; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); btnopenPopup = (Button) findViewById(R.id.openpopup); btnopenPopup.setonClickListener(new Button.OnClickListener() { @Override public void onClick(View arg0) { creatPopubWindow_1(); } }); } /** * 1 */ private void creatPopubWindow_1() { LayoutInflater layoutInflater = (LayoutInflater) getBaseContext() .getSystemService(LAYOUT_INFLATER_SERVICE); View popupView = layoutInflater.inflate(R.layout.popup, null); final PopupWindow popupWindow = new PopupWindow(popupView, 200, 200); Button btndismiss = (Button) popupView.findViewById(R.id.dismiss); btndismiss.setonClickListener(new Button.OnClickListener() { @Override public void onClick(View v) { popupWindow.dismiss(); } }); popupWindow.showAsDropDown(btnopenPopup, 50, 50); popupView.setonTouchListener(new OnTouchListener() { int orgX, orgY; int offsetX, offsetY; @Override public boolean onTouch(View v, MotionEvent event) { switch (event.getAction()) { case MotionEvent.ACTION_DOWN: orgX = (int) event.getX(); orgY = (int) event.getY(); break; case MotionEvent.ACTION_MOVE: offsetX = (int) event.getRawX() - orgX; offsetY = (int) event.getRawY() - orgY; popupWindow.update(offsetX, offsetY, -1, -1, true); break; } return true; } }); } }
效果如图:ui
首先对Popupwindow设置触摸事件,而后在回调方法中进行计算,若是手指拖动了Popupwindow,那么就调用update()方法来更新它的位置。有些同窗可能不太理解参数-1是什么意思,在上面的API中,写明的是宽和高,这里怎么变成-1了呢,看一下Popupwindow源代码就明白了。this
/** * <p>Updates the position and the dimension of the popup window. Width and * height can be set to -1 to update location only. Calling this function * also updates the window with the current popup state as * described for {@link #update()}.</p> * * @param x the new x location * @param y the new y location * @param width the new width, can be -1 to ignore * @param height the new height, can be -1 to ignore * @param force reposition the window even if the specified position * already seems to correspond to the LayoutParams */ public void update(int x, int y, int width, int height, boolean force) { if (width != -1) { mLastWidth = width; setWidth(width); } if (height != -1) { mLastHeight = height; setHeight(height); } if (!isShowing() || mContentView == null) { return; } WindowManager.LayoutParams p = (WindowManager.LayoutParams) mPopupView.getLayoutParams(); boolean update = force; final int finalWidth = mWidthMode < 0 ? mWidthMode : mLastWidth; if (width != -1 && p.width != finalWidth) { p.width = mLastWidth = finalWidth; update = true; } final int finalHeight = mHeightMode < 0 ? mHeightMode : mLastHeight; if (height != -1 && p.height != finalHeight) { p.height = mLastHeight = finalHeight; update = true; } if (p.x != x) { p.x = x; update = true; } if (p.y != y) { p.y = y; update = true; } final int newAnim = computeAnimationResource(); if (newAnim != p.windowAnimations) { p.windowAnimations = newAnim; update = true; } final int newFlags = computeFlags(p.flags); if (newFlags != p.flags) { p.flags = newFlags; update = true; } if (update) { setLayoutDirectionFromAnchor(); mWindowManager.updateViewLayout(mPopupView, p); } }前两个if判断已经说得很清楚了,若是参数是-1的话,就不改变Popupwindow的大小了,由于咱们只是移动位置,因此才这样写。那关于Popupwindow的移动最后是怎么实现的呢,能够看出就是调用WindowManager的updateViewLayout()方法,这个方法在WindowManager中并无实现,它是ViewManager接口里面的方法,WindowManager继承了ViewManager。说到ViewManager,它里面定义的方法都很经常使用,看代码:
/** Interface to let you add and remove child views to an Activity. To get an instance * of this class, call {@link android.content.Context#getSystemService(java.lang.String) Context.getSystemService()}. */ public interface ViewManager { /** * Assign the passed LayoutParams to the passed View and add the view to the window. * <p>Throws {@link android.view.WindowManager.BadTokenException} for certain programming * errors, such as adding a second view to a window without removing the first view. * <p>Throws {@link android.view.WindowManager.InvaliddisplayException} if the window is on a * secondary {@link display} and the specified display can't be found * (see {@link android.app.Presentation}). * @param view The view to be added to this window. * @param params The LayoutParams to assign to view. */ public void addView(View view, ViewGroup.LayoutParams params); public void updateViewLayout(View view, ViewGroup.LayoutParams params); public void removeView(View view); }这下你们应该明了,咱们常常用的addView、removeView方法就是在这里面定义的,那么谁去实现呢?就是Layout控件,好比LinearLayout、RelativeLayout等,因此咱们刚才用的updateViewLayout()方法也是在xml布局文件中的layout定义好的。
Android PopupWindow 的使用
下面是核心代码:
package com.android.tutor; import android.app.Activity; import android.content.Context; import android.os.Bundle; import android.view.Gravity; import android.view.LayoutInflater; import android.view.View; import android.view.View.OnClickListener; import android.view.ViewGroup.LayoutParams; import android.widget.Button; import android.widget.PopupWindow; public class PopupWindowDemo extends Activity implements OnClickListener{ private Button btn; public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); btn = (Button)findViewById(R.id.btn); btn.setOnClickListener(this); } @Override public void onClick(View v) { Context mContext = PopupWindowDemo.this; if (v.getId() == R.id.btn) { LayoutInflater mLayoutInflater = (LayoutInflater) mContext .getSystemService(LAYOUT_INFLATER_SERVICE); View music_popunwindwow = mLayoutInflater.inflate( R.layout.music_popwindow, null); PopupWindow mPopupWindow = new PopupWindow(music_popunwindwow, LayoutParams.FILL_PARENT, LayoutParams.WRAP_CONTENT); mPopupWindow.showAtLocation(findViewById(R.id.main), Gravity.RIGHT|Gravity.BOTTOM, 0, 0); } } }
package com.android.tutor; import java.util.Timer; import java.util.TimerTask; import android.app.Activity; import android.content.Context; import android.os.Bundle; import android.os.Handler; import android.os.Message; import android.view.Gravity; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup.LayoutParams; import android.widget.PopupWindow; public class PopupWindowDemo extends Activity{ private Handler mHandler = new Handler(){ public void handleMessage(Message msg) { switch (msg.what) { case 1: showPopupWindow(); break; } }; }; public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); //create the timer Timer timer = new Timer(); timer.schedule(new initPopupWindow(), 100); } private class initPopupWindow extends TimerTask{ @Override public void run() { Message message = new Message(); message.what = 1; mHandler.sendMessage(message); } } public void showPopupWindow() { Context mContext = PopupWindowDemo.this; LayoutInflater mLayoutInflater = (LayoutInflater) mContext .getSystemService(LAYOUT_INFLATER_SERVICE); View music_popunwindwow = mLayoutInflater.inflate( R.layout.music_popwindow, null); PopupWindow mPopupWindow = new PopupWindow(music_popunwindwow, LayoutParams.FILL_PARENT, LayoutParams.WRAP_CONTENT); mPopupWindow.showAtLocation(findViewById(R.id.main), Gravity.CENTER, 0, 0); } }
效果如下图:

我们今天的关于android PopupWindow点击外部和返回键消失的解决方法和popupwindow点击外部不消失的分享已经告一段落,感谢您的关注,如果您想了解更多关于android PopupWindow、android popupwindow 大小问题、Android Popupwindow 拖动、Android PopupWindow 的使用的相关信息,请在本站查询。
本文标签: