首先写这个功能的初衷是因为公司项目上有很多弹出框的样式,类似于这样的
![](https://img.haomeiwen.com/i1822424/829f5caecdaffa2a.png)
![](https://img.haomeiwen.com/i1822424/6e5df9c9ac86b7b6.png)
去Github上搜了一圈发现没有比较通用、简单的库,大多数都封装了很多样式,比如类似于Dialog之类的,我觉得Dialog就用Dialog写就好了啊,没有必要用PopupWindow来写啊。
但是像这种简单的UI用的还是很多的,于是乎决定自己写一个简单的
所以便有了简单通用并且可以高度定制的Wpopup
![](https://img.haomeiwen.com/i1822424/e2148fb78e22dec7.gif)
![](https://img.haomeiwen.com/i1822424/1a6c03c726dc4c3a.gif)
首先决定都要有什么功能
- 基本的PopupWindow的功能要有
- 自动根据锚点View来设置弹出位置(也可以手动设置位置)
- 可以根据用户手指点击位置来自动弹出(也可以手动设置位置)
- 有一个通用的、基础的UI,必须可以高度定制
- 如果想要自定义UI那也必须简单
开始实现
基本的PopupWindow的功能要有
这个比较简单,只需要封装好一些方法到BasePopup
即可。比如:
- 设置contentView
- 设置背景变暗
- 设置点击外部不能dismiss
- ...
说起来比较简单,其实除了第一个,剩下的都很坑
自动根据锚点View来设置弹出位置(也可以手动设置位置)
这个功能是个小难点,因为平时我们写PopupWindow的时候最后都是用的showAsDropDown
,但是这个方法缺点就是如果弹出的View比较宽,但是你的锚点View又在边缘,那么有可能就出现显示不齐全的情况,而且想要弹出到哪里还要自己计算偏移量,很是麻烦。
那我们是不是可以改进一下,不用showAsDropDown
,来改用showAtLocation
,这样一来,所有的参数都由我们自己来设置,那么这个方法就不会出现显示不全的情况。首先我们有两个需求
- 自动根据锚点View来设置弹出位置
- 根据锚点View手动设置弹出位置
对于这两个需求,首先我们要做的就是要拿到这个View的位置,那么什么时候拿最好呢?我考虑了一下ListView和RecyclerView的情况,那当然是show的时候传入View最好,因为item点击回调才知道是哪个View,所以这就简单了,我们计算一下位置即可。
showAtLocation
里的X和Y都是弹出View的左上角坐标
可以根据用户手指点击位置来自动弹出(也可以手动设置位置)
上面说根据锚点View来设置弹出位置比较好做,是因为只有一个View,但是根据手指位置弹出,谁也不知道你点击的是一个RecyclerView还是ListView,所以这里就重点说一下这两个。
想要让Rv/Lv也能设置为根据手指点击位置自动弹出,那么就必须拦截Rv/Lv的事件,具体如何拦截事件呢?
if (popParams.longClickView != null) {
// 判断是否是ListView或者GridView
if (popParams.longClickView!! is AbsListView) {
// 拦截点击事件获取坐标
(popParams.longClickView!! as AbsListView).setOnTouchListener { v, event ->
when (event.action) {
MotionEvent.ACTION_DOWN -> {
clickLocation[0] = event.rawX
clickLocation[1] = event.rawY
}
}
false
}
} else if (popParams.longClickView!! is RecyclerView) {
// 判断是不是RecyclerView 拦截点击事件获取坐标
(popParams.longClickView!! as RecyclerView).addOnItemTouchListener(object : RecyclerView.OnItemTouchListener {
override fun onTouchEvent(p0: RecyclerView, p1: MotionEvent) {
}
override fun onInterceptTouchEvent(p0: RecyclerView, event: MotionEvent): Boolean {
when (event.action) {
MotionEvent.ACTION_DOWN -> {
clickLocation[0] = event.rawX
clickLocation[1] = event.rawY
}
}
return false
}
override fun onRequestDisallowInterceptTouchEvent(p0: Boolean) {
}
})
} else
popParams.longClickView!!.setOnTouchListener(this)
}
必须在创建WPopup的时候就传入一个ClickView,让我来知道你想根据手指点击弹出的View是什么样的View。
因为如果在点击的时候传入ClickView,那么这个时候注册拦截事件就已经晚了
剩下的就和上面一样了,计算坐标,计算距离等等
有一个通用的、基础的UI,必须可以高度定制
这部分就是WPopup
首先搭建好一个弹出的View,这里面有两个部分,一个是弹出的列表(用RecyclerView编写),一个是小三角,这两部分的背景全部都用Drawable来编写,好处是体积小,并且可以自定义颜色,Rv的背景很简单,但是小三角就比较复杂,以下是Drawable画三角的代码
<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<!-- 正三角 -->
<item>
<rotate
android:fromDegrees="45"
android:pivotX="-40%"
android:pivotY="80%">
<shape android:shape="rectangle">
<size
android:width="16dp"
android:height="16dp" />
<solid android:color="#CC000000" />
</shape>
</rotate>
</item>
</layer-list>
这里就是一个正方形然后旋转的结果,但是变更颜色的时候很复杂,一般我们编写的Drawable是这样的:
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<solid android:color="#CC000000"/>
<corners android:radius="5dp"/>
</shape>
这种一般情况下想要变更颜色就可以(view.background as GradientDrawable).setColor(xxx)
,然鹅,我们的小三角是一个layer-list,而且多层嵌套,这种情况下想要改颜色,那么就只能这样:
val layerListDrawable = view.background as LayerDrawable
val rotateDrawable = layerListDrawable.getDrawable(0) as RotateDrawable
(rotateDrawable.rotateDrawable as GradientDrawable).setColor(xxx)
- 首先获得LayerDrawable
- 然后通过LayerDrawable.getDrawable(position)来获得下面的Drawable
- 再用获得的Drawable.XXXDrawable强制转换为GradientDrawable来设置颜色
虽然View写好了,但是我们弹出的时候是自动设置位置,而且这个小三角是动态添加到Rv上的,所以不管Rv多宽多长,小三角始终是在中间的位置。那么这个时候就必须要设置一下这个小三角的边距才可以。
而且我观察了一部分的APP,他们只有在上下弹出时候有小三角,左右弹出是没有的,所以我的WPopup也是这样设计的,只有上下才有,左右没有。(如果你们有意见,我也可以改23333)
要弹出的View已经画好了,那下面就是可以让用户自己定义这个View里所有的参数。
在设计框架的时候想到了Android原生的AlertDialog,AlertDialog就是用Builder模式
编写的,在Build里保存参数,然后在create()的时候传给Wpopup,这样就完成了所有的参数的传递,并且隐藏了所有的细节
这样一个简单又可以高度定制的WPopup就大功告成啦!
网友评论