|
@@ -0,0 +1,335 @@
|
|
|
+package cn.yyxx.eyuangame.core.ui.floatview
|
|
|
+
|
|
|
+import android.app.Activity
|
|
|
+import android.content.Context
|
|
|
+import android.graphics.PixelFormat
|
|
|
+import android.graphics.drawable.ColorDrawable
|
|
|
+import android.os.Build
|
|
|
+import android.os.Handler
|
|
|
+import android.os.Looper
|
|
|
+import android.os.Message
|
|
|
+import android.view.*
|
|
|
+import android.view.WindowManager.LayoutParams.FLAG_FULLSCREEN
|
|
|
+import android.widget.FrameLayout
|
|
|
+import android.widget.ImageView
|
|
|
+import android.widget.LinearLayout
|
|
|
+import android.widget.PopupWindow
|
|
|
+import cn.yyxx.eyuangame.base.utils.Logger
|
|
|
+import cn.yyxx.support.DensityUtils
|
|
|
+import cn.yyxx.support.scheduler.ScheduledWorker
|
|
|
+import java.util.concurrent.TimeUnit
|
|
|
+import kotlin.math.abs
|
|
|
+
|
|
|
+
|
|
|
+/**
|
|
|
+ * @author #Suyghur.
|
|
|
+ * Created on 2021/06/30
|
|
|
+ */
|
|
|
+class FloatingBall(val activity: Activity, private val isLandscape: Boolean, private val callback: FloatingBallCallback) : FrameLayout(activity),
|
|
|
+ View.OnTouchListener {
|
|
|
+
|
|
|
+ private lateinit var wm: WindowManager
|
|
|
+ private lateinit var wlp: WindowManager.LayoutParams
|
|
|
+
|
|
|
+ private lateinit var rootLinearLayout: LinearLayout
|
|
|
+ private lateinit var frameLayout: FrameLayout
|
|
|
+ private lateinit var ballView: ImageView
|
|
|
+ private lateinit var ballMenu: FloatingBallMenu
|
|
|
+ private lateinit var menu: PopupWindow
|
|
|
+
|
|
|
+ //浮标是否移动
|
|
|
+ private var isMove = false
|
|
|
+
|
|
|
+ //手指按下时坐标
|
|
|
+ private var mTouchStartX = 0f
|
|
|
+ private var mTouchStartY = 0f
|
|
|
+
|
|
|
+ //是否展开
|
|
|
+ private var hasShowContent = false
|
|
|
+
|
|
|
+ //默认吸附在左边
|
|
|
+ private var isLeftLocation = true
|
|
|
+
|
|
|
+ //菜单menu的长度
|
|
|
+ private var mSize = 0
|
|
|
+
|
|
|
+ private var hasStatusBar = false
|
|
|
+
|
|
|
+ private var displayWorker: ScheduledWorker? = null
|
|
|
+// private var displayWorker: ScheduledWorker? = null
|
|
|
+
|
|
|
+
|
|
|
+ private val timerHandler = object : Handler(Looper.getMainLooper()) {
|
|
|
+ override fun handleMessage(msg: Message) {
|
|
|
+
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ init {
|
|
|
+ hasStatusBar = activity.window.attributes.flags and FLAG_FULLSCREEN != FLAG_FULLSCREEN
|
|
|
+ createWM()
|
|
|
+ createView()
|
|
|
+ addView(rootLinearLayout)
|
|
|
+ wm.addView(this, wlp)
|
|
|
+ displayWorker = ScheduledWorker(1)
|
|
|
+ invokeDisplayTimerWork()
|
|
|
+ }
|
|
|
+
|
|
|
+ private fun createWM() {
|
|
|
+ wm = activity.windowManager
|
|
|
+ wlp = WindowManager.LayoutParams()
|
|
|
+ wlp.apply {
|
|
|
+ //总是出现在应用程序窗口之上
|
|
|
+ type = WindowManager.LayoutParams.LAST_APPLICATION_WINDOW
|
|
|
+ format = PixelFormat.RGBA_8888
|
|
|
+ flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
|
|
|
+ wlp.gravity = Gravity.TOP or Gravity.LEFT
|
|
|
+ //默认左边居中
|
|
|
+ wlp.x = 0
|
|
|
+ wlp.y = DensityUtils.getHeigthAndWidth(activity)[1] / 2
|
|
|
+ width = WindowManager.LayoutParams.WRAP_CONTENT
|
|
|
+ height = WindowManager.LayoutParams.WRAP_CONTENT
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ private fun createView() {
|
|
|
+ rootLinearLayout = LinearLayout(activity)
|
|
|
+ rootLinearLayout.layoutParams = ViewGroup.LayoutParams(DensityUtils.dip2px(activity, 40f), DensityUtils.dip2px(activity, 40f))
|
|
|
+
|
|
|
+ frameLayout = FrameLayout(activity)
|
|
|
+ frameLayout.layoutParams = ViewGroup.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT)
|
|
|
+
|
|
|
+ ballView = ImageView(activity)
|
|
|
+ ballView.apply {
|
|
|
+ layoutParams = ViewGroup.LayoutParams(DensityUtils.dip2px(activity, 40f), DensityUtils.dip2px(activity, 40f))
|
|
|
+ invokeUpdateBallView()
|
|
|
+ scaleType = ImageView.ScaleType.FIT_XY
|
|
|
+ //TODO onClickListener ?
|
|
|
+ setOnTouchListener(this@FloatingBall)
|
|
|
+// ballView.setOnClickListener {
|
|
|
+// invokeMenuShowOrDismiss()
|
|
|
+// }
|
|
|
+ frameLayout.addView(this)
|
|
|
+ }
|
|
|
+
|
|
|
+ ballMenu = FloatingBallMenu(activity, object : FloatingBallMenu.FloatingBallMenuCallback {
|
|
|
+ override fun onInitMenuData(): MutableList<FloatingBallMenu.FloatingBallMenuItem> {
|
|
|
+ mSize = callback.onInitMenuData().size
|
|
|
+ return callback.onInitMenuData()
|
|
|
+ }
|
|
|
+
|
|
|
+ override fun onDismissMenu() {
|
|
|
+ callback.onDismissMenu()
|
|
|
+ }
|
|
|
+
|
|
|
+ override fun onMenuItemClick(item: FloatingBallMenu.FloatingBallMenuItem, pos: Int) {
|
|
|
+ callback.onMenuItemClick(item, pos)
|
|
|
+ }
|
|
|
+ })
|
|
|
+
|
|
|
+ menu = PopupWindow(activity)
|
|
|
+ menu.apply {
|
|
|
+ contentView = ballMenu.contentView
|
|
|
+ width = DensityUtils.dip2px(activity, 40f + mSize * 40f)
|
|
|
+ height = DensityUtils.dip2px(activity, 40f)
|
|
|
+ isFocusable = true
|
|
|
+ setBackgroundDrawable(ColorDrawable())
|
|
|
+ isOutsideTouchable = true
|
|
|
+ setOnDismissListener {
|
|
|
+ invokeDisplayTimerWork()
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ if (isLandscape) {
|
|
|
+ handleFullScreenModel()
|
|
|
+ }
|
|
|
+ rootLinearLayout.addView(frameLayout)
|
|
|
+ }
|
|
|
+
|
|
|
+ fun dismissMenu() {
|
|
|
+ hasShowContent = false
|
|
|
+ menu.dismiss()
|
|
|
+ invokeUpdateBallView()
|
|
|
+ }
|
|
|
+
|
|
|
+ private fun invokeMenuShowOrDismiss() {
|
|
|
+ Logger.d("invokeMenuShowOrDismiss")
|
|
|
+ if (hasShowContent) {
|
|
|
+ menu.dismiss()
|
|
|
+ hasShowContent = false
|
|
|
+ } else {
|
|
|
+ hasShowContent = true
|
|
|
+ callback.onExpandMenu(false)
|
|
|
+ ballMenu.updateLayout(isLeftLocation)
|
|
|
+ if (hasStatusBar) {
|
|
|
+ menu.showAtLocation(ballMenu.contentView, Gravity.NO_GRAVITY, wlp.x + 10, wlp.y + getStatusBarHeight(activity))
|
|
|
+ } else {
|
|
|
+ when {
|
|
|
+ isNotch() -> {
|
|
|
+ menu.showAtLocation(ballMenu.contentView, Gravity.NO_GRAVITY, wlp.x + 10, wlp.y + getStatusBarHeight(activity))
|
|
|
+ }
|
|
|
+ isDisplayCutout(activity) -> {
|
|
|
+ menu.showAtLocation(ballMenu.contentView, Gravity.NO_GRAVITY, wlp.x + 10, wlp.y + getStatusBarHeight(activity))
|
|
|
+ }
|
|
|
+ else -> {
|
|
|
+ menu.showAtLocation(ballMenu.contentView, Gravity.NO_GRAVITY, wlp.x + 10, wlp.y)
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ private fun invokeUpdateBallView() {
|
|
|
+ callback.onUpdateBallView(ballView, isLeftLocation, false)
|
|
|
+ }
|
|
|
+
|
|
|
+ private fun invokeDisplayTimerWork() {
|
|
|
+ displayWorker?.invokeAtFixedRate({
|
|
|
+ timerHandler.sendEmptyMessage(1000)
|
|
|
+ }, 10, 3, TimeUnit.SECONDS)
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+ fun attach() {
|
|
|
+ if (visibility == View.GONE) {
|
|
|
+ visibility = View.VISIBLE
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ fun detach() {
|
|
|
+ if (visibility == View.VISIBLE) {
|
|
|
+ visibility = View.GONE
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ fun release() {
|
|
|
+ displayWorker?.apply {
|
|
|
+ cancel()
|
|
|
+ displayWorker = null
|
|
|
+ }
|
|
|
+
|
|
|
+ wm.removeView(rootLinearLayout)
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 解决PopupWindow无法在状态栏上显示
|
|
|
+ *
|
|
|
+ */
|
|
|
+ private fun handleFullScreenModel() {
|
|
|
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
|
|
|
+ try {
|
|
|
+ val mLayoutInScreen = PopupWindow::class.java.getDeclaredField("mLayoutInScreen")
|
|
|
+ mLayoutInScreen.isAccessible = true
|
|
|
+ mLayoutInScreen.set(menu, true)
|
|
|
+ } catch (e: Exception) {
|
|
|
+ e.printStackTrace()
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ private fun getStatusBarHeight(context: Context): Int {
|
|
|
+ val id = context.resources.getIdentifier("status_bar_height", "dimen", "android")
|
|
|
+ return context.resources.getDimensionPixelSize(id)
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 适配小米Android O设备 判断是否是刘海屏
|
|
|
+ */
|
|
|
+ private fun isNotch(): Boolean {
|
|
|
+ try {
|
|
|
+ val method = Class.forName("android.os.SystemProperties").getMethod("getInt", String::class.java, Int::class.java)
|
|
|
+ return (method.invoke(null, "ro.miui.notch", 0) as Int == 1)
|
|
|
+ } catch (e: Exception) {
|
|
|
+ e.printStackTrace()
|
|
|
+ }
|
|
|
+ return false
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 判断是否为挖孔屏幕
|
|
|
+ */
|
|
|
+ private fun isDisplayCutout(activity: Activity): Boolean {
|
|
|
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
|
|
|
+ try {
|
|
|
+ return activity.window.decorView.rootWindowInsets?.displayCutout != null
|
|
|
+ } catch (e: Exception) {
|
|
|
+ e.printStackTrace()
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return false
|
|
|
+ }
|
|
|
+
|
|
|
+ override fun onTouch(v: View, event: MotionEvent): Boolean {
|
|
|
+ displayWorker?.cancel()
|
|
|
+ //获取相对屏幕的坐标
|
|
|
+ val x = event.rawX
|
|
|
+ val y = event.rawY
|
|
|
+ //下面的这些事件,跟图标的移动无关,为了区分开拖动和点击事件
|
|
|
+ when (event.action) {
|
|
|
+ MotionEvent.ACTION_DOWN -> {
|
|
|
+ isMove = false
|
|
|
+ mTouchStartX = event.x
|
|
|
+ mTouchStartY = event.y
|
|
|
+ }
|
|
|
+ MotionEvent.ACTION_MOVE -> {
|
|
|
+ //图标移动的逻辑在这里
|
|
|
+ val moveStartX = event.x
|
|
|
+ val moveStartY = event.y
|
|
|
+ //移动量大于3才判定移动
|
|
|
+ if (abs(mTouchStartX - moveStartX) > 3 && abs(mTouchStartY - moveStartY) > 3) {
|
|
|
+ wlp.x = (x - mTouchStartX).toInt()
|
|
|
+ wlp.y = (y - mTouchStartY).toInt()
|
|
|
+ wm.updateViewLayout(this, wlp)
|
|
|
+ isMove = true
|
|
|
+ if (menu.isShowing) {
|
|
|
+ menu.dismiss()
|
|
|
+ hasShowContent = false
|
|
|
+ }
|
|
|
+ return false
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ MotionEvent.ACTION_UP -> {
|
|
|
+ //大于屏幕宽度的一半吸附在左边
|
|
|
+ v.performClick()
|
|
|
+ if (wlp.x <= DensityUtils.getHeigthAndWidth(activity)[0] / 2) {
|
|
|
+ wlp.x = 0
|
|
|
+ isLeftLocation = true
|
|
|
+ } else {
|
|
|
+ wlp.x = DensityUtils.getHeigthAndWidth(activity)[0]
|
|
|
+ isLeftLocation = false
|
|
|
+ }
|
|
|
+
|
|
|
+ wm.updateViewLayout(this, wlp)
|
|
|
+
|
|
|
+ invokeMenuShowOrDismiss()
|
|
|
+
|
|
|
+ if (isMove) {
|
|
|
+ invokeDisplayTimerWork()
|
|
|
+ } else {
|
|
|
+ invokeUpdateBallView()
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ return true
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+ interface FloatingBallCallback {
|
|
|
+
|
|
|
+ fun onUpdateBallView(ballView: ImageView, isLeftLocation: Boolean, isHide: Boolean)
|
|
|
+
|
|
|
+ //fun onUpdateRedDotView(redDotView: ImageView)
|
|
|
+
|
|
|
+ fun onInitMenuData(): MutableList<FloatingBallMenu.FloatingBallMenuItem>
|
|
|
+
|
|
|
+ fun onMenuItemClick(item: FloatingBallMenu.FloatingBallMenuItem, pos: Int)
|
|
|
+
|
|
|
+ fun onExpandMenu(hasRedDot: Boolean)
|
|
|
+
|
|
|
+ fun onDismissMenu()
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+}
|