本章涉及知识点复习:
Activity、Dialog、PopWinow、Toast窗口添加机制
https://www.jianshu.com/p/a02a4f504948
总结:添加View到Window就是调用WindowManager 的 addView 方法,需要三个对象,WindowManager、View、LayoutParams。
需求:
- 全局消息弹框
- 顶部弹入弹出
- 点击跳转聊天界面
- 消息外部区域点击事件透传
以下是几种常见的创建全局消息弹框的方法:
1、通过AlertDialog实现
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
Utils.init(applicationContext)
}
public fun showDialog(v: View) {
ActivityLifecycleManager[BaseApplication.instance].getCurrentActivity()?.let
{
AppUtils.showMessage(it, "")
}
}
public fun showCustomView(v: View) {
ActivityLifecycleManager[BaseApplication.instance].getCurrentActivity()?.let
{
AppUtils.showActivityWindow(it,"")
}
}
public fun showActivity(v: View) {
AppUtils.showMessageActivity("")
}
public fun showSystemWindow(v: View) {
AppUtils.showSystemWindow("")
}
public fun showOverlayPermission(v:View){
AppUtils.checkOverlayPermission(this)
}
}
fun showMessage(context: Activity, msg: String) {
var dialog = AlertDialog.Builder(context, R.style.TopMessageDialog).create()
var view = View.inflate(context, R.layout.layout_new_message, null)
dialog.setView(view)
dialog.window?.setGravity(Gravity.TOP)
//设置沉浸式导航栏
dialog.window?.clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS)
dialog.window?.addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS)
//设置window以外区域的点击事件透传的下层window
dialog.window?.addFlags(WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL)
//设置dialog的动画效果
dialog.window?.setWindowAnimations(R.style.DialogScaleAnimStyle)
dialog.show()
var llContainer =
dialog.window?.decorView?.findViewById<LinearLayout>(R.id.fl_container)
var params = llContainer?.layoutParams
params?.width = ScreenUtils.getScreenWidth()
params?.height = SizeUtils.dp2px(65f) +
XStatusBarHelper.getStatusBarHeight(context)
llContainer?.layoutParams = params
llContainer?.setPadding(
SizeUtils.dp2px(20f),
XStatusBarHelper.getStatusBarHeight(context),
SizeUtils.dp2px(20f),
SizeUtils.dp2px(10f)
)
llContainer?.setOnClickListener {
ToastUtils.showShort("跳转到聊天界面")
dialog?.dismiss()
}
}
<style name="TopMessageDialog" parent="AppTheme">
<item name="android:windowIsFloating">true</item>
<item name="android:windowIsTranslucent">true</item>
<item name="android:windowNoTitle">true</item>
<item name="android:windowBackground">@android:color/transparent</item>
<item name="android:backgroundDimAmount">0.0</item>
</style>
优点:不需要申请悬浮窗口权限
缺点:必须依赖activity,上下文必须传入activity。某些情况下无法获取当前可见activity时无法使用,如果 Dialog 弹出来之前 Activity 已经被销毁了,则这个 Dialog 在弹出的时候就会抛出异常,这个在线上版本收集到的崩溃中偶尔会出现。
附activity生命周期管理工具类:
class ActivityLifecycleManager : Application.ActivityLifecycleCallbacks {
private var totalActivityCount = 0
private var activityCount = 0
private var foreground = false
private var sCurrentActivityWeakRef: WeakReference<Activity>? = null
private val listeners = CopyOnWriteArrayList<AppStateListener>()
companion object {
private var instance: ActivityLifecycleManager? = null
fun init(application: Application): ActivityLifecycleManager {
if (instance == null) {
instance =
ActivityLifecycleManager()
application.registerActivityLifecycleCallbacks(instance)
}
return instance as ActivityLifecycleManager
}
operator fun get(application: Application): ActivityLifecycleManager {
if (instance == null) {
init(application)
}
return instance!!
}
operator fun get(ctx: Context): ActivityLifecycleManager {
if (instance == null) {
val appCtx = ctx.applicationContext
if (appCtx is Application) {
init(appCtx)
}
throw IllegalStateException(
"Foreground is not initialised and " + "cannot obtain the
Application object"
)
}
return instance as ActivityLifecycleManager
}
fun get(): ActivityLifecycleManager {
if (instance == null) {
throw IllegalStateException(
"Foreground is not initialised - invoke " + "at least once with parameterised init/get"
)
}
return instance as ActivityLifecycleManager
}
}
override fun onActivityCreated(activity: Activity, p1: Bundle?) {
totalActivityCount++
AppActivityManager.instance.addActivity(activity)
}
override fun onActivityStarted(activity: Activity) {
activityCount++
}
override fun onActivitySaveInstanceState(activity: Activity, p1: Bundle) {
}
override fun onActivityResumed(activity: Activity) {
sCurrentActivityWeakRef = WeakReference(activity)
if (!foreground) {
foreground = true
LogUtils.dTag("Lifecycle", "app into the foreground")
}
}
override fun onActivityPaused(activity: Activity) {
}
override fun onActivityStopped(activity: Activity) {
activityCount--
if (activityCount == 0 && totalActivityCount > 0) {
foreground = false
LogUtils.dTag("Lifecycle", "app into the background")
}
}
override fun onActivityDestroyed(activity: Activity) {
totalActivityCount--
AppActivityManager.instance.removeActivity(activity)
if (totalActivityCount == 0) LogUtils.dTag("Lifecycle", "app exit")
}
fun isForeground(): Boolean {
return foreground
}
fun isBackground(): Boolean {
return !foreground
}
fun getCurrentActivity(): Activity? {
return sCurrentActivityWeakRef?.get()
}
fun addListener(listener: AppStateListener) {
listeners.add(listener)
}
fun removeListener(listener: AppStateListener) {
listeners.remove(listener)
}
interface AppStateListener {
fun onBecameForeground()
fun onBecameBackground()
}
}
2、通过Activity的Window实现
fun showActivityWindow(context: Activity, msg: String) {
val layoutParams = WindowManager.LayoutParams()
layoutParams.flags =
WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN or
WindowManager.LayoutParams.FLAG_FULLSCREEN or
WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
layoutParams.screenOrientation = ActivityInfo.SCREEN_ORIENTATION_PORTRAIT
layoutParams.gravity = Gravity.TOP
layoutParams.x = 0
layoutParams.y = 0
layoutParams.width = WindowManager.LayoutParams.MATCH_PARENT
layoutParams.height = SizeUtils.dp2px(65f) +
XStatusBarHelper.getStatusBarHeight(context)
layoutParams.format = PixelFormat.TRANSPARENT
layoutParams.windowAnimations = R.style.DialogScaleAnimStyle
val layoutInflater = LayoutInflater.from(context) as LayoutInflater
val view = layoutInflater.inflate(R.layout.layout_new_message, null) as View
val windowManager = context.getSystemService(Context.WINDOW_SERVICE) as
WindowManager
windowManager.addView(view, layoutParams)
view.setPadding(
SizeUtils.dp2px(20f),
XStatusBarHelper.getStatusBarHeight(context),
SizeUtils.dp2px(20f),
SizeUtils.dp2px(10f)
)
view.setOnClickListener {
ToastUtils.showShort("跳转到聊天界面")
windowManager.removeView(it)
}
}
优点:不需要申请悬浮窗口权限
缺点:必须依赖activity,上下文必须传入activity。某些情况下无法获取当前可见activity时无法使用。
3、通过系统Window实现
fun showSystemWindow(msg: String) {
val layoutParams = WindowManager.LayoutParams()
if (Build.VERSION.SDK_INT > Build.VERSION_CODES.O) {
//8.0以后使用TYPE_SYSTEM_ALERT,部分手机直接抛出异常,崩溃
layoutParams.type = WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY
} else if (Build.VERSION.SDK_INT > Build.VERSION_CODES.M) {
layoutParams.type = WindowManager.LayoutParams.TYPE_TOAST
} else {
layoutParams.type = WindowManager.LayoutParams.TYPE_SYSTEM_ALERT
}
layoutParams.flags =
WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN or
WindowManager.LayoutParams.FLAG_FULLSCREEN or
WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
layoutParams.screenOrientation = ActivityInfo.SCREEN_ORIENTATION_PORTRAIT
layoutParams.gravity = Gravity.TOP
layoutParams.x = 0
layoutParams.y = 0
layoutParams.width = WindowManager.LayoutParams.MATCH_PARENT
layoutParams.height =
SizeUtils.dp2px(65f) +
XStatusBarHelper.getStatusBarHeight(BaseApplication.instance)
layoutParams.format = PixelFormat.TRANSPARENT
layoutParams.windowAnimations = R.style.DialogScaleAnimStyle
val layoutInflater =
LayoutInflater.from(BaseApplication.instance.applicationContext) as
LayoutInflater
val view = layoutInflater.inflate(R.layout.layout_new_message, null) as View
val windowManager =
BaseApplication.instance.getSystemService(Context.WINDOW_SERVICE) as
WindowManager
windowManager.addView(view, layoutParams)
view.setPadding(
SizeUtils.dp2px(20f),
XStatusBarHelper.getStatusBarHeight(BaseApplication.instance),
SizeUtils.dp2px(20f),
SizeUtils.dp2px(10f)
)
view.setOnClickListener {
ToastUtils.showShort("跳转到聊天界面")
windowManager.removeView(it)
}
}
fun checkOverlayPermission(context: Context): Boolean {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
if (Settings.canDrawOverlays(context)) {
Toast.makeText(context, "已取得悬浮窗使用权限", Toast.LENGTH_SHORT).show()
} else {
Toast.makeText(context, "需要取得权限以使用悬浮窗", Toast.LENGTH_SHORT).show()
val intent = Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION)
intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK
intent.data = Uri.parse("package:${context.packageName}")
context.startActivity(intent)
}
return Settings.canDrawOverlays(context)
} else {
//SDK在23以下,不用管.
return true
}
}
优点:不需要依赖任何activity
缺点:6.0之后需要申请悬浮窗使用权限,否则无法显示。实际情况是大部分人不会允许。
4、通过透明Activity实现
fun showMessageActivity(msg: String) {
NewMessageNotificationActivity.start(BaseApplication.instance.applicationContext)
}
class NewMessageNotificationActivity : AppCompatActivity(), View.OnClickListener {
companion object {
private const val EXTRA_KEY1 = "extra_key1"
private val bgColors: List<String> =
mutableListOf("#F76793", "#D2B469", "#43D96D", "#50C7FB")
fun start(context: Context) {
val intent = Intent(context, NewMessageNotificationActivity::class.java)
if (context !is Activity) {
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
}
context.startActivity(intent)
}
}
private var tast: Runnable? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
window.setGravity(Gravity.TOP)
window.clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS)
window.addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS)
window.addFlags(WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL)
setFinishOnTouchOutside(true)
initLayout()
initView()
initData()
initEvent()
tast = Runnable { finish() }
BaseApplication.handler.postDelayed(tast, 3000)
}
override fun dispatchKeyEvent(event: KeyEvent): Boolean {
if (event.keyCode == 4) {
finish()
}
return super.dispatchKeyEvent(event)
}
fun initLayout() {
setContentView(R.layout.layout_new_message)
var background = fl_container.background as GradientDrawable
background.setColor(Color.parseColor(bgColors[java.util.Random().nextInt(4)]))
var layoutParams = fl_container.layoutParams as FrameLayout.LayoutParams
layoutParams.width = ScreenUtils.getScreenWidth()
layoutParams.height = SizeUtils.dp2px(65f) +
XStatusBarHelper.getStatusBarHeight(this)
fl_container.layoutParams = layoutParams
fl_container.setPadding(
SizeUtils.dp2px(20f),
XStatusBarHelper.getStatusBarHeight(this),
SizeUtils.dp2px(20f),
SizeUtils.dp2px(10f)
)
}
fun initEvent() {
fl_container.setOnClickListener(this)
}
fun initData() {
}
override fun onStop() {
super.onStop()
tast?.let {
BaseApplication.handler.removeCallbacks(it)
}
finish()
}
override fun onNewIntent(intent: Intent) {
super.onNewIntent(intent)
tast?.let {
BaseApplication.handler.removeCallbacks(it)
BaseApplication.handler.postDelayed(it, 3000)
}
initView()
}
fun initView() {
}
override fun onClick(v: View) {
when (v.id) {
R.id.fl_container -> {
ToastUtils.showShort("跳转到聊天界面")
finish()
}
}
}
override fun finish() {
super.finish()
overridePendingTransition(R.anim.slide_in_null, R.anim.pop_top_out)
}
override fun onBackPressed() {
finish()
}
}
优点:弹框以四大组件的形式存在,不需要用户开通悬浮窗权限。各版本、各机型不存在适配问题。且有完整的生命周期管理,可以处理更多的事务。
缺点:暂无。