Android(安卓)PopupWindow工具类 (已解决7.1以上showAsDropDown显示MATCH的PopWindow时覆盖控件的问题)
16lz
2021-12-04
前言
PopWindow工具类
import android.app.Activityimport android.content.Contextimport android.graphics.drawable.BitmapDrawableimport android.os.Buildimport android.view.Displayimport android.view.Gravityimport android.view.Viewimport android.view.WindowManagerimport 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)
更多相关文章
- 屏幕方向android:screenOrientation
- Android兼容性优化-Android(安卓)8.0设置Activity透明主题崩溃
- Android(安卓)Dimension
- Android(安卓)Dialog风格弹出框的Activity
- Android实用代码
- android nfc 开发
- 49.Android中各种Span的用法
- android在service中使用AsyncHttpClient加载网络资源
- Android(安卓)edittext刚进入页面取消焦点