今天看啥  ›  专栏  ›  z-xJie

Android PopupWindow工具类 (已解决7.1以上showAsDropDown显示MATCH的PopWindow时覆盖控件的问题)

z-xJie  · CSDN  ·  · 2020-01-01 00:00

前言

PopWindow工具类
  • 1
  • 1

import android.app.Activity
import android.content.Context
import android.graphics.drawable.BitmapDrawable
import android.os.Build
import android.view.Display
import android.view.Gravity
import android.view.View
import android.view.WindowManager
import android.widget.PopupWindow

/***
 * PopWindowUtil
 * Created by zxjie on 2021/5/17.
 * address:bailingkeji
 * describe:使用时请先调用init进行初始化,之后调用showPopWindow进行显示,该工具类采用单例设计模式
 * link:https://blog.csdn.net/weixin_46603990/article/details/116987681
 * https://juejin.cn/post/6963513638783205406/
 * example: - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
 *          |   -实例化对象-
 *          |   private val popWindowUtil = PopWindowUtil
 *          |
 *          |   -初始化PopWindow-
 *          |   --设置固定大小
 *          |   popWindowUtil.init(this,R.layout.pop_alert_two,295F,135F,null)
 *          |   --适应屏幕最大化
 *          |   popWindowUtil.init(this,R.layout.pop_alert_two,PopWindowUtil.MATCH,PopWindowUtil.MATCH,null)
 *          |   --基于屏幕设置宽高,,Restraint中的参数只有在前者为CHANGE时才会生效,公式为 屏幕宽高-Restraint.width/height
 *          |   popWindowUtil.init(this,R.layout.pop_alert_two,PopWindowUtil.CHANGE,PopWindowUtil.CHANGE,Restraint(20,60))
 *          |   --组合使用,宽置满,高基于屏幕设置
 *          |   popWindowUtil.init(this,R.layout.pop_alert_two,PopWindowUtil.MATCH,PopWindowUtil.CHANGE,Restraint(0,60))
 *          |
 *          |   -获取所用到的控件
 *          |   val btn_ok = popWindowUtil.getViewById(R.id.btn_ok) as TextView
 *          |
 *          |   -设置点击窗口外边,窗口消失,默认为false
 *          |   popWindowUtil.outSideTouchable(true)
 *          |
 *          |   -获取焦点,默认为false
 *          |   popWindowUtil.itemClickable(true)
 *          |
 *          |   -使用默认的阴影效果,默认为false
 *          |   popWindowUtil.shadowShowAble(true)
 *          |
 *          |   -设置PopWindow消失监听
 *          |   popWindowUtil.dismissListener = {
 *          |         -自定义背景
 *          |      }
 *          |
 *          |   -弹出PopWindow 注意:弹出的PopWindow永远不会跃出屏幕
 *          |   --基于整个屏幕居中弹出,向下偏移10dp
 *          |   popWindowUtil.showPopWindow(toolbar,true, Excursion(Gravity.CENTER,0,10),null)
 *          |   --基于整个屏幕在左上弹出,并向左偏移10dp 注意:这时候PopWindow已经显示在屏幕最左侧了,所以这个-10是无效的
 *          |   popWindowUtil.showPopWindow(toolbar,true, Excursion(Gravity.LEFT or Gravity.TOP,-10,0),null)
 *          |   --基于整个屏幕从下方滑动弹出,并在tabLayout上方
 *          |   popWindowUtil.showPopWindow(tab_layout, true, Excursion(Gravity.BOTTOM, 0, DensityUtil.px2dip(this, (tab_layout.layoutParams.height+nbHeight-4) * 1F)),
 *          |   --注意偏移的方向并不是正右下,负左上,它是根据位置约束来决定的,位置约束为BOTTOM,那么就是正上,负下,如果为TOP,就是正下,负上,x轴同理
 *          |   --不建议使用 基于toolbar控件的左下弹出 注意:为false时位置约束(Gravity.CENTER)将会失效
 *          |   popWindowUtil.showPopWindow(toolbar,false, Excursion(Gravity.CENTER,0,0),null)
 *          |
 *          |   -建议设置PopWindow弹出时按返回键关闭PopWindow,而不是关闭Activity,注意:onBackPressed()方法只有在Activity中才能重写
 *          |   override fun onBackPressed() {
 *          |        if (!popWindowUtil.disMiss()) finish()
 *          |        }
 *          |
 *          |   -关闭PopWindow
 *          |   popWindowUtil.disMiss()
 *          |
 *          |   -销毁PopWindow所使用的的对象,在onDestroy调用
 *          |   popWindowUtil.destroy()
 *          |
 *          |   -当工具类提供的方法不满足你的需求时可以调用以下方法获取PopWindow对象
 *          |   popWindowUtil.getPopWindow()
 *          |- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
 */
object PopWindowUtil {
    private var winHeight: Int = 0
    private var winWidth: Int = 0
    private var view: View? = null
    private var popupWindow: PopupWindow? = null
    private var context: Context? = null
    private var canDown: Boolean = false
    private var canClick: Boolean = false
    private var canShow: Boolean = false
    private var ERROR: Boolean = false
    const val MATCH = -100F
    const val CHANGE = -1000F


    /***
     * @param context 上下文
     * @param layoutId 资源文件
     * @param width PopWindow所需宽/dp 为MATCH时全屏 为CHANGE时可设置距屏幕的距离
     * @param height PopWindow所需高/dp 为MATCH时全屏 为CHANGE时可设置距屏幕的距离
     * @param restraint 该参数可为空,宽高非CHANGE情况下无效,当width为CHANGE时Restraint.width生效,即PopWindow距屏幕左右的距离/dp,
     * 当height为CHANGE时Restraint.height生效,即PopWindow距屏幕上下的距离/dp
     */
    fun init(context: Context, layoutId: Int, width: Float, height: Float, restraint: Restraint?) {
        this.context = context
        if (isExist()) {
            popupWindow!!.dismiss()
            popupWindow = null
        }
        val wm = context.getSystemService(Context.WINDOW_SERVICE) as WindowManager
        val display: Display = wm.defaultDisplay
        winHeight = display.height
        winWidth = display.width
        view = View.inflate(context, layoutId, null)
        val useWidth = when (width) {
            MATCH -> winWidth
            CHANGE -> (winWidth - dip2px(restraint!!.width))
            else -> dip2px(width)
        }
        ERROR = height == MATCH
        val useHeight = when {
            height == MATCH -> winHeight
            width == CHANGE -> (winHeight - dip2px(restraint!!.height))
            else -> dip2px(height)
        }
        popupWindow = PopupWindow(view, useWidth, useHeight)
    }

    /**
     * @param id 要获取的id
     * @return 返回一个view对象,需要强转为所需控件类型
     */
    fun getViewById(id: Int): View? =
        if (isExist()) view!!.findViewById(id)
        else throw NullPointerException("查找控件失败,请初始化")

    /**
     * @param location 一个View对象,用来约束PopWindow的位置
     * @param global 是否基于屏幕弹出 true基于屏幕弹出, false基于location控件的左下方弹出, 不建议使用false
     * @param excursion 参数一是位置约束,global为false时位置约束将会失效,参数二传入x轴的偏移/dp,,参数三传入y轴的偏移/dp,注意偏移的方向并不是正右下,负左上,
     * 它是根据位置约束来决定的,位置约束为BOTTOM,那么就是正上,负下,如果为TOP,就是正下,负上,x轴同理
     * @param anim 动画效果,选择性传入,不需要时传入空
     */
    fun showPopWindow(location: View, global: Boolean, excursion: Excursion, anim: Int?) {
        val disPose = disPose(location)
        val x = dip2px(excursion.x.toFloat())
        val y = dip2px(excursion.y.toFloat())
        if (isExist()) {
            if (anim != null) popupWindow?.animationStyle = anim
            popupWindow?.isFocusable = canClick
            if (canDown) popupWindow?.setBackgroundDrawable(BitmapDrawable())
            popupWindow?.isOutsideTouchable = canDown
            if (canShow) setBackgroundAlpha(0.5F)
            popupWindow!!.setOnDismissListener {
                if (canShow) setBackgroundAlpha(1F)
                dismissListener?.invoke()
            }
            if (global) {
                if (ERROR) popupWindow?.height = winHeight - disPose
                popupWindow?.showAtLocation(location, excursion.gravity, x, y)
            } else {
                if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && ERROR) {
                    val height = location.layoutParams.height
                    popupWindow?.height = winHeight - (disPose + height)
                    popupWindow?.showAtLocation(location, Gravity.NO_GRAVITY, 0, disPose + height)
                } else popupWindow?.showAsDropDown(location, x, y)
            }
        } else throw NullPointerException("显示PopWindow失败,请初始化")

    }

    /**
     * 处理7.1之上Match覆盖控件的问题
     */
    fun disPose(location: View): Int {
        val a = IntArray(2)
        location.getLocationOnScreen(a)
        return a[1]
    }

    /**
     * 关闭PopWindow弹窗
     * @return 成功关闭为true,否则为false
     */
    fun disMiss(): Boolean =
        if (isExist() && isShowing()) {
            popupWindow!!.dismiss()
            true
        } else false
    
    /**
     * 获取当前PopWindow对象
     * @return isExist()为true时返回一个PopWindow对象 为false时抛出异常
     */
    fun getPopWindow() =
        if (isExist()) popupWindow
        else throw NullPointerException("获取PopWindow对象失败,PopWindow示例对象为空")

    /**
     * 设置背景
     */
    fun setBackgroundAlpha(bgAlpha: Float) {
        val activity: Activity = context as Activity
        val lp = activity.window.attributes
        lp.alpha = bgAlpha
        if (bgAlpha == 1f) activity.window.clearFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND)
        else activity.window.addFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND)
        activity.window.attributes = lp
    }

    /**
     *设置是否使用自带的阴影效果
     * @param canShow false 不使用, true 使用
     */
    fun shadowShowAble(canShow: Boolean) {
        this.canShow = canShow
    }

    /**
     * 设置是否能点击窗口外边窗口消失
     * @param canDown false 不能点击消失, true 可以点击消失
     */
    fun outSideTouchable(canDown: Boolean) {
        this.canDown = canDown
    }

    /**
     * 设置是否获得焦点
     * @param canClick false 不获取,无法点击, true 获取,可以点击
     */
    fun itemClickable(canClick: Boolean) {
        this.canClick = canClick
    }
    
    /**
     * PopWindow是否为显示状态
     * @return true 显示, false 未显示
     */
    fun isShowing() = popupWindow!!.isShowing

    /**
     * PopWindow是否实例化
     * @return true 已实例化, false 为空
     */
    fun isExist() = popupWindow != null

    /**
     * 将所用到的对象置空,建议调用
     */
    fun destroy() {
        dismissListener = null
        popupWindow?.dismiss()
        popupWindow = null
        context = null
        view = null
    }

    /**
     * 单位换算 dp转px
     * @return 转为px后的数值
     */
    fun dip2px(dpValue: Float): Int =
        (dpValue * this.context!!.resources.displayMetrics.density + 0.5f).toInt()

    /**
     * 通过方法变量实现接口回调
     */
    var dismissListener: (() -> Unit)? = null
}

/**
 * @param gravity 位置约束 参数为:Gravity.TOP,Gravity.BOTTOM,Gravity.LEFT,Gravity.RIGHT,Gravity.CENTER
 * @param x
 * @param y
 */
data class Excursion(val gravity: Int, val x: Int, val y: Int)

/**
 * @param width PopWindow距屏幕左右的距离
 * @param height PopWindow距屏幕上下的距离
 */
data class Restraint(val width: Float, val height: Float)
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
  • 122
  • 123
  • 124
  • 125
  • 126
  • 127
  • 128
  • 129
  • 130
  • 131
  • 132
  • 133
  • 134
  • 135
  • 136
  • 137
  • 138
  • 139
  • 140
  • 141
  • 142
  • 143
  • 144
  • 145
  • 146
  • 147
  • 148
  • 149
  • 150
  • 151
  • 152
  • 153
  • 154
  • 155
  • 156
  • 157
  • 158
  • 159
  • 160
  • 161
  • 162
  • 163
  • 164
  • 165
  • 166
  • 167
  • 168
  • 169
  • 170
  • 171
  • 172
  • 173
  • 174
  • 175
  • 176
  • 177
  • 178
  • 179
  • 180
  • 181
  • 182
  • 183
  • 184
  • 185
  • 186
  • 187
  • 188
  • 189
  • 190
  • 191
  • 192
  • 193
  • 194
  • 195
  • 196
  • 197
  • 198
  • 199
  • 200
  • 201
  • 202
  • 203
  • 204
  • 205
  • 206
  • 207
  • 208
  • 209
  • 210
  • 211
  • 212
  • 213
  • 214
  • 215
  • 216
  • 217
  • 218
  • 219
  • 220
  • 221
  • 222
  • 223
  • 224
  • 225
  • 226
  • 227
  • 228
  • 229
  • 230
  • 231
  • 232
  • 233
  • 234
  • 235
  • 236
  • 237
  • 238
  • 239
  • 240
  • 241
  • 242
  • 243
  • 244
  • 245
  • 246
  • 247
  • 248
  • 249
  • 250
  • 251
  • 252
  • 253
  • 254
  • 255
  • 256
  • 257
  • 258
  • 259
  • 260
  • 261
  • 262
  • 263
  • 264
  • 265
  • 266
  • 267
  • 268
  • 269
  • 270
  • 271
  • 272
  • 273
  • 274
  • 275
  • 276
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
  • 122
  • 123
  • 124
  • 125
  • 126
  • 127
  • 128
  • 129
  • 130
  • 131
  • 132
  • 133
  • 134
  • 135
  • 136
  • 137
  • 138
  • 139
  • 140
  • 141
  • 142
  • 143
  • 144
  • 145
  • 146
  • 147
  • 148
  • 149
  • 150
  • 151
  • 152
  • 153
  • 154
  • 155
  • 156
  • 157
  • 158
  • 159
  • 160
  • 161
  • 162
  • 163
  • 164
  • 165
  • 166
  • 167
  • 168
  • 169
  • 170
  • 171
  • 172
  • 173
  • 174
  • 175
  • 176
  • 177
  • 178
  • 179
  • 180
  • 181
  • 182
  • 183
  • 184
  • 185
  • 186
  • 187
  • 188
  • 189
  • 190
  • 191
  • 192
  • 193
  • 194
  • 195
  • 196
  • 197
  • 198
  • 199
  • 200
  • 201
  • 202
  • 203
  • 204
  • 205
  • 206
  • 207
  • 208
  • 209
  • 210
  • 211
  • 212
  • 213
  • 214
  • 215
  • 216
  • 217
  • 218
  • 219
  • 220
  • 221
  • 222
  • 223
  • 224
  • 225
  • 226
  • 227
  • 228
  • 229
  • 230
  • 231
  • 232
  • 233
  • 234
  • 235
  • 236
  • 237
  • 238
  • 239
  • 240
  • 241
  • 242
  • 243
  • 244
  • 245
  • 246
  • 247
  • 248
  • 249
  • 250
  • 251
  • 252
  • 253
  • 254
  • 255
  • 256
  • 257
  • 258
  • 259
  • 260
  • 261
  • 262
  • 263
  • 264
  • 265
  • 266
  • 267
  • 268
  • 269
  • 270
  • 271
  • 272
  • 273
  • 274
  • 275
  • 276



原文地址:访问原文地址
快照地址: 访问文章快照