技术标签: 浮窗不显示 addView Android Kotlin
看得到的真相往往可能是骗人的,但是代码不会. 如果程序中出现了”灵异”事件,一定是问题没有分析到位,亦或是分析问题的方向出错
最近在项目中,我需要在一个独立进程的不死服务中打开一个浮窗,但是浮窗不显示.但是在Activity中可以打开浮窗.这是问题的表现,其中不同点是创建WindowManager的Context不同,使用Activity的Context可以,使用Service和Application的Context都不可以.通过Log日志可以发现,addView()方法是执行了,但是View并没有显示出来.接下来,我就进行了各方位的尝试
屏蔽浮窗移除代码浮窗还是没有显示
代码如下,浮窗还是没有出现
class FloatWindowService : Service() {
companion object {
private const val TAG = "DaemonService"
const val NOTICE_ID = 100
fun openFloatWindowService(mContext: Context) {
val intent = Intent(mContext, FloatWindowService::class.java)
intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK
mContext.startService(intent)
}
}
private val TAG = "FloatWindowManager"
/**
* 小悬浮窗View的实例
*/
@SuppressLint("StaticFieldLeak")
private var smallWindow: FloatWindowSmallView? = null
/**
* 小悬浮窗View的参数
*/
private var smallWindowParams: WindowManager.LayoutParams? = null
/**
* 用于控制在屏幕上添加或移除悬浮窗
*/
private var mWindowManager: WindowManager? = null
/**
* 是否有悬浮窗(包括小悬浮窗和大悬浮窗)显示在屏幕上。
*
* @return 有悬浮窗显示在桌面上返回true,没有的话返回false。
*/
var isWindowShowing: Boolean = false
override fun onCreate() {
super.onCreate()
smallWindow = FloatWindowSmallView(applicationContext)
smallWindowParams = WindowManager.LayoutParams()
mWindowManager = getWindowManager(applicationContext)
setSmallWindowParams(mWindowManager!!, smallWindowParams!!)
}
private fun setSmallWindowParams(windowManager: WindowManager,smallWindowParams: WindowManager.LayoutParams) {
val outMetrics = DisplayMetrics()
windowManager.defaultDisplay.getMetrics(outMetrics)
val screenWidth = outMetrics.widthPixels
val screenHeight = outMetrics.heightPixels
smallWindowParams!!.type = WindowManager.LayoutParams.TYPE_PHONE
// smallWindowParams!!.format = PixelFormat.RGBA_8888
smallWindowParams.format = PixelFormat.TRANSPARENT
smallWindowParams!!.flags = WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL or WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
smallWindowParams!!.gravity = Gravity.LEFT or Gravity.TOP
smallWindowParams!!.width = 600
smallWindowParams!!.height = 600
smallWindowParams!!.x = 0
smallWindowParams!!.y = 0
}
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
smallWindow!!.setParams(smallWindowParams!!)
mWindowManager!!.addView(smallWindow, smallWindowParams)
return super.onStartCommand(intent, flags, startId)
}
/**
* 如果WindowManager还未创建,则创建一个新的WindowManager返回。否则返回当前已创建的WindowManager。
*
* @param context 必须为应用程序的Context.
* @return WindowManager的实例,用于控制在屏幕上添加或移除悬浮窗。
*/
private fun getWindowManager(context: Context): WindowManager {
if (mWindowManager == null) {
mWindowManager = context.getSystemService(Context.WINDOW_SERVICE) as WindowManager
}
return mWindowManager!!
}
override fun onBind(intent: Intent?): IBinder {
TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
}
}
在我们平时写代码的时候,经常要用到windowmanager这个类,一般是通过addview的方式,把一个view添加到一个窗口中,在系统中,比如toast,悬浮框,状态栏,通知栏,锁屏界面他们都不是平常的activity中的window,因此通过windowmanager 直接addview的方式来呈现给用户
WindowManager是一个接口,并继承了ViewManager接口.ViewManager有三个方法,
addView
,updateViewLayout
,removeView
,分别用于添加View,更新View的布局和移除View,由于WindowManager继承ViewManager,可见WindowManager也是对View进行管理,其中WindowManager有多了一个removeViewImmediate()
用于立刻移除View,用于Activity中,防止Activity销毁之后造成窗口溢出
public interface ViewManager
{
public void addView(View view, ViewGroup.LayoutParams params);
public void updateViewLayout(View view, ViewGroup.LayoutParams params);
public void removeView(View view);
}
而从打印的Log信息来看,addView()
是执行成功,没有异常的,所以浮窗是添加成功的.但是在创建浮窗和移除浮窗的时候,一定要先确认浮窗addView.如果浮窗已经添加了,再次添加会报异常;如果浮窗没有添加,在执行removeView(View view)
会报异常
(1)将浮窗layout中的宽高赋值,而不是wrap_content
(2)将addView(View view, ViewGroup.LayoutParams params)
浮窗params的宽高写死为200,但是还是没有显示.但是点击浮窗所在的位置,会执行updateViewLayout
方法和浮窗的点击事件,由此更加确认浮窗已经存在
setBackgroundColor(Color.GREEN)
这个时候出现了一个绿色的方块儿区域,但是图片还是没有显示出来
背景显示出来了,图片未显示
app:srcCompat
换为android:src
图片显示出来了,本来直接给出结果就可以了,但是我还是梳理了整个分析的流程.
接下来就是揭晓原因的时候了
使用 app:srcCompat 的时候 引入的图片显示不出来的解决方案
首先查看的你的Activity 继承的是那个Activity 如果是继承AppcompatActivity 使用 ImageView的 app:srcCompat 是没有问题的
如果你的Activity不是继承的AppcompatActivity, 需要用到 android.support.v7.widget.AppCompatImageView 代替 ImageView。
这样就可以解释为什么我可以通过Activity打开浮窗,而Service和Application的Context都不可以,当然我的Activity是继承AppcompatActivity.这里有两种修改方式,如下
方法一:修改ImageView
为AppCompatImageView
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:orientation="vertical">
<androidx.appcompat.widget.AppCompatImageView
android:id="@+id/athanasy_float_window"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:srcCompat="@mipmap/ic_launcher_round"
/>
</LinearLayout>
方法二:修改 app:srcCompat
为“““
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:orientation="vertical">
<ImageView
android:id="@+id/athanasy_float_window"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@mipmap/ic_launcher_round"
/>
</LinearLayout>
package com.hp.athanasyservice.xiaoqi.floatwindow
import android.content.Context
import android.view.LayoutInflater
import android.view.MotionEvent
import android.view.WindowManager
import android.widget.LinearLayout
import com.billy.cc.core.component.CC
import com.blankj.utilcode.util.ReflectUtils
import com.hp.athanasyservice.R
import com.hp.baseres.base.BaseApplication.Companion.mContext
import com.hp.baseres.utils.LogUtils
import com.hp.business.AppCCConstants
import kotlinx.android.synthetic.main.athanasy_small_float_window.view.*
/**
* Created by tangdekun on 2017/6/2.
*/
class FloatWindowSmallView(mContext: Context) : LinearLayout(mContext) {
/**
* 用于更新小悬浮窗的位置
*/
private val windowManager: WindowManager = mContext.getSystemService(Context.WINDOW_SERVICE) as WindowManager
/**
* 小悬浮窗的参数
*/
private var mParams: WindowManager.LayoutParams? = null
/**
* 记录当前手指位置在屏幕上的横坐标值
*/
private var xInScreen: Float = 0.toFloat()
/**
* 记录当前手指位置在屏幕上的纵坐标值
*/
private var yInScreen: Float = 0.toFloat()
/**
* 记录手指按下时在屏幕上的横坐标的值
*/
private var xDownInScreen: Float = 0.toFloat()
/**
* 记录手指按下时在屏幕上的纵坐标的值
*/
private var yDownInScreen: Float = 0.toFloat()
/**
* 记录手指按下时在小悬浮窗的View上的横坐标的值
*/
private var xInView: Float = 0.toFloat()
/**
* 记录手指按下时在小悬浮窗的View上的纵坐标的值
*/
private var yInView: Float = 0.toFloat()
private var screenWidth: Float = 0.toFloat()
private var screenHeight: Float = 0.toFloat()
init {
LayoutInflater.from(mContext).inflate(R.layout.athanasy_small_float_window, this)
viewWidth = athanasy_float_window.layoutParams.width
viewHeight = athanasy_float_window.layoutParams.height
}
override fun onTouchEvent(event: MotionEvent): Boolean {
when (event.action) {
MotionEvent.ACTION_DOWN -> {
// 手指按下时记录必要数据,纵坐标的值都需要减去状态栏高度
xInView = event.x
yInView = event.y
xDownInScreen = event.rawX
yDownInScreen = event.rawY - getStatusBarHeight()
xInScreen = event.rawX
yInScreen = event.rawY - getStatusBarHeight()
}
MotionEvent.ACTION_MOVE -> {
xInScreen = event.rawX
yInScreen = event.rawY - getStatusBarHeight()
// 手指移动的时候更新小悬浮窗的位置
updateViewPosition()
}
MotionEvent.ACTION_UP -> {
val t = System.currentTimeMillis().toFloat()
// 如果手指离开屏幕时,xDownInScreen和xInScreen相等,且yDownInScreen和yInScreen相等,则视为触发了单击事件。
updateViewPositionFinish()
LogUtils.d(TAG, "updateViewPositionFinish():" + (System.currentTimeMillis() - t))
if (xDownInScreen == xInScreen && yDownInScreen == yInScreen) {
openBigWindow()
}
LogUtils.d(TAG, "openBigWindow():" + (System.currentTimeMillis() - t))
}
}
return true
}
/**
* 更新浮窗移动完毕的位置
*/
private fun updateViewPositionFinish() {
mParams!!.y = (yInScreen - yInView).toInt()
screenWidth = windowManager.defaultDisplay.width.toFloat()
screenHeight = windowManager.defaultDisplay.height.toFloat()
if (xInScreen <= screenWidth / 2) {
mParams!!.x = 0
} else {
mParams!!.x = screenWidth.toInt()
}
windowManager.updateViewLayout(this, mParams)
mXProportion = mParams!!.x / screenWidth
mYProportion = mParams!!.y / screenHeight
}
/**
* 将小悬浮窗的参数传入,用于更新小悬浮窗的位置。
*
* @param params 小悬浮窗的参数
*/
fun setParams(params: WindowManager.LayoutParams) {
mParams = params
}
/**
* 更新小悬浮窗在屏幕中的位置。
*/
private fun updateViewPosition() {
mParams!!.x = (xInScreen - xInView).toInt()
mParams!!.y = (yInScreen - yInView).toInt()
windowManager.updateViewLayout(this, mParams)
}
/**
* 打开Activity
*/
private fun openBigWindow() {
CC.obtainBuilder(AppCCConstants.COMPONENT_NAME)
.setActionName(AppCCConstants.ACTION_OPEN_MAINACTIVITY)
.setContext(mContext)
.build()
.call()
}
/**
* 用于获取状态栏的高度。
*
* @return 返回状态栏高度的像素值。
*/
private fun getStatusBarHeight(): Int {
if (statusBarHeight == 0) {
try {
val x = ReflectUtils.reflect("com.android.internal.R\$dimen").field("status_bar_height").get<Int>()
statusBarHeight = resources.getDimensionPixelSize(x)
} catch (e: Exception) {
e.printStackTrace()
}
}
return statusBarHeight
}
companion object {
val TAG = FloatWindowSmallView::class.java.name!!
/**
* 记录每次浮窗当前的位置 --每次切换旋转屏幕按比例跳转位置
*/
var mXProportion = 1f
var mYProportion = 0.5f
/**
* 记录小悬浮窗的宽度
*/
var viewWidth: Int = 0
/**
* 记录小悬浮窗的高度
*/
var viewHeight: Int = 0
/**
* 记录系统状态栏的高度
*/
private var statusBarHeight: Int = 0
}
}
package com.hp.athanasyservice.xiaoqi.floatwindow
import android.annotation.SuppressLint
import android.content.Context
import android.graphics.PixelFormat
import android.util.DisplayMetrics
import android.view.Gravity
import android.view.WindowManager
import android.view.WindowManager.LayoutParams
import com.hp.baseres.utils.LogUtils
/**
* Created by tangdekun on 2017/6/2.
*/
object FloatWindowManager {
private val TAG = "FloatWindowManager"
/**
* 小悬浮窗View的实例
*/
@SuppressLint("StaticFieldLeak")
private var smallWindow: FloatWindowSmallView? = null
/**
* 小悬浮窗View的参数
*/
private var smallWindowParams: LayoutParams? = null
/**
* 用于控制在屏幕上添加或移除悬浮窗
*/
private var mWindowManager: WindowManager? = null
/**
* 是否有悬浮窗(包括小悬浮窗和大悬浮窗)显示在屏幕上。
*
* @return 有悬浮窗显示在桌面上返回true,没有的话返回false。
*/
var isWindowShowing: Boolean = false
/**
* 创建一个小悬浮窗。初始位置为屏幕的右部中间位置。
*
* @param context 必须为应用程序的Context.
*/
fun createSmallWindow(context: Context) {
val windowManager = getWindowManager(context)
fun addView(context: Context, windowManager: WindowManager) {
LogUtils.dTag(TAG, "创建浮窗$context")
if (smallWindow == null) {
smallWindow = FloatWindowSmallView(context)
if (smallWindowParams == null) {
setSmallWindowParams(context)
}
smallWindow!!.setParams(smallWindowParams!!)
LogUtils.dTag(TAG, smallWindowParams.toString())
}
windowManager.addView(smallWindow, smallWindowParams)
isWindowShowing = true
}
if (!isWindowShowing) {
addView(context, windowManager)
} else {
LogUtils.dTag(TAG, "浮窗已存在$context")
}
}
/**
* 重置小浮窗位置
*
* @param context
*/
fun resetSmallWindowPosition(context: Context) {
LogUtils.dTag(TAG, "resetSmallWindowPosition")
if (smallWindowParams != null) {
val windowManager = getWindowManager(context)
val outMetrics = DisplayMetrics()
windowManager.defaultDisplay.getMetrics(outMetrics)
val screenWidth = outMetrics.widthPixels
val screenHeight = outMetrics.heightPixels
smallWindowParams!!.x = (FloatWindowSmallView.mXProportion * screenWidth).toInt()
smallWindowParams!!.y = (FloatWindowSmallView.mYProportion * screenHeight).toInt()
if (smallWindow != null) {
getWindowManager(context).updateViewLayout(smallWindow, smallWindowParams)
}
}
}
private fun setSmallWindowParams(context: Context) {
val windowManager = getWindowManager(context)
val outMetrics = DisplayMetrics()
windowManager.defaultDisplay.getMetrics(outMetrics)
val screenWidth = outMetrics.widthPixels
val screenHeight = outMetrics.heightPixels
smallWindowParams = LayoutParams()
smallWindowParams!!.type = WindowManager.LayoutParams.TYPE_PHONE
smallWindowParams!!.format = PixelFormat.RGBA_8888
smallWindowParams!!.flags = WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL or WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
smallWindowParams!!.gravity = Gravity.LEFT or Gravity.TOP
smallWindowParams!!.width = FloatWindowSmallView.viewWidth
smallWindowParams!!.height = FloatWindowSmallView.viewHeight
smallWindowParams!!.x = screenWidth
smallWindowParams!!.y = screenHeight / 2
}
/**
* 将小悬浮窗从屏幕上移除。
*
* @param context 必须为应用程序的Context.
*/
fun removeSmallWindow(context: Context) {
if (smallWindow != null && isWindowShowing) {
LogUtils.dTag(TAG, "删除浮窗$context")
val windowManager = getWindowManager(context)
windowManager.removeView(smallWindow)
isWindowShowing = false
smallWindow = null
smallWindowParams = null
} else {
LogUtils.dTag(TAG, "$context 浮窗不存在,无法删除")
}
}
/**
* 如果WindowManager还未创建,则创建一个新的WindowManager返回。否则返回当前已创建的WindowManager。
*
* @param context 必须为应用程序的Context.
* @return WindowManager的实例,用于控制在屏幕上添加或移除悬浮窗。
*/
private fun getWindowManager(context: Context): WindowManager {
if (mWindowManager == null) {
mWindowManager = context.getSystemService(Context.WINDOW_SERVICE) as WindowManager
}
return mWindowManager!!
}
}
文章浏览阅读731次。2016-05-24 14:52:52 浏览量:991win7系统安装显卡后出现蓝屏问题怎么办呢?其实安装显卡后出现蓝屏也是比较常见的故障问题,网上也讲解很多关于win7系统安装显卡后出现蓝屏的资讯,可能是驱动不兼容显卡导致,如何解决呢?大家可以在win7系统开机时进入安全模式来设置与修复,轻松解决问题,下面来看看详细设置步骤吧。2017-06-23 09:51:30 浏览量:1728Win7..._华为服务器安装win7蓝屏
文章浏览阅读8k次,点赞67次,收藏82次。Redis是用C语言开发的一个开源的高性能键值对(key-value)数据库。本期文章将带你一气呵成,了解Redis安装与连接_redis连接
文章浏览阅读900次,点赞21次,收藏18次。本项目为参加分类考试的学生提供一个了解学校及专业的平台,以各个学校专业的信息和特点为基础,并让学生从自身的兴趣爱好出发,有效减少学生选择学校专业时的盲目性,让学生自主地选择适合自身的学校专业。在本项目的开发过程中,通过对项目的需求进行详细分析后,得到主要的功能模块,并对每一个功能模块进行细化。针对于参加分类招生考试的学生了解高职院校方式单一的现状,本项目为参加分类招生考试的学生提供一个了解学校、专业的平台,以及辅助学生了解自身喜好,搭建学生和学校间的桥梁,解决了分类考试中学生选择学校专业过程里的需求缺口。
文章浏览阅读93次。1. 高精度问题 可参考题目题目1137:浮点数加法 http://ac.jobdu.com/problem.php?pid=1137 对于高精度问题可以考虑使用结构体。上述为浮点数加法,因此该结构体可以具有保存小数部分以及整数部分的成员变量,使用数组进行保存。于此同时还要记录下所保存数据的长度,因此还应当有两个int变量,保存整数部分长度和保存小数部分长度。 ...
文章浏览阅读70次。java计算机毕业设计物业服务管理系统源码+数据库+系统+lw文档+mybatis+运行部署。ssm基于SSM+VUE技术的小区车辆档案车位管理系统设计与实现。ssm+sqlserver高校教室多媒体设备报修管理系统。JSP+sql图书管理系统(源程序+论文+数据库+录像)ssm+sqlserver海城同泽中学图书仓库管理系统。ssm基于Vue的潍坊学院宿舍管理系统的设计与实现。ssm+sqlserver多媒体素材管理系统。springboot疫情返乡人员管理系统。ssm基于ssm架构的校园二手物品交易。.
文章浏览阅读596次,点赞12次,收藏11次。基于Laravel和Vue的快速开发的后台管理系统。支持php8.0版本533738074加群请备注来源:如gitee、github、官网等。_vmware17 共享文件夹
文章浏览阅读1k次。RIL_UNSOL_PHYSICAL_CHANNEL_CONFIGPhysicalChannelConfig.javaLTE下radio log搜RIL_UNSOL_PHYSICAL_CHANNEL_CONFIG,查看是否有以下字符串mConnectionStatus=SecondaryServing,mRat=20,有的话就是NSA_physicalchannelconfig.java
文章浏览阅读1k次。若依 apifox 自动登录_若依 自动登陆
文章浏览阅读207次。这题是挑战程序设计竞赛的例题,因为师兄讲了所以就补了做法:建图的方法是:加一个源点和汇点,讲饮料或食物分别连向源点、汇点,对于每一头牛,把它和它喜欢的食物和饮料连边,由于每一头牛在考虑的时候都是只能被一个边流入和流出(因为每一头牛都是只能吃一个食物喝一个饮料),所以把牛拆点(假如拆成牛1,牛2),就在牛1和牛2之间连一条边。然后源点到汇点跑一遍dinic就可以了。代码:#p..._3281dining 挑程
文章浏览阅读484次。四 HDFS 的数据流4.1 HDFS 写数据流程4.1.1 剖析文件写入 1)客户端通过 Distributed FileSystem 模块向 namenode 请求上传文件,namenode 检查目标文件是否已存在,父目录是否存在。2)namenode 返回是否可以上传。3)客户端请求第一个 block 上传到哪几个 datanode 服务器上。4)namenod..._pwd /opt/module/hadoop-2.7.2/data/tmp/dfs/name/current
文章浏览阅读8k次。<!DOCTYPE html><html><head> <style> div { width: 60px; height: 60px; margin: 10px; float: left; border: 2px solid blue;}.blue { background: blue;}.w..._用if编写点击div改变宽高颜色
文章浏览阅读776次。1、MQ消息体中某些必填参数为NULL,或者全部必填为NULL,字段类型、长度是否不符合约定2、MQ消息体中参数位置错误3、消息重复发送,只消费一条 —幂等性一般根据消息内容中唯一标识来去重4、消息到达顺序不一致,导致业务异常比如业务是有先后顺序的案例1:订单下单后再取消,如果先收到取消的消息,再收到下单消息,就会有问题案例2:一条政策新增后马上删除,政策同步时,政策删除的消息先到达,新增的消息后到,就会导致最小价该条政策没删除,只能等全量同步的时候再删除5、消息发送失败,重试次数1)Pr_mq发送消息illegal parameter, param is someone