一、前言
近期突然想坚持的写写博客,以后每周可能都会都会不定时的发上几篇,来记录一下这一周的学习成果以及回顾一下知识。
二、背景
平时开发中或多或少都会使用到[Dialog,每次都需要自定义继承Dialog,写多了不胜其烦,索性直接对Dialog做个简单的封装。以后直接拷贝来用,话不多说上代码。如有大佬发现问题也请多多指教一起讨论!
三、相关源码:
CustomControlsDialogUtils.kt
package com.example.thirdpartyintegrationmodule.util
import android.app.Activity
import android.app.Dialog
import android.content.Context
import android.content.ContextWrapper
import android.graphics.Color
import android.graphics.drawable.ColorDrawable
import android.view.LayoutInflater
import android.view.View
import android.view.Window
import kotlin.math.roundToInt
object CustomControlsDialogUtils {
/**
* 创建对话框
* layout:弹框布局文件
* theme:弹框布局样式
* direction:弹框方向
* listener: 弹框监听
*/
fun createCustomDialog(
context: Context?, layout: Int,
theme: Int, direction: Int,
onCreatedView: (Dialog, View) -> Unit?
): Dialog {
val view = LayoutInflater.from(context).inflate(layout, null)
val dialog: Dialog = CustomDialog(context!!, view, theme)
return run {
dialog.requestWindowFeature(Window.FEATURE_NO_TITLE)
dialog.setCancelable(true)// 是否可取消(按物理返回键)
onCreatedView(dialog, view)
setDialogWindow(dialog.window!!, direction)
dialog
}
}
/**
* 获取控件的绑定
*/
fun <T : View> findViewById(mView: View, viewId: Int): T? {
return mView.findViewById(viewId)
}
/**
* 修改对话框的属性
*/
private fun setDialogWindow(window: Window, direction: Int) {
val widthRatio = 0.85f // 默认的宽度比
val attr = window.attributes
attr.width =
((SoftInputUtils.getScreenWidthPixels() * widthRatio).roundToInt()) // 获取屏幕宽度*设置的默认宽度
attr.gravity = direction // 设置对话框参数
window.attributes = attr // 设置对话框属性
}
/**
* 关闭对话框
*/
fun dismissDialog(dialog: Dialog?) {
if (dialog != null) {
val context = (dialog.context as ContextWrapper).baseContext
if (context is Activity) {
dialog.dismiss()
}
}
}
/**
* 构建自定义的对话框参数
*/
open class CustomDialog(mContext: Context, var view: View, var mTheme: Int) :
Dialog(mContext) {
init {
setTransparentBackground()
}
override fun show() {
super.show()
setContentView(view)
}
private fun setTransparentBackground() {
window?.setBackgroundDrawable(ColorDrawable(Color.TRANSPARENT))
}
}
}
调用,这里的dialog_privacy_rights,dialog_style是我根据自己的需求场景自定义的xml文件以及样式,可自行更换
R.id.customControlsDialogBoxBut -> {
CustomControlsDialogUtils.createCustomDialog(this,
R.layout.dialog_privacy_rights,
R.style.dialog_style,
Gravity.BOTTOM) { dialog: Dialog, mView: View ->
CustomControlsDialogUtils.findViewById<TextView>(mView,R.id.dialogPrivacyTitleTv)?.text = "隐私政策"
CustomControlsDialogUtils.findViewById<TextView>(mView, R.id.dialogPrivacyDisagreeTv)?.setOnClickListener {
CustomControlsDialogUtils.dismissDialog(dialog)
}
CustomControlsDialogUtils.findViewById<TextView>(mView, R.id.dialogPrivacyAgreeTv)?.setOnClickListener {
CustomControlsDialogUtils.dismissDialog(dialog)
}
}.show()
}
基本样式参考
<style name="dialog_style" parent="@android:style/Theme.Dialog">
<!-- 是否有边框 -->
<item name="android:windowFrame">@null</item>
<!-- 是否漂现在activity上 -->
<item name="android:windowIsFloating">true</item>
<!-- 是否半透明 -->
<item name="android:windowIsTranslucent">true</item>
<!-- 是否隐藏标题 -->
<item name="android:windowNoTitle">true</item>
<!-- 设置背景透明 -->
<item name="android:background">@android:color/transparent</item>
<!-- 是否允许背景变暗 -->
<item name="android:backgroundDimEnabled">true</item>
<!-- 设置蒙层的透明度0-1 -->
<item name="android:backgroundDimAmount">0.5</item>
<!-- 设置背景 -->
<item name="android:windowBackground">@null</item>
<!-- 设置点击弹窗之外是否关闭 -->
<item name="android:windowCloseOnTouchOutside">true</item>
<!-- 设置键盘弹窗模式 -->
<item name="android:windowSoftInputMode">adjustResize</item>
<!-- 设置动画样式 -->
<item name="android:windowAnimationStyle">@style/bottomDialogAnimation</item>
</style>
四、杂谈:
结合网上整理了一下关于Dialog的面试问题以及回答方式。
1、当Dialog存在于Activity上时按下Home键的生命周期?
答案:
onPause ⇒ onStop ⇒ 重新显示 ⇒ onRestart ⇒ onStart () ⇒ onResume,开始按下Home键,才会继续执行onPause()和onStop()方法。到这里就说明对话框并没有使Activity进入后台,而是在点击了Home键后Activity才进入后台工作。
原因就是,其实Dialog是Activity的一个组件,此时Activity并不是不可见,而是被Dialog组件覆盖了其他的组件,此时我们无法对其他组件进行操作而已。
2、Activity、Dialog、PopupWindow、Toast 与Window的关系
答案:
-
Activity
在Activity创建过程中所创建的PhoneWindow,是层级最小的Window,叫做应用Window,层级范围1-99。(层级范围大的Window可以覆盖层级小的Window) -
Dialog
Dialog的显示过程和Activity基本相同,也是创建了PhoneWindow,初始化DecorView,并将Dialog的视图添加到DecorView中,最终通过addView显示出来。
但是有一点不同的是,Dialog的Window并不是应用窗口,而是子窗口,层级范围1000-1999,子Window的显示必须依附于应用窗口,也会覆盖应用级- Window。这也就是为什么Dialog传入的上下文必须为Activity的Context了。 -
PopupWindow
PopupWindow的显示就有所不同了,它没有创建PhoneWindow,而是直接创建了一个View(PopupDecorView),然后通过WindowManager的addView方法显示出来了。没有创建PhoneWindow,是不是就跟Window没关系了呢?并不是,其实只要是调用了WindowManager的addView方法,那就是创建了Window,跟你有没有创建PhoneWindow无关。View就是Window的表现形式,只不过PhoneWindow的存在让Window形象更立体了一些。所以PopupWindow也是通过Window展示出来的,而它的Window层级属于子Window,必须依附与应用窗口。 -
Toast
Toast和PopupWindow比较像,没有新建PhoneWindow,直接通过addView方法显示View即可。不同的是它属于系统级Window,层级范围2000-2999,所以无须依附于Activity。
总结
四个比较下来,可以发现,只要想显示View,就会涉及到WindowManager的addView方法,也就用到了Window这个概念,然后会根据不同的分层依次显示覆盖到界面上。不同的是,Activity和Dialog涉及到了布局比较复杂,还会有布局主题等元素,所以用到了PhoneWindow进行一个解耦,帮助他们管理View。而PopupWindow和Toast结构比较简单,所以直接新建一个类似DecorView的View,通过addView显示到界面。