91超碰碰碰碰久久久久久综合_超碰av人澡人澡人澡人澡人掠_国产黄大片在线观看画质优化_txt小说免费全本

溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

怎么在Android中實現一個懸浮窗功能

發布時間:2021-04-19 17:31:59 來源:億速云 閱讀:381 作者:Leah 欄目:移動開發

怎么在Android中實現一個懸浮窗功能?相信很多沒有經驗的人對此束手無策,為此本文總結了問題出現的原因和解決方法,通過這篇文章希望你能解決這個問題。

判斷是否有懸浮窗權限

點擊左上角圖標時,我們要先判斷當前app是否有懸浮窗權限,首先我們在配置文件中添加,懸浮窗的權限。

<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />

(很多文章標題都是懸浮窗如何繞過權限,什么設置類型為TOAST或者PHONE,我想說不可能的事,TOAST類型的雖然部分機型可以顯示但是就是一個普通的TOSAT會自動消失)

那么我們如何判斷是否有懸浮窗權限呢,這一塊不同廠商處理方案可能不一樣,這里我們用一種通用的處理方案,測試表明除了(vivo部分)無效,其他多數機型都ok。并且vivo部分機型微信通話也不會彈出提示(這我就放心了~)

fun zoom(v: View) {
  if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
    if (!Settings.canDrawOverlays(this)) {
      Toast.makeText(this, "當前無權限,請授權", Toast.LENGTH_SHORT)
      GlobalDialogSingle(this, "", "當前未獲取懸浮窗權限", "去開啟", DialogInterface.OnClickListener { dialog, which ->
        dialog.dismiss()
        startActivityForResult(Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION, Uri.parse("package:" + packageName)), 0)
      }).show()
 
    } else {
      moveTaskToBack(true)
      val intent = Intent(this@Main2Activity, FloatWinfowServices::class.java)
      hasBind = bindService(intent, mVideoServiceConnection, Context.BIND_AUTO_CREATE)
    }
  }
}

我們通過Settings.canDrawOverlays(this)來判斷當前應用是否有懸浮窗權限,如果沒有,我們彈窗提示,通過

startActivityForResult(Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION, Uri.parse("package:" + packageName)), 0)

 跳轉到開啟懸浮窗權限頁面。如果懸浮窗權限已開啟,直接將當前任務棧置于后臺,開啟服務即可。

其實回調方法,并沒有直接告訴我們是否授權成功,所以我們需要在回調中再次判斷

override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent) {
  if (requestCode == 0) {
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
      if (!Settings.canDrawOverlays(this)) {
        Toast.makeText(this, "授權失敗", Toast.LENGTH_SHORT).show()
      } else {
        Handler().postDelayed({
          val intent = Intent(this@Main2Activity, FloatWinfowServices::class.java)
          intent.putExtra("rangeTime", rangeTime)
          hasBind = bindService(intent, mVideoServiceConnection, Context.BIND_AUTO_CREATE)
          moveTaskToBack(true)
        }, 1000)
 
      }
    }
  }
}

這里我們可以看到回調中延遲了1秒,因為測試發現某些機型反應“過快”,收到回調的時候還以為沒有授權成功,其實已經成功了。

綁定Service我們需要一個ServiceConnection對象

internal var mVideoServiceConnection: ServiceConnection = object : ServiceConnection {
  override fun onServiceConnected(name: ComponentName, service: IBinder) {
    // 獲取服務的操作對象
    val binder = service as FloatWinfowServices.MyBinder
    binder.service
  }
  override fun onServiceDisconnected(name: ComponentName) {}
}

Main2Activity的完整代碼如下所示:

/**
 * @author Huanglinqing
 */
class Main2Activity : AppCompatActivity() {
  private val chronometer: Chronometer? = null
  private var hasBind = false
  private val rangeTime: Long = 0
  override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setContentView(R.layout.activity_main2)
  }
  fun zoom(v: View) {
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
      if (!Settings.canDrawOverlays(this)) {
        Toast.makeText(this, "當前無權限,請授權", Toast.LENGTH_SHORT)
        GlobalDialogSingle(this, "", "當前未獲取懸浮窗權限", "去開啟", DialogInterface.OnClickListener { dialog, which ->
          dialog.dismiss()
          startActivityForResult(Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION, Uri.parse("package:" + packageName)), 0)
        }).show()
      } else {
        moveTaskToBack(true)
        val intent = Intent(this@Main2Activity, FloatWinfowServices::class.java)
        hasBind = bindService(intent, mVideoServiceConnection, Context.BIND_AUTO_CREATE)
      }
    }
  }
  internal var mVideoServiceConnection: ServiceConnection = object : ServiceConnection {
    override fun onServiceConnected(name: ComponentName, service: IBinder) {
      // 獲取服務的操作對象
      val binder = service as FloatWinfowServices.MyBinder
      binder.service
    }
    override fun onServiceDisconnected(name: ComponentName) {}
  }
  override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent) {
    if (requestCode == 0) {
      if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
        if (!Settings.canDrawOverlays(this)) {
          Toast.makeText(this, "授權失敗", Toast.LENGTH_SHORT).show()
        } else {
          Handler().postDelayed({
            val intent = Intent(this@Main2Activity, FloatWinfowServices::class.java)
            intent.putExtra("rangeTime", rangeTime)
            hasBind = bindService(intent, mVideoServiceConnection, Context.BIND_AUTO_CREATE)
            moveTaskToBack(true)
          }, 1000)
        }
      }
    }
  }
  override fun onRestart() {
    super.onRestart()
    Log.d("RemoteView", "重新顯示了")
    //不顯示懸浮框
    if (hasBind) {
      unbindService(mVideoServiceConnection)
      hasBind = false
    }
  }
  override fun onNewIntent(intent: Intent) {
    super.onNewIntent(intent)
  }
  override fun onDestroy() {
    super.onDestroy()
  }
}

新建懸浮窗Service

新建懸浮窗Service FloatWinfowServices,因為我們使用的BindService,我們在onBind方法中初始化service中的布局

override fun onBind(intent: Intent): IBinder? {
  initWindow()
  //懸浮框點擊事件的處理
  initFloating()
  return MyBinder()
}

service中我們通過WindowManager來添加一個布局顯示。

/**
 * 初始化窗口
 */
private fun initWindow() {
  winManager = application.getSystemService(Context.WINDOW_SERVICE) as WindowManager
  //設置好懸浮窗的參數
  wmParams = params
  // 懸浮窗默認顯示以左上角為起始坐標
  wmParams!!.gravity = Gravity.LEFT or Gravity.TOP
  //懸浮窗的開始位置,因為設置的是從左上角開始,所以屏幕左上角是x=0;y=0
  wmParams!!.x = winManager!!.defaultDisplay.width
  wmParams!!.y = 210
  //得到容器,通過這個inflater來獲得懸浮窗控件
  inflater = LayoutInflater.from(applicationContext)
  // 獲取浮動窗口視圖所在布局
  mFloatingLayout = inflater!!.inflate(R.layout.remoteview, null)
  // 添加懸浮窗的視圖
  winManager!!.addView(mFloatingLayout, wmParams)
}

懸浮窗的參數主要設置懸浮窗的類型為

WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY

8.0 以下可設置為:

wmParams!!.type = WindowManager.LayoutParams.TYPE_PHONE

代碼如下所示:

private //設置window type 下面變量2002是在屏幕區域顯示,2003則可以顯示在狀態欄之上
    //設置可以顯示在狀態欄上
    //設置懸浮窗口長寬數據
val params: WindowManager.LayoutParams
  get() {
    wmParams = WindowManager.LayoutParams()
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
      wmParams!!.type = WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY
    } else {
      wmParams!!.type = WindowManager.LayoutParams.TYPE_PHONE
    }
    wmParams!!.flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE or WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL or
        WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN or WindowManager.LayoutParams.FLAG_LAYOUT_INSET_DECOR or
        WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH
    wmParams!!.width = WindowManager.LayoutParams.WRAP_CONTENT
    wmParams!!.height = WindowManager.LayoutParams.WRAP_CONTENT
    return wmParams
  }

當點擊懸浮窗的時候回到Activity2頁面,并且懸浮窗消失,所以我們只需要給懸浮窗添加點擊事件

linearLayout!!.setOnClickListener { startActivity(Intent(this@FloatWinfowServices, Main2Activity::class.java)) }

當Service走到onDestory的時候將view移除,對于Activity2頁面來說 當onResume的時候 解綁Service,當onstop的時候 綁定Service。

從效果圖中我們可以看到懸浮窗可以拖拽的,所以還要設置觸摸事件,當移動距離超過某個值的時候讓onTouch消費事件,這樣就不會觸發點擊事件了。這個算是view比較基礎的知識,相信大家都明白了。

//開始觸控的坐標,移動時的坐標(相對于屏幕左上角的坐標)
private var mTouchStartX: Int = 0
private var mTouchStartY: Int = 0
private var mTouchCurrentX: Int = 0
private var mTouchCurrentY: Int = 0
//開始時的坐標和結束時的坐標(相對于自身控件的坐標)
private var mStartX: Int = 0
private var mStartY: Int = 0
private var mStopX: Int = 0
private var mStopY: Int = 0
//判斷懸浮窗口是否移動,這里做個標記,防止移動后松手觸發了點擊事件
private var isMove: Boolean = false
private inner class FloatingListener : View.OnTouchListener {
  override fun onTouch(v: View, event: MotionEvent): Boolean {
    val action = event.action
    when (action) {
      MotionEvent.ACTION_DOWN -> {
        isMove = false
        mTouchStartX = event.rawX.toInt()
        mTouchStartY = event.rawY.toInt()
        mStartX = event.x.toInt()
        mStartY = event.y.toInt()
      }
      MotionEvent.ACTION_MOVE -> {
        mTouchCurrentX = event.rawX.toInt()
        mTouchCurrentY = event.rawY.toInt()
        wmParams!!.x += mTouchCurrentX - mTouchStartX
        wmParams!!.y += mTouchCurrentY - mTouchStartY
        winManager!!.updateViewLayout(mFloatingLayout, wmParams)
        mTouchStartX = mTouchCurrentX
        mTouchStartY = mTouchCurrentY
      }
      MotionEvent.ACTION_UP -> {
        mStopX = event.x.toInt()
        mStopY = event.y.toInt()
        if (Math.abs(mStartX - mStopX) >= 1 || Math.abs(mStartY - mStopY) >= 1) {
          isMove = true
        }
      }
      else -> {
      }
    }
    //如果是移動事件不觸發OnClick事件,防止移動的時候一放手形成點擊事件
    return isMove
  }
}

FloatWinfowServices所有代碼如下所示:

class FloatWinfowServices : Service() {
   private var winManager: WindowManager? = null
  private var wmParams: WindowManager.LayoutParams? = null
  private var inflater: LayoutInflater? = null
  //浮動布局
  private var mFloatingLayout: View? = null
  private var linearLayout: LinearLayout? = null
  private var chronometer: Chronometer? = null
  override fun onBind(intent: Intent): IBinder? {
    initWindow()
    //懸浮框點擊事件的處理
    initFloating()
    return MyBinder()
  }
  inner class MyBinder : Binder() {
    val service: FloatWinfowServices
      get() = this@FloatWinfowServices
  }
  override fun onCreate() {
    super.onCreate()
  }
  /**
   * 懸浮窗點擊事件
   */
  private fun initFloating() {
    linearLayout = mFloatingLayout!!.findViewById<LinearLayout>(R.id.line1)
    linearLayout!!.setOnClickListener { startActivity(Intent(this@FloatWinfowServices, Main2Activity::class.java)) }
    //懸浮框觸摸事件,設置懸浮框可拖動
    linearLayout!!.setOnTouchListener(FloatingListener())
  }
  //開始觸控的坐標,移動時的坐標(相對于屏幕左上角的坐標)
  private var mTouchStartX: Int = 0
  private var mTouchStartY: Int = 0
  private var mTouchCurrentX: Int = 0
  private var mTouchCurrentY: Int = 0
  //開始時的坐標和結束時的坐標(相對于自身控件的坐標)
  private var mStartX: Int = 0
  private var mStartY: Int = 0
  private var mStopX: Int = 0
  private var mStopY: Int = 0
  //判斷懸浮窗口是否移動,這里做個標記,防止移動后松手觸發了點擊事件
  private var isMove: Boolean = false
  private inner class FloatingListener : View.OnTouchListener {
    override fun onTouch(v: View, event: MotionEvent): Boolean {
      val action = event.action
      when (action) {
        MotionEvent.ACTION_DOWN -> {
          isMove = false
          mTouchStartX = event.rawX.toInt()
          mTouchStartY = event.rawY.toInt()
          mStartX = event.x.toInt()
          mStartY = event.y.toInt()
        }
        MotionEvent.ACTION_MOVE -> {
          mTouchCurrentX = event.rawX.toInt()
          mTouchCurrentY = event.rawY.toInt()
          wmParams!!.x += mTouchCurrentX - mTouchStartX
          wmParams!!.y += mTouchCurrentY - mTouchStartY
          winManager!!.updateViewLayout(mFloatingLayout, wmParams)
          mTouchStartX = mTouchCurrentX
          mTouchStartY = mTouchCurrentY
        }
        MotionEvent.ACTION_UP -> {
          mStopX = event.x.toInt()
          mStopY = event.y.toInt()
          if (Math.abs(mStartX - mStopX) >= 1 || Math.abs(mStartY - mStopY) >= 1) {
            isMove = true
          }
        }
        else -> {
        }
      }
      //如果是移動事件不觸發OnClick事件,防止移動的時候一放手形成點擊事件
      return isMove
    }
  }
  /**
   * 初始化窗口
   */
  private fun initWindow() {
    winManager = application.getSystemService(Context.WINDOW_SERVICE) as WindowManager
    //設置好懸浮窗的參數
    wmParams = params
    // 懸浮窗默認顯示以左上角為起始坐標
    wmParams!!.gravity = Gravity.LEFT or Gravity.TOP
    //懸浮窗的開始位置,因為設置的是從左上角開始,所以屏幕左上角是x=0;y=0
    wmParams!!.x = winManager!!.defaultDisplay.width
    wmParams!!.y = 210
    //得到容器,通過這個inflater來獲得懸浮窗控件
    inflater = LayoutInflater.from(applicationContext)
    // 獲取浮動窗口視圖所在布局
    mFloatingLayout = inflater!!.inflate(R.layout.remoteview, null)
    chronometer = mFloatingLayout!!.findViewById<Chronometer>(R.id.chronometer)
    chronometer!!.start()
    // 添加懸浮窗的視圖
    winManager!!.addView(mFloatingLayout, wmParams)
  }
  private //設置window type 下面變量2002是在屏幕區域顯示,2003則可以顯示在狀態欄之上
      //設置可以顯示在狀態欄上
      //設置懸浮窗口長寬數據
  val params: WindowManager.LayoutParams
    get() {
      wmParams = WindowManager.LayoutParams()
      if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
        wmParams!!.type = WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY
      } else {
        wmParams!!.type = WindowManager.LayoutParams.TYPE_PHONE
      }
      wmParams!!.flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE or WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL or
          WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN or WindowManager.LayoutParams.FLAG_LAYOUT_INSET_DECOR or
          WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH
      wmParams!!.width = WindowManager.LayoutParams.WRAP_CONTENT
      wmParams!!.height = WindowManager.LayoutParams.WRAP_CONTENT
      return wmParams
    }
  override fun onStartCommand(intent: Intent, flags: Int, startId: Int): Int {
    return super.onStartCommand(intent, flags, startId)
  }
  override fun onDestroy() {
    super.onDestroy()
    winManager!!.removeView(mFloatingLayout)
  }
}

實際應用中需要考慮的一些其他問題

在使用使用的過程中,我們肯定會遇到其他問題:

1.用戶使用過程中,可能會直接按Home鍵,這個時候如何提示呢?

   產生問題原因:因為用戶按Home鍵之后,開發者無法重寫Home鍵邏輯,此時應用不在前臺運行,無法彈窗提醒,此時用戶點擊APP圖標進入的是第一個棧,這個時候用戶就沒有進入通話頁面的入口了。

  解決方案:

  第一種解決方案 我們可以仿照微信那樣去做,就是在整個通話過程中開啟一個前臺通知,用戶點擊通知時進入通話頁面。

  第二種解決方案 就是檢測應用是否在前臺,當通話頁面在運行的時候,并且應用重新回到前臺,我們廣播到其他頁面,提示權限引導即可。

2.用戶在通話頁面(singleInstance模式),點擊Home鍵

應用在后臺運行的時候,通話結束,Activity被finish,此時從任務程序中切回應用你會發現打開的竟然是通話頁面!

這個問題簡單的說就是,如果你在通話頁面呼叫某人,通話過程中按Home鍵,然后電話掛斷,此時你從任務程序中切回應用,會再次呼叫這個人,也就是這種狀態下重新回到了onCreate方法。

問題產生原因:

1.因為通話頁面是singleInstance模式,此時有兩個任務棧,按Home鍵后再從任務程序中切回,此時應用只保留了第二個任務棧,已經失去了和第一個任務棧的關系,finish之后無法在回到第一個任務棧。

解決方案:

1.(不推薦)通話頁面不使用singleInstance模式,這種情況下,在通話過程中無法操作軟件的其他功能,一般都不采取。

2.(我目前的解決方案)設置一個標記位,標記當前是否在通話,在onCreate中如果通話已經結束了,跳轉到一個過渡頁面(標準模式),過渡頁面中finish,就可以了,添加過渡頁面的原因是我們不知道上一個頁面是哪里,因為我們收到來電可能是任意頁面,我們我們在過渡頁面finsh之后,就再次回到了第一個任務棧。

看完上述內容,你們掌握怎么在Android中實現一個懸浮窗功能的方法了嗎?如果還想學到更多技能或想了解更多相關內容,歡迎關注億速云行業資訊頻道,感謝各位的閱讀!

向AI問一下細節

免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。

AI

抚松县| 布尔津县| 汶川县| 安新县| 金沙县| 仙桃市| 长春市| 南昌县| 宣威市| 天镇县| 会理县| 洛扎县| 喀喇沁旗| 遂平县| 夏邑县| 富顺县| 托里县| 吴忠市| 唐海县| 海伦市| 房产| 临猗县| 景谷| 卓资县| 门源| 吕梁市| 德令哈市| 普定县| 建湖县| 高雄县| 淮阳县| 剑河县| 抚松县| 随州市| 会泽县| 延吉市| 山东| 双城市| 商水县| 九龙县| 清水河县|