不得不吐槽的Android PopupWindow的几个痛点(实现带箭头的上下文菜单遇到的坑)
说到PopupWindow,我个人感觉是又爱又恨,没有深入使用之前总觉得这个东西应该很简单,很好用,但是真正使用PopupWindow实现一些效果的时候总会遇到一些问题,但是即便是人家的api有问题,作为程序员也没有办法,只能去想办法去补救。
下面是我在使用过程中发现的关于PopupWindow的几个痛点:
痛点一:不设置背景就不能响应返回键和点击外部消失的,这个我已经有一篇文章进行分析过http://www.cnblogs.com/popfisher/p/5608717.html,这个我认为就是api留下的bug,有些版本里面修复了这个问题,感兴趣的可以多看看几个版本的源码,还可以看出Google是怎么修改的。
痛点二:showAsDropDown(View anchorView)方法使用也会遇到坑,如果不看api注释,会认为PopupWindow只能显示在anchorView的下面(与anchorView左下角对齐显示),但是看了方法注释之后发现此方法是可以让PopupWindow显示在anchorView的上面的(anchorView左上角对齐显示)。如果真这样,那实现自适应带箭头的上下文菜单不就很容易了么,事实证明还是会有些瑕疵。
痛点三:个人觉得api设计得不好使,不过这个只能怪自己对api理解不够深刻,不过下面几个api组合使用还是得介绍一下。
// 如果不设置PopupWindow的背景,有些版本就会出现一个问题:无论是点击外部区域还是Back键都无法dismiss弹框
popupWindow.setBackgroundDrawable(new ColorDrawable()); // setOutsideTouchable设置生效的前提是setTouchable(true)和setFocusable(false)
popupWindow.setOutsideTouchable(true); // 设置为true之后,PopupWindow内容区域 才可以响应点击事件
popupWindow.setTouchable(true); // true时,点击返回键先消失 PopupWindow
// 但是设置为true时setOutsideTouchable,setTouchable方法就失效了(点击外部不消失,内容区域也不响应事件)
// false时PopupWindow不处理返回键
popupWindow.setFocusable(false);
popupWindow.setTouchInterceptor(new View.OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
return false; // 这里面拦截不到返回键
}
});
将理论始终听起来很形象,通过实例可以让人更加印象深刻,第一点已经有文章介绍了,下面实现一个带箭头的上下文菜单体会一下痛点二和三,到底怎么个痛法。先上效果再上代码,代码里面的注释标注了痛点的地方。
上下文菜单效果图
默认向下弹出

下面空间不足时先上弹出

特例出现了,我希望第一排右边按钮点击时PopupWindow在下面,但是我失望了

虽然达不到我要的效果,但是作为学习资源还是不错的,下面贴出代码
import android.app.Activity;
import android.graphics.drawable.ColorDrawable;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewTreeObserver;
import android.widget.PopupWindow;
import android.widget.RelativeLayout;
import android.widget.Toast; public class TopBottomArrowPopupActivity extends Activity implements View.OnClickListener { private View mButton1;
private View mButton2;
private View mButton3;
private View mButton4;
private View mButton5;
private View mButton6;
private PopupWindow mCurPopupWindow; @Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_top_arrow_pos_window);
mButton1 = findViewById(R.id.buttion1);
mButton2 = findViewById(R.id.buttion2);
mButton3 = findViewById(R.id.buttion3);
mButton4 = findViewById(R.id.buttion4);
mButton5 = findViewById(R.id.buttion5);
mButton6 = findViewById(R.id.buttion6);
mButton1.setOnClickListener(this);
mButton2.setOnClickListener(this);
mButton3.setOnClickListener(this);
mButton4.setOnClickListener(this);
mButton5.setOnClickListener(this);
mButton6.setOnClickListener(this);
} @Override
public void onClick(View v) {
int id = v.getId();
switch (id) {
case R.id.buttion1:
mCurPopupWindow = showTipPopupWindow(mButton1, new View.OnClickListener() {
@Override
public void onClick(View v) {
Toast.makeText(getBaseContext(), "点击到弹窗内容", Toast.LENGTH_SHORT).show();
}
});
break;
case R.id.buttion2:
mCurPopupWindow = showTipPopupWindow(mButton2, new View.OnClickListener() {
@Override
public void onClick(View v) {
Toast.makeText(getBaseContext(), "点击到弹窗内容", Toast.LENGTH_SHORT).show();
}
});
break;
case R.id.buttion3:
mCurPopupWindow = showTipPopupWindow(mButton3, new View.OnClickListener() {
@Override
public void onClick(View v) {
Toast.makeText(getBaseContext(), "点击到弹窗内容", Toast.LENGTH_SHORT).show();
}
});
break;
case R.id.buttion4:
mCurPopupWindow = showTipPopupWindow(mButton4, new View.OnClickListener() {
@Override
public void onClick(View v) {
Toast.makeText(getBaseContext(), "点击到弹窗内容", Toast.LENGTH_SHORT).show();
}
});
break;
case R.id.buttion5:
showTipPopupWindow(mButton5, new View.OnClickListener() {
@Override
public void onClick(View v) {
Toast.makeText(getBaseContext(), "点击到弹窗内容", Toast.LENGTH_SHORT).show();
}
});
break;
case R.id.buttion6:
mCurPopupWindow = showTipPopupWindow(mButton6, new View.OnClickListener() {
@Override
public void onClick(View v) {
Toast.makeText(getBaseContext(), "点击到弹窗内容", Toast.LENGTH_SHORT).show();
}
});
break;
}
} public PopupWindow showTipPopupWindow(final View anchorView, final View.OnClickListener onClickListener) {
final View contentView = LayoutInflater.from(anchorView.getContext())
.inflate(R.layout.popuw_content_top_arrow_layout, null);
contentView.measure(View.MeasureSpec.UNSPECIFIED, View.MeasureSpec.UNSPECIFIED);
// 创建PopupWindow时候指定高宽时showAsDropDown能够自适应
// 如果设置为wrap_content,showAsDropDown会认为下面空间一直很充足(我以认为这个Google的bug)
// 备注如果PopupWindow里面有ListView,ScrollView时,一定要动态设置PopupWindow的大小
final PopupWindow popupWindow = new PopupWindow(contentView,
contentView.getMeasuredWidth(), contentView.getMeasuredHeight(), false); contentView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
popupWindow.dismiss();
onClickListener.onClick(v);
}
}); contentView.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
@Override
public void onGlobalLayout() {
// 自动调整箭头的位置
autoAdjustArrowPos(popupWindow, contentView, anchorView);
contentView.getViewTreeObserver().removeGlobalOnLayoutListener(this);
}
});
// 如果不设置PopupWindow的背景,有些版本就会出现一个问题:无论是点击外部区域还是Back键都无法dismiss弹框
popupWindow.setBackgroundDrawable(new ColorDrawable()); // setOutsideTouchable设置生效的前提是setTouchable(true)和setFocusable(false)
popupWindow.setOutsideTouchable(true); // 设置为true之后,PopupWindow内容区域 才可以响应点击事件
popupWindow.setTouchable(true); // true时,点击返回键先消失 PopupWindow
// 但是设置为true时setOutsideTouchable,setTouchable方法就失效了(点击外部不消失,内容区域也不响应事件)
// false时PopupWindow不处理返回键
popupWindow.setFocusable(false);
popupWindow.setTouchInterceptor(new View.OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
return false; // 这里面拦截不到返回键
}
});
// 如果希望showAsDropDown方法能够在下面空间不足时自动在anchorView的上面弹出
// 必须在创建PopupWindow的时候指定高度,不能用wrap_content
popupWindow.showAsDropDown(anchorView);
return popupWindow;
} private void autoAdjustArrowPos(PopupWindow popupWindow, View contentView, View anchorView) {
View upArrow = contentView.findViewById(R.id.up_arrow);
View downArrow = contentView.findViewById(R.id.down_arrow); int pos[] = new int[2];
contentView.getLocationOnScreen(pos);
int popLeftPos = pos[0];
anchorView.getLocationOnScreen(pos);
int anchorLeftPos = pos[0];
int arrowLeftMargin = anchorLeftPos - popLeftPos + anchorView.getWidth() / 2 - upArrow.getWidth() / 2;
upArrow.setVisibility(popupWindow.isAboveAnchor() ? View.INVISIBLE : View.VISIBLE);
downArrow.setVisibility(popupWindow.isAboveAnchor() ? View.VISIBLE : View.INVISIBLE); RelativeLayout.LayoutParams upArrowParams = (RelativeLayout.LayoutParams) upArrow.getLayoutParams();
upArrowParams.leftMargin = arrowLeftMargin;
RelativeLayout.LayoutParams downArrowParams = (RelativeLayout.LayoutParams) downArrow.getLayoutParams();
downArrowParams.leftMargin = arrowLeftMargin;
} @Override
public void onBackPressed() {
if (mCurPopupWindow != null && mCurPopupWindow.isShowing()) {
mCurPopupWindow.dismiss();
} else {
super.onBackPressed();
}
}
}
结束语
虽然不能完全把PopupWindow的问题描述清楚,但是只要知道有这些坑,以后写代码的时候就会多留意下,知道PopupWindow的那几个常用api相互组合会出现什么样的结果。坚持写文章不容易,但是感觉遇到的问题就应该记录下来,好记性不如烂笔头,时间长了可以通过文章记录的知识快速为自己找到问题的解决方法。
有需要源码可以点击下载地址https://github.com/PopFisher/SmartPopupWindow 上面还有关于PopupWindow的一些其他用法,遇到新的问题时会更新记录一下
思考:怎么使得PopupWindow可以实现点击外部可以消失,内容区域可以响应点击事件,同时还能拦截返回键?
不得不吐槽的Android PopupWindow的几个痛点(实现带箭头的上下文菜单遇到的坑)的更多相关文章
- android PopupWindow实现从底部弹出或滑出选择菜单或窗口
本实例弹出窗口主要是继承PopupWindow类来实现的弹出窗体,布局可以根据自己定义设计.弹出效果主要使用了translate和alpha样式实现,具体实习如下: 第一步:设计弹出窗口xml: &l ...
- Android PopupWindow Dialog 关于 is your activity running 崩溃详解
Android PopupWindow Dialog 关于 is your activity running 崩溃详解 [TOC] 起因 对于 PopupWindow Dialog 需要 Activi ...
- Android PopupWindow的使用和分析
Android PopupWindow的使用和分析 PopupWindow使用 PopupWindow这个类用来实现一个弹出框,可以使用任意布局的View作为其内容,这个弹出框是悬浮在当前activi ...
- Android PopupWindow的使用技巧(转)
Android PopupWindow的使用技巧 PopupWindow是Android上自定义弹出窗口,使用起来很方便. PopupWindow的构造函数为 public PopupWindow(V ...
- android开发之使用上下文菜单
android中的上下文菜单类似于PC上的鼠标右键单击,不同的是android上没有鼠标这一概念,更谈不上右键单击,在android中,一般是长按某个View,调出上下文菜单.与OptionsMenu ...
- android上下文菜单
XML: <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmln ...
- Android自定义View的实现方法,带你一步步深入了解View(四)
转载请注明出处:http://blog.csdn.net/guolin_blog/article/details/17357967 不知不觉中,带你一步步深入了解View系列的文章已经写到第四篇了,回 ...
- Android 的上下文菜单: Context Menu,registerForContextMenu(getListView())
概述: Android 的上下文菜单类似于 PC 上的右键菜单.当为一个视图注册了上下文菜单之后,长按(2 秒左右)这个视图对象就会弹出一个浮动菜单,即上下文菜单.任何视图都可以注册上下文菜单,不过, ...
- Android 上下文菜单实现
1.覆盖Activity的onCreateContenxtMenu()方法,调用Menu的add方法添加菜单项(MenuItem). 2.覆盖Activity的onContextItemSelecte ...
随机推荐
- VisualVM通过jstatd方式远程监控远程主机
配置好权限文件 [root@test bin]# cd $JAVA_HOME/bin [root@test bin]# vim jstatd.all.policy grant codebase &qu ...
- ASP.NET内置对象的总结
1. Response对象可形象的称之为响应对象,用于将数据从服务器发送回浏览器. 实例源码:链接: http://pan.baidu.com/s/1dDCKQ8x 密码: ihq0 2. Requ ...
- console的高级使用
1.console.table()用来表格化展示数据. var people = { zqz: { name: 'zhaoqize', age: 'guess?' }, wdx: { name: 'w ...
- [APUE]UNIX进程的环境(上)
一. 前言 本章将学习:当执行程序时,其main函数是如何被调用的,命令行参数是如何传送给执行程序的,典型的存储器布局是什么样式,如何分配另外的存储空间,进程如何使用环境变量,进程终止的不同方式等.另 ...
- MVC Core 网站开发(Ninesky) 2.1、栏目的前台显示(补充)
在2.1.栏目的前台显示中因右键没有添加视图把微软给鄙视了一下,后来有仔细研究了一下发现应该鄙视自己,其实这个功能是有的,是自己没搞清楚乱吐糟. 其实只要在NuGet中安装两个包(Microsoft. ...
- C#基础篇 - 理解委托和事件
1.委托 委托类似于C++中的函数指针(一个指向内存位置的指针).委托是C#中类型安全的,可以订阅一个或多个具有相同签名方法的函数指针.简单理解,委托是一种可以把函数当做参数传递的类型.很多情况下,某 ...
- WebAPi之SelfHost自创建证书启动Https疑难解惑及无法正确返回结果
前言 话说又来需求了,之前对于在SelfHost中需要嵌套页面并操作为非正常需求,这回来正常需求了,客户端现在加了https,老大过来说WebAPi访问不了了,这是什么情况,我去试了试,还真是这个情况 ...
- .net erp(办公oa)开发平台架构概要说明之表单设计器
背景:搭建一个适合公司erp业务的开发平台. 架构概要图: 表单设计开发部署示例图 表单设计开发部署示例说明1)每个开发人员可以自己部署表单设计至本地一份(当然也可以共用一套开发环境,但是如 ...
- UWP开发必备:常用数据列表控件汇总比较
今天是想通过实例将UWP开发常用的数据列表做汇总比较,作为以后项目开发参考.UWP开发必备知识点总结请参照[UWP开发必备以及常用知识点总结]. 本次主要讨论以下控件: GridView:用于显示数据 ...
- setCapture、releasCapture 浅析
1. setCapture 简介 setCapture可以将鼠标事件锁定在指定的元素上,当元素捕获了鼠标事件后,该事件只能作用在当前元素上. 以下情况会导致事件锁定失败: 当窗口失去焦点时,锁定的事件 ...