android 悬浮窗口和主界面同时显示,Android 悬浮窗口(及解决6.0以上无法显示问题)...
思路實現
通過WindowManager添加一個View,創建一個系統頂級的窗口,實現懸浮窗口的效果。
本篇思路,來源于郭霖大神的懸浮窗口教程。
大致介紹WindowManager 類
創建的對象:
Context.getSystemService(Context.WINDOW_SERVICE)
常用API:
addView():添加一個View對象
updateViewLayout():更新指定的View對象
removeView():移除一個View對象
使用Kotlin編程,實戰開發
1. 編寫彈窗中布局文件,item_message.xml:
android:orientation="vertical"
android:id="@+id/suspension_window_layout"
android:layout_width="wrap_content"
android:layout_height="wrap_content">
android:text="系統懸浮彈窗"
android:textColor="@android:color/white"
android:textSize="18sp"
android:padding="10dp"
android:background="@color/colorPrimary"
android:layout_height="wrap_content" />
2. 編寫懸浮彈窗的View:
定義一個布局,將對應的item_message.xml綁定上,重寫onTouche()懸浮彈窗,實現自動拖動,點擊關閉的效果。
class SuspensionWindowLayout(context: Context) : RelativeLayout(context) {
/**
* statusbar系統狀態欄的高度
*/
var statusbarHeight = 0
/**
* 窗口管理器
*/
val windowManager: WindowManager
init {
windowManager = context.getSystemService(Context.WINDOW_SERVICE) as WindowManager
var childView= View.inflate(context, R.layout.item_message,this)
widget_width = childView.suspension_window_layout.layoutParams.width
widget_height = childView.suspension_window_layout.layoutParams.height
}
/**
* 按下屏幕時手指在x,y軸上的坐標
*/
var down_x = 0.0f
var down_y = down_x
/**
* 移動時候的手指在x,y軸上的坐標
*/
var move_x = down_x
var move_y = down_x
/**
* 按下屏幕時候,控件在x,y軸位置
*/
var widget_x = down_x
var widget_y = down_x
/**
* 重寫處理拖動事件
*/
override fun onTouchEvent(event: MotionEvent): Boolean {
when (event.action) {
MotionEvent.ACTION_DOWN -> {
// 手指按下時記錄必要數據,縱坐標的值都需要減去狀態欄高度
widget_x = event.x
widget_y = event.x
//沒有移動,down->up,點擊事件
down_x = event.rawX
down_y = event.rawY - getStatusBarHeight()
move_x = event.rawX
move_y = event.rawY - getStatusBarHeight()
}
MotionEvent.ACTION_MOVE -> {
// 手指移動的時候更新小懸浮窗的位置
move_x = event.rawX
move_y = event.rawY - getStatusBarHeight()
updateWidgetPostion()
}
MotionEvent.ACTION_UP -> {//
//坐標沒有改變,是點擊動作
if (move_x == down_x && move_y == down_y) {
SuspensionWindowManagerUtils.removeSuspensionWindow(context)
}
}
else -> {
}
}
return true
}
/**
* 更新控件位置,在x,y軸的的位置
*/
fun updateWidgetPostion() {
var layoutParams = SuspensionWindowManagerUtils.getWidgetLayoutParams()
layoutParams!!.x = (move_x - widget_x).toInt()
layoutParams!!.y = (move_y - widget_y).toInt()
windowManager.updateViewLayout(this, layoutParams)
}
/**
* 獲取系統狀態欄,返回狀態欄高度的像素值
*/
fun getStatusBarHeight(): Int {
if (statusbarHeight == 0) {
statusbarHeight = resources.getDimensionPixelSize(ViewUtils.getStatusBarHeight())
}
return statusbarHeight
}
companion object {
var widget_width = 0
var widget_height = 0
}
}
一個工具類:
public class ViewUtils {
/**
* 反射獲取狀態欄高度
*@return
*/
public static int getStatusBarHeight(){
int x=0;
try {
Class> c = Class.forName("com.android.internal.R$dimen");
Object o = c.newInstance();
Field field = c.getField("status_bar_height");
x = (Integer) field.get(o);
} catch (Exception e) {
e.printStackTrace();
}
return x;
}
}
3. 編寫WindowManager工具類:
一些列,檢查彈窗,開啟彈窗,關閉彈窗的操作封裝到該類中。
class SuspensionWindowManagerUtils {
companion object {
var windowManager: WindowManager?=null
var layoutParams: WindowManager.LayoutParams?=null
var suspensionWindowWidget: SuspensionWindowLayout? = null
/**
* 創建懸浮窗口
*/
@JvmStatic
fun createSuspensionWindow(context: Context) {
if (suspensionWindowWidget==null){
suspensionWindowWidget= SuspensionWindowLayout(context)
}
getWindowManager(context)!!.addView(suspensionWindowWidget, getWidgetLayoutParams())
}
/**
* 移除懸浮窗口
*/
fun removeSuspensionWindow(context: Context) {
if (suspensionWindowWidget != null) {
getWindowManager(context)!!.removeView(suspensionWindowWidget)
suspensionWindowWidget = null
}
}
/**
* 懸浮窗口是否已經打開
*/
fun windowIsOpen():Boolean{
if (suspensionWindowWidget!=null)
return true
else return false
}
/**
* 獲取懸浮窗口的布局參數
*/
fun getWidgetLayoutParams(): WindowManager.LayoutParams? {
if (layoutParams == null) {
layoutParams = WindowManager.LayoutParams()
layoutParams!!.type = WindowManager.LayoutParams.TYPE_PHONE
layoutParams!!.format = PixelFormat.RGBA_8888
layoutParams!!.flags = WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL or WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
layoutParams!!.gravity = Gravity.LEFT or Gravity.TOP
layoutParams!!.x = windowManager!!.defaultDisplay.width
layoutParams!!.y =0
layoutParams!!.width = SuspensionWindowLayout.widget_width
layoutParams!!.height = SuspensionWindowLayout.widget_height
}
return layoutParams
}
/**
* 獲取窗口管理器
*/
fun getWindowManager(context: Context): WindowManager ?{
if (windowManager == null) {
windowManager = context.getSystemService(Context.WINDOW_SERVICE) as WindowManager
}
return windowManager
}
}
}
4. 開啟一個懸浮窗口:
先進行判斷,若是懸浮彈窗為未開啟,則進行開啟。
import kotlinx.android.synthetic.main.activity_main.*
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
//點擊開啟懸浮窗口
main_open_window.setOnClickListener {
requestPermission()
}
}
/**
* 開啟懸浮彈窗
*/
fun openSuspensionWindow(){
//未開啟窗口,則開啟
if (!SuspensionWindowManagerUtils.windowIsOpen()) {
SuspensionWindowManagerUtils.createSuspensionWindow(applicationContext)
}
}
}
5. 在AndroidManifest.xml中添加系統彈窗權限:
6. Android 5.1及其以下系統,運行效果:
在模擬器上運行無問題,但是在紅米手機上出現問題。
紅米手機需要先開啟懸浮權限:顯示懸浮窗–>允許。
錄制效果如下:
授權 SYSTEM_ALERT_WINDOW Permission 在 android 6.0 及其以上版本的系統
運行設備:
AndroidStudio 自帶的模擬器,其API 24。
運行結果:
在輸出臺上提示以下錯誤:
Caused by: android.view.WindowManager$BadTokenException: Unable to add window
android.view.ViewRootImpl$W@b9261f -- permission denied for window type 2002
at android.view.ViewRootImpl.setView(ViewRootImpl.java:702)
at android.view.WindowManagerGlobal.addView(WindowManagerGlobal.java:342)
at android.view.WindowManagerImpl.addView(WindowManagerImpl.java:93)
查看SYSTEM_ALERT_WINDOW權限,可知:
若是運用程序的目標API在23及其以上,程序需要通過權限管理界面,開啟授權。程序發送ACTION_MANAGE_OVERLAY_PERMISSION的動作,使用Settings.canDrawOverlays()來檢查是否授權。
解決方式:
1. 檢查權限:
使用Settings.canDrawOverlays()來檢查是否授權。
/**
* 當目標版本大于23時候,檢查權限
*/
fun checkPermission():Boolean{
if (Build.VERSION.SDK_INT>=23)
return Settings.canDrawOverlays(this)
else
return true
}
2. 用戶授權:
發送ACTION_MANAGE_OVERLAY_PERMISSION,開啟權限授權界面,用戶授權,允許懸浮在運用程序之上。
/**
* 申請權限的狀態code
*/
var request_code=1
/**
* 開啟權限管理界面,授權。
*/
fun requestPermission(){
var intent=Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION, Uri.parse("package:$packageName"))
startActivityForResult(intent,request_code)
}
3. 響應授權結果:
使用Settings.canDrawOverlays()來檢查授權結果,用戶在管理界面是否授權。
/**
* 回調申請結果
*/
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
when(requestCode) {
request_code -> {
if (checkPermission()) { //用戶授權成功
openSuspensionWindow()
} else { //用戶拒絕授權
Toast.makeText(application, "彈窗權限被拒絕", Toast.LENGTH_SHORT).show()
}
}
}
super.onActivityResult(requestCode, resultCode, data)
}
4. 在Android6.0及其以上,運行效果:
在7.0系統模擬器上,API24運行項目,效果如下
項目代碼:https://github.com/13767004362/SuspensionWindowDemo
資源參考:
郭大大的教程:http://blog..net/guolin_blog/article/details/8689140/
創作挑戰賽新人創作獎勵來咯,堅持創作打卡瓜分現金大獎總結
以上是生活随笔為你收集整理的android 悬浮窗口和主界面同时显示,Android 悬浮窗口(及解决6.0以上无法显示问题)...的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: android第三方登录appid,An
- 下一篇: android 弹窗in,Android