Android kotlin实现悬浮窗拖动,LayoutParams参数不能为负数的解决方案
2023-09-14 09:05:07 时间
目录
2.kotlin实现悬浮窗拖动,LayoutParams参数不能为负数的解决方案的核心类
3.kotlin实现悬浮窗拖动,LayoutParams参数不能为负数的解决方案的核心功能实现和分析
3.3WindowManager管理类 负责显示和关闭悬浮窗
1.概述
在用kotlin实现悬浮窗功能的时候,遇到过当设置WindowManager.LayoutParams的x坐标为负数时,它还是在左上角0的位置,x值为负数不起作用的情况,这就需要查看WindowManger的相关参数了
2.kotlin实现悬浮窗拖动,LayoutParams参数不能为负数的解决方案的核心类
frameworks/base/core/java/android/view/WindowManager.java
3.kotlin实现悬浮窗拖动,LayoutParams参数不能为负数的解决方案的核心功能实现和分析
3.1 WindowManager.java中相关常量分析
@Deprecated
public static final int FLAG_TOUCHABLE_WHEN_WAKING = 0x00000040;
/** Window flag: as long as this window is visible to the user, keep
* the device's screen turned on and bright. */
public static final int FLAG_KEEP_SCREEN_ON = 0x00000080;
/** Window flag: place the window within the entire screen, ignoring
* decorations around the border (such as the status bar). The
* window must correctly position its contents to take the screen
* decoration into account. This flag is normally set for you
* by Window as described in {@link Window#setFlags}.
*
* <p>Note: on displays that have a {@link DisplayCutout}, the window may be placed
* such that it avoids the {@link DisplayCutout} area if necessary according to the
* {@link #layoutInDisplayCutoutMode}.
*/
public static final int FLAG_LAYOUT_IN_SCREEN = 0x00000100;
/** Window flag: allow window to extend outside of the screen. */
public static final int FLAG_LAYOUT_NO_LIMITS = 0x00000200;
在WindowManager的LayoutParams的参数中可以看出FLAG_LAYOUT_NO_LIMITS常量
就是可以允许超出屏幕之外的区域显示悬浮窗
3.2 kotlin实现悬浮窗功能
自定义View
package com.pne.view
import android.content.Context
import android.os.Handler
import android.os.Message
import android.util.TypedValue
import android.view.MotionEvent
import android.view.View
import android.view.View.OnTouchListener
import android.view.ViewConfiguration
import android.view.WindowManager
import android.widget.LinearLayout
import com.pne.jnitest.R
/**
*
*/
class DragScreenView(private val mContext: Context) : LinearLayout(mContext), View.OnClickListener {
private var mWindowManager: WindowManager?=null
private var mLayoutParams: WindowManager.LayoutParams? = null
private var mLastDownTime: Long = 0
private var mLastDownX = 0f
private var mDownX = 0f
private var mLastDownY = 0f
private var mDownY = 0f
private var mIsLongTouch = false
private var mIsTouching = false
private var mTouchSlop = 0f
private var mStatusBarHeight = 0
private var mCurrentMode = 0
private val time = 0
private var mOffsetToParent = 0
private var mOffsetToParentY = 0
private var mAddCount = 0
private var mSubCount = 0
private val mHandler: Handler = object : Handler() {
override fun handleMessage(msg: Message) {
super.handleMessage(msg)
when (msg.what) {
0 -> if (mAddCount <= 20) {
mAddCount += 1
mLayoutParams!!.x = -200 + mAddCount * 10
mWindowManager?.updateViewLayout(this@DragScreenView, mLayoutParams) //不断刷新悬浮窗的位置
sendEmptyMessageDelayed(0, 50)
}
1 -> if (mSubCount <= 20) {
mSubCount += 1
mLayoutParams!!.x = -mSubCount * 10
mWindowManager?.updateViewLayout(this@DragScreenView, mLayoutParams) //不断刷新悬浮窗的位置
sendEmptyMessageDelayed(1, 50)
}
}
}
}
private fun initView() {
val view = View.inflate(context, R.layout.layout_ball, this)
mTouchSlop = ViewConfiguration.get(context).scaledTouchSlop.toFloat()
mCurrentMode = MODE_NONE
mStatusBarHeight = statusBarHeight
mOffsetToParent = dip2px(25f)
mOffsetToParentY = mStatusBarHeight + mOffsetToParent
mHandler.sendEmptyMessageDelayed(0, 200)
view.setOnTouchListener(OnTouchListener { v, event ->
when (event.action) {
MotionEvent.ACTION_DOWN -> {
mIsTouching = true
mLastDownTime = System.currentTimeMillis()
mLastDownX = event.x
mLastDownY = event.y
postDelayed({
if (isLongTouch) {
mIsLongTouch = true
}
}, LONG_CLICK_LIMIT)
}
MotionEvent.ACTION_MOVE -> {
if (!mIsLongTouch && isTouchSlop(event)) {
return@OnTouchListener true
}
if (mIsLongTouch && (mCurrentMode == MODE_NONE || mCurrentMode == MODE_MOVE)) {
// mLayoutParams.x = (int) (event.getRawX() - mLastDownX);
// mLayoutParams.y = (int) (event.getRawY() - mLastDownY);
// mWindowManager.updateViewLayout(RecordScreenView.this, mLayoutParams);//不断刷新悬浮窗的位置
mCurrentMode = MODE_MOVE
}
}
MotionEvent.ACTION_CANCEL, MotionEvent.ACTION_UP -> {
mIsTouching = false
if (mIsLongTouch) {
mIsLongTouch = false
}
mCurrentMode = MODE_NONE
mDownX = event.x
mDownY = event.y
if (mLastDownX - mDownX >= 30 && Math.abs(mDownY - mLastDownY) <= 30) {
mHandler.sendEmptyMessageDelayed(1, 100)
}
}
}
true
})
}
private val isLongTouch: Boolean
private get() {
val time = System.currentTimeMillis()
return if (mIsTouching && mCurrentMode == MODE_NONE && time - mLastDownTime >= LONG_CLICK_LIMIT) {
true
} else false
}
/**
* 判断是否是轻微滑动
*
* @param event
* @return
*/
private fun isTouchSlop(event: MotionEvent): Boolean {
val x = event.x
val y = event.y
return if (Math.abs(x - mLastDownX) < mTouchSlop && Math.abs(y - mLastDownY) < mTouchSlop) {
true
} else false
}
fun setLayoutParams(params: WindowManager.LayoutParams?) {
mLayoutParams = params
}
/**
* 获取通知栏高度
*
* @return
*/
private val statusBarHeight: Int
private get() {
var statusBarHeight = 0
try {
val c = Class.forName("com.android.internal.R\$dimen")
val o = c.newInstance()
val field = c.getField("status_bar_height")
val x = field[o] as Int
statusBarHeight = resources.getDimensionPixelSize(x)
} catch (e: Exception) {
e.printStackTrace()
}
return statusBarHeight
}
fun dip2px(dip: Float): Int {
return TypedValue.applyDimension(
TypedValue.COMPLEX_UNIT_DIP, dip, context.resources.displayMetrics
).toInt()
}
override fun onClick(view: View) {}
companion object {
private const val LONG_CLICK_LIMIT: Long = 20
private const val TIME_COUNT = 0
private const val MODE_NONE = 0x000
private const val MODE_MOVE = 0x001
}
init {
mWindowManager = context.getSystemService(Context.WINDOW_SERVICE) as WindowManager
initView()
}
}
layout_ball.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="200px"
android:layout_height="100px"
android:orientation="vertical"
android:background="@drawable/notification_bg">
<TextView
android:id="@+id/tv_itemdialog_ok"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical|center_horizontal"
android:text="ok"
android:textColor="@android:color/black"
/>
<TextView
android:id="@+id/tv_itemdialog_close"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical|center_horizontal"
android:text="cancel"
android:textColor="@android:color/black"
/>
</LinearLayout>
通过对view.setOnTouchListener的监听onTouch事件,负责对手势滑动的监听然后判断手势的x坐标滑动前后对比,然后做出左右滑动事件的响应,默认是从左边慢慢滑出悬浮窗的,左滑慢慢隐藏悬浮窗
3.3WindowManager管理类 负责显示和关闭悬浮窗
package com.pne.view
import android.content.Context
import android.graphics.PixelFormat
import android.view.Gravity
import android.view.WindowManager
object FloatWindowManager {
private var mBallView: DragScreenView? = null
private var mWindowManager: WindowManager? = null
@JvmStatic
fun addBallView(context: Context) {
if (mBallView == null) {
val windowManager = getWindowManager(context)
val screenWidth = windowManager!!.defaultDisplay.width
val screenHeight = windowManager.defaultDisplay.height
mBallView = DragScreenView(context)
val params = WindowManager.LayoutParams()
params.x = -200
params.y = 0
params.width = WindowManager.LayoutParams.WRAP_CONTENT
params.height = WindowManager.LayoutParams.WRAP_CONTENT
params.gravity = Gravity.LEFT or Gravity.TOP
params.type = WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY
params.format = PixelFormat.RGBA_8888
params.flags = (WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
or WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE or WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS)
mBallView!!.setLayoutParams(params)
windowManager.addView(mBallView, params)
}
}
fun removeBallView(context: Context) {
if (mBallView != null) {
val windowManager = getWindowManager(context)
windowManager!!.removeView(mBallView)
mBallView = null
}
}
private fun getWindowManager(context: Context): WindowManager? {
if (mWindowManager == null) {
mWindowManager = context.getSystemService(Context.WINDOW_SERVICE) as WindowManager
}
return mWindowManager
}
}
FloatWindowManager 通过WindowManager调用addBallView(context: Context)添加悬浮窗,通过
removeBallView(context: Context)移除悬浮窗功能
3.4 Activity中启用悬浮窗
import android.app.Activity;
import android.content.Intent;
import android.os.Build;
import android.os.Bundle;
import android.provider.Settings;
import android.widget.Toast;
import com.pne.view.FloatWindowManager;
public class FloatActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if (Build.VERSION.SDK_INT >= 23) {
//设置中请求开启悬浮窗权限
if (!Settings.canDrawOverlays(this)) {
Intent intent = new Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
startActivity(intent);
}else{
initView();
}
}
}
private void initView() {
FloatWindowManager.addBallView(FloatActivity.this);
finish();
}
在onCreate(Bundle savedInstanceState)中先是判断有没悬浮窗权限,然后启用悬浮窗功能
相关文章
- android开机动画多长时间_Android开机动画原理分析
- mac 电脑android环境变量设置,mac上Android环境变量配置[通俗易懂]
- android系统中toast是什么_Android个人资料简单布局
- ubuntu android studio_android自启动
- android触摸屏事件,Android Touch事件分析
- Android 编译_android线程
- android attrs获取_关于Android attrs 自定义属性的说明
- android studio与eclipse_androidstudio源码网
- Android修改字体_android设置字体样式
- Android Services Library_android freeware
- Android SDK Tools_android.intent.category.DEFAULT
- Android开发笔记(一百九十)增强了日志功能的第二版Logcat
- Android 自定义View 之 Mac地址输入框
- 【错误记录】Android Studio 编译时 Kotlin 代码编译报错 ( Not enough information to infer type variable T )
- 【Android 逆向】Android 逆向用途 | Android 逆向原理
- 【Android Gradle 插件】主工程依赖指定 Library 的特定变体 ( LibraryExtension#publishNonDefault 配置 | 依赖指定 Library 变体 )
- Android照片墙的实现详解手机开发
- [android] sharedPreference入门详解手机开发
- Android实现PHP连接MySQL进行数据交互(android通过php连接mysql)
- 解析Android开发中多点触摸的实现方法
- Android入门之ListView应用解析(一)