最近项目中碰到了这么个需求,就是一个popwindow要随着软件键盘弹出与消失,首先看一下完成后的效果。
问题思考:
一,首先我要知道能够监听到PopupWindow的弹出和消失。
PopupWindow的消失很简单,PopupWindow提供了setOnDismissListener的监
PopupWindow的弹出可能需要自己写接口回调了。PopupWindow没有直接提供相应的监听。
我是这么实现的:
@Override
public void showAsDropDown(View anchor) {
super.showAsDropDown(anchor);
showPopupWindow();
}
@Override
public void showAsDropDown(View anchor, int xoff, int yoff, int gravity) {
super.showAsDropDown(anchor, xoff, yoff, gravity);
showPopupWindow();
}
@Override
public void showAsDropDown(View anchor, int xoff, int yoff) {
super.showAsDropDown(anchor, xoff, yoff);
showPopupWindow();
}
@Override
public void showAtLocation(View parent, int gravity, int x, int y) {
super.showAtLocation(parent, gravity, x, y);
showPopupWindow();
}
private void showPopupWindow() {
if (mShowingListener != null) {
mShowingListener.onShowing();
}
}
public interface ShowingListener {
public void onShowing();
}
private ShowingListener mShowingListener;
public void setShowingListener(ShowingListener showingListener) {
this.mShowingListener = showingListener;
}
PopupWindow虽然没有提供弹出的监听,但是它提供了
1.showAsDropDown(View anchor)在某View的下面显示
2.showAsDropDown(View anchor, int xoff, int yoff, int gravity)在某View的下面显示,后面的3个参数分别是x轴偏移量和Y轴偏移量和对齐方式
3.showAsDropDown(View anchor, int xoff, int yoff)在某View的下面显示,后面的2个参数分别是x轴偏移量和Y轴偏移量
4.showAtLocation(View parent, int gravity, int x, int y)3个参数分别是锚点View,对齐方式和x轴偏移量和Y轴偏移量
这4个弹出PopupWindow的方法,所以我们只需要定义回调接口,在这三个中调用回调方法就可以监听到Popwindow的弹出事件。
二,在弹出的时候自动弹出然键盘把PopupWindow顶上去,在消失的时候隐藏软件盘。
软件盘的弹出和隐藏相信大家会:
/**
* 多种隐藏软件盘方法的其中一种
*
* @param token
* 聚焦view的toen
*/
public void hideSoftInput(IBinder token) {
if (token != null) {
InputMethodManager im = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
im.hideSoftInputFromWindow(token,
InputMethodManager.HIDE_NOT_ALWAYS);
}
}
/**
* 聚焦的view
* @param view
*/
public void showSoftInput(View view) {
//1.得到InputMethodManager对象
InputMethodManager imm = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
//2.调用showSoftInput方法显示软键盘,其中view为聚焦的view组件
imm.showSoftInput(view, InputMethodManager.SHOW_FORCED);
}
好了我现在把代码写成了这样:
@Override
public void onClick(View v) {
if (myPopupWindow == null) {
myPopupWindow = new MyPopupWindow(this);
myPopupWindow.setShowingListener(new MyPopupWindow.ShowingListener() {
@Override
public void onShowing() {
showSoftInput(getCurrentFocus());
}
});
myPopupWindow.setOnDismissListener(new PopupWindow.OnDismissListener() {
@Override
public void onDismiss() {
hideSoftInput(getCurrentFocus().getWindowToken());
}
});
}
myPopupWindow.showAtLocation(findViewById(android.R.id.content), Gravity.BOTTOM, 0, 0);
}
补充一点我这个点击事件是设置在一个EditText上的,那个Hellon World其实是一个不可编辑EditText。
来来看一下效果:
什么鬼,完全不是我想要的效果好吧。为什么出现这种情况呢?首先我们要知道软键盘要把View顶上出首先就要求那个View聚焦,所以在PopupWindow的构造方法中加上
setFocusable(true);
是不是加上行代码就行了呢?还是不行的。我们还要知道Activity的windowSoftInputMode属性:
各值的含义:
1.stateUnspecified:软键盘的状态并没有指定,系统将选择一个合适的状态或依赖于主题的设置
2.stateUnchanged:当这个activity出现时,软键盘将一直保持在上一个activity里的状态,无论是隐藏还是显示
3.stateHidden:用户选择activity时,软键盘总是被隐藏
4.stateAlwaysHidden:当该Activity主窗口获取焦点时,软键盘也总是被隐藏的
5.stateVisible:软键盘通常是可见的
6.stateAlwaysVisible:用户选择activity时,软键盘总是显示的状态
7.adjustUnspecified:默认设置,通常由系统自行决定是隐藏还是显示
8.adjustResize:该Activity总是调整屏幕的大小以便留出软键盘的空间
9.adjustPan:当前窗口的内容将自动移动以便当前焦点从不被键盘覆盖和用户能总是看到输入内容的部分
我再把Activity的windowSoftInputMode设置成
android:windowSoftInputMode="adjustResize"
试试。
O(∩_∩)O哈哈~这个完全不是我想要的效果啊莫非是onDismiss()中的hideSoftInput(getCurrentFocus().getWindowToken());的方法没有效果吗?不是,是因为焦点那活儿被PoppupWindow抢走了,而PoppupWindow消失了所以是隐藏不掉的,那怎么办,不隐藏了?我们只能另辟蹊径了,看看上面关于windowSoftInputMode的属性有一个stateAlwaysHidden(当该Activity主窗口获取焦点时,软键盘也总是被隐藏的),那就试试它:
android:windowSoftInputMode="stateAlwaysHidden|adjustResize">
你会发现可以了:
三,貌似全部可以了,那就调调PopupWindow的样式,背景变暗和那个白色边框去掉什么的那。
1,背景变暗
/**
* 控制窗口背景的不透明度 *
*/
private void setWindowBackgroundAlpha(float alpha) {
Window window = ((Activity) mContext).getWindow();
WindowManager.LayoutParams layoutParams = window.getAttributes();
layoutParams.alpha = alpha;
window.setAttributes(layoutParams);
}
想要加渐变动画的孩纸自己取写啊!变暗的方法有了在哪里调用呢?当然PoppupWindow的那4个弹出方法和那个dismiss()方法中那。
private void initView(Context context) {
this.mContext = context;
this.mView = new LinearLayout(context);
this.mView.setBackgroundColor(Color.RED);
setWidth(ViewGroup.LayoutParams.MATCH_PARENT);
setHeight(getScreenSize()[1] / 4);
setFocusable(true);
setContentView(mView);
}
@Override
public void showAsDropDown(View anchor) {
super.showAsDropDown(anchor);
showPopupWindow();
}
@Override
public void showAsDropDown(View anchor, int xoff, int yoff, int gravity) {
super.showAsDropDown(anchor, xoff, yoff, gravity);
showPopupWindow();
}
@Override
public void showAsDropDown(View anchor, int xoff, int yoff) {
super.showAsDropDown(anchor, xoff, yoff);
showPopupWindow();
}
@Override
public void showAtLocation(View parent, int gravity, int x, int y) {
super.showAtLocation(parent, gravity, x, y);
showPopupWindow();
}
private void showPopupWindow() {
setWindowBackgroundAlpha(0.9f);
if (mShowingListener != null) {
mShowingListener.onShowing();
}
}
@Override
public void dismiss() {
setWindowBackgroundAlpha(1.0f);
super.dismiss();
}
/**
* 控制窗口背景的不透明度 *
*/
private void setWindowBackgroundAlpha(float alpha) {
Window window = ((Activity) mContext).getWindow();
WindowManager.LayoutParams layoutParams = window.getAttributes();
layoutParams.alpha = alpha;
window.setAttributes(layoutParams);
}
2,白色边框
setBackgroundDrawable(new ColorDrawable(0x00000000));
有没有人提出这样的疑问,我setBackgroundDrawable(null)也可以去掉白色边框,但是你会发现你设置为null之后,你无论是点击返回的按键还是点击外部区域都没有效果了(前提是你也setFocus(true)).
setBackgroundDrawable | setFocusable | setOutsideTouchable | 点击返回按钮 | 点击外部区域 |
colorDrawable | true | true | 关闭窗口 | 关闭弹窗 |
colorDrawable | true | false | 关闭窗口 | 关闭弹窗 |
colorDrawable | false | true | 退出当前Activity | 关闭弹窗 |
colorDrawable | false | false | 退出当前Activity | 不关闭弹窗 |
null | true | true | 无操作响应 | 不关闭弹窗 |
null | true | false | 无操作响应 | 不关闭弹窗 |
null | false | true | 退出当前Activity | 不关闭弹窗 |
null | false | false | 退出当前Activity | 不关闭弹窗 |
解释这个问题我们得看一下PopupWindow的源码:
/**
* 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, surfaceInset);
p.hasManualSurfaceInsets = true;
mPopupViewInitialLayoutDirectionInherited =
(mContentView.getRawLayoutDirection() == View.LAYOUT_DIRECTION_INHERIT);
mPopupWidth = p.width;
mPopupHeight = p.height;
}
我们看到在backgroundDrawable!=null的情况下,PopupWindow会以backgroundDrawable作为背景用contentView和backgroundDrawable生成一个PopupBackgroundView并返回,而如果在backgroundDrawable==null的情况下,则直接返回contentView。于是乎接着往下搜索,原来PopupBackgroundView是一个内部私有类继承至FrameLayout,且该类完成了对onKey和onTouch事件的分发处理。因为contentView我们并没有进行onKey和onTouch事件的分发处理,所以以在backgroundDrawable==null的情况下,即使PopupWindow获得屏幕焦点,PopupWindow也不能处理物理按键的点击事件,因此就算点击返回按钮也会没有任何反应,更别说外部点击事件的响应了。
嗯,就是这样,我们看看效果:
一切看起来没什么不好的呢?但是细心的同学可能看到(我还故意点了2次),那就是当我单击返回的时候,然键盘收回了但是PopupWidnow没有消失。这就引出问题思考4了。
四,如何再软键盘收回的时候隐藏PoppupWindow。
猜想一,竟然是点击物理按键返回隐藏的软键盘,我就在onBackPressed()的时候隐藏PopupWindow,如下:
@Override
public void onBackPressed() {
if (myPopupWindow !=null){
myPopupWindow.dismiss();
}
super.onBackPressed();
}
有没有觉得很奇怪,当软键盘弹出时这个方法并有被调用,那就是很明显事件被输入法拦截了,O(∩_∩)O哈哈~,这个方法宣告无效。
猜想二,监听软键盘的隐藏事件。可是我网上溜达了好久也没有发现谷歌有提供软键盘隐藏的监听接口(如果有同学知道麻烦告诉我一下)。那难道这个想法也要宣告失败吗?不是,常规的方法不能实现我们就想想其它的方法。
我发现软键盘弹出的时候布局会上移,那我们就从这里下手,监听整个布局的onLayoutChange(View v, int left, int top, int right, int bottom, int oldLeft, int oldTop, int oldRight, int oldBottom),然后我们设置一个软键盘弹起的阈值,当(oldBottom - bottom)>阈值就是软键盘弹起了,(oldBottom - bottom)<阈值就是软键盘隐藏了。
我是这样写的:
private void initSoftListener(){
findViewById(android.R.id.content).addOnLayoutChangeListener(new View.OnLayoutChangeListener() {
@Override
public void onLayoutChange(View v, int left, int top, int right, int bottom, int oldLeft, int oldTop, int oldRight, int oldBottom) {
//现在认为只要控件将Activity向上推的高度超过了1/3屏幕高,就认为软键盘弹起
if(oldBottom != 0 && bottom != 0 &&(oldBottom - bottom > getScreenSize()[1]/3)){
onOpenSoftInput();
}else if(oldBottom != 0 && bottom != 0 &&(bottom - oldBottom > getScreenSize()[1]/3)){
onCloseSoftInput();
}
}
});
}
/**
* 当软键盘打开时回调
*/
protected void onOpenSoftInput(){
}
/**
* 当软键盘关闭时回调
*/
protected void onCloseSoftInput(){
if (myPopupWindow != null && myPopupWindow.isShowing()) {
myPopupWindow.dismiss();
}
}
我们再看看效果:
嗯!蛮好的,貌似就是我们要的效果。说是貌似那就证明还是有问题滴!那就引出了思考五。
思考五:为什么红米手机不行呢?
红米手机的效果我没办法截图,但是确实是有问题的。软键盘把PopupWindow覆盖住了,没有顶上去。
所以我在PopupWindow的构造函数中加了这几句代码:
setSoftInputMode(PopupWindow.INPUT_METHOD_NEEDED);
setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE);
整个是这样的:
private void initView(Context context) {
this.mContext = context;
this.mView = new LinearLayout(context);
this.mView.setBackgroundColor(Color.RED);
setWidth(ViewGroup.LayoutParams.MATCH_PARENT);
setHeight(getScreenSize()[1] / 4);
setBackgroundDrawable(new ColorDrawable(0x00000000));
setOutsideTouchable(true);
setFocusable(true);
setSoftInputMode(PopupWindow.INPUT_METHOD_NEEDED);
setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE);
setContentView(mView);
}
只是设置了一下PopupWindow的软键盘的SoftInputMode(),妥妥的解决了问题。
项目代码地址:http://download.csdn.net/detail/baidu_34012226/9613454