主页面布局很简单,只有一个RelativelyLayout

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
android:id="@+id/rl_content"
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingBottom="@dimen/activity_vertical_margin"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
tools:context="example.floatingviewtest.MainActivity">
</RelativeLayout>

悬浮窗中只有一个TextView

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="wrap_content"
android:layout_height="wrap_content"> <TextView
android:id="@+id/tv_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="这是一个悬浮TextView"
android:paddingTop="30dp"
android:paddingBottom="30dp"
android:background="@color/colorPrimary"
android:textSize="15sp"/> </LinearLayout>

主界面代码

public class MainActivity extends AppCompatActivity {
private static final String TAG = "MainActivity";
/**
* 获取sdk版本号
*/
private static final int SDKVERSION = Build.VERSION.SDK_INT;
/**
* 窗口管理器
*/
private WindowManager windowManager;
/**
* 浮动按钮布局
*/
private View floatingButtonView;
/**
* 浮动按钮布局参数
*/
private WindowManager.LayoutParams floatingButtonParams;
/**
* 顶部状态栏高度
*/
private int top;
/**
* 浮动窗原始位置
*/
private float startPositionX = 0;
private float startPositionY = 0;
/**
* 屏幕宽高
*/
private int contentWidth;
private int contentHeight; private float lastX;
private float lastY;
private float mTouchStartX;
private float mTouchStartY; @Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
windowManager = (WindowManager) getSystemService(WINDOW_SERVICE); initFloatingButton();
} private void initFloatingButton() {
//浮动按钮布局
floatingButtonView = LayoutInflater.from(this).inflate(R.layout.floating_view, null); floatingButtonParams = new WindowManager.LayoutParams();
if (SDKVERSION >= 19) {
floatingButtonParams.type = WindowManager.LayoutParams.TYPE_TOAST;
} else {
floatingButtonParams.type = WindowManager.LayoutParams.TYPE_SYSTEM_ALERT;
}
floatingButtonParams.flags = WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL | WindowManager.LayoutParams
.FLAG_NOT_FOCUSABLE;
floatingButtonParams.format = PixelFormat.TRANSLUCENT;
floatingButtonParams.width = WindowManager.LayoutParams.WRAP_CONTENT;
floatingButtonParams.height = WindowManager.LayoutParams.WRAP_CONTENT; floatingButtonParams.gravity = Gravity.TOP| Gravity.LEFT;
floatingButtonParams.x = 0;
floatingButtonParams.y = 0; Log.d(TAG, "initFloatingButton: " + floatingButtonParams.x + " " + floatingButtonParams.y);
windowManager.addView(floatingButtonView, floatingButtonParams);
floatingButtonView.setOnTouchListener(new View.OnTouchListener() { @Override
public boolean onTouch(View v, MotionEvent event) {
lastX = event.getRawX();
lastY = event.getRawY() - top;
switch (event.getActionMasked()) {
case MotionEvent.ACTION_DOWN:
mTouchStartX = event.getX();
mTouchStartY = event.getY();
//记录悬浮窗原始位置
startPositionX = floatingButtonParams.x;
startPositionY = floatingButtonParams.y;
Log.d(TAG, "onTouch down : m " + mTouchStartX + " " + mTouchStartY);
Log.d(TAG, "onTouch down : last " + lastX + " " + lastY);
Log.d(TAG, "onTouch down : start " + startPositionX + " " + startPositionY);
Log.d(TAG, "onTouch down : Params " + floatingButtonParams.x + " " + floatingButtonParams
.y);
break;
case MotionEvent.ACTION_MOVE:
//计算新的位置
floatingButtonParams.x = (int) (lastX - mTouchStartX);
floatingButtonParams.y = (int) (lastY - mTouchStartY); //如果原始位置在中间,所以需要减去屏幕宽高的一半
// floatingButtonParams.x = (int) (lastX - mTouchStartX - contentWidth / 2);
// floatingButtonParams.y = (int) (lastY - mTouchStartY - contentHeight / 2); Log.d(TAG, "onTouch move : m " + mTouchStartX + " " + mTouchStartY);
Log.d(TAG, "onTouch move : last " + lastX + " " + lastY);
Log.d(TAG, "onTouch move : start " + startPositionX + " " + startPositionY);
Log.d(TAG, "onTouch move : Params " + floatingButtonParams.x + " " + floatingButtonParams
.y);
//
windowManager.updateViewLayout(floatingButtonView, floatingButtonParams);
break;
case MotionEvent.ACTION_UP:
Log.d(TAG, "onTouch up : m " + mTouchStartX + " " + mTouchStartY);
Log.d(TAG, "onTouch up : last " + lastX + " " + lastY);
Log.d(TAG, "onTouch up : start " + startPositionX + " " + startPositionY);
Log.d(TAG, "onTouch up : Params " + floatingButtonParams.x + " " + floatingButtonParams.y);
if (Math.abs(floatingButtonParams.x - startPositionX) < 20 && Math.abs(floatingButtonParams.y
- startPositionY) < 20) {
Toast.makeText(MainActivity.this, "click", Toast.LENGTH_LONG);
}
break;
}
return false;
}
});
} @Override
public void onWindowFocusChanged(boolean hasFocus) {
super.onWindowFocusChanged(hasFocus); //获取整个布局的宽高
Point size = new Point();
windowManager.getDefaultDisplay().getSize(size);
contentWidth = size.x;
contentHeight = size.y;
//下面这两个方法已经不建议使用了
// windowManager.getDefaultDisplay().getWidth();
// windowManager.getDefaultDisplay().getHeight();
Rect rect = new Rect();
// /取得整个视图部分,注意,如果你要设置标题样式,这个必须出现在标题样式之后,否则会出错
getWindow().getDecorView().getWindowVisibleDisplayFrame(rect);
top = rect.top;//状态栏的高度,所以rect.height,rect.width分别是系统的高度的宽度
Log.d(TAG, "onWindowFocusChanged: " + contentWidth + " " + contentHeight + " " + top);
}
}

以上代码主要参考了这篇博客

【【Android Demo】悬浮窗体实现】http://www.cnblogs.com/yc-755909659/p/4281214.html。

这篇博客【android悬浮窗口的实现】http://blog.csdn.net/stevenhu_223/article/details/8504058也是关于悬浮窗的,里面有源码分析,讲的更深入一些,还没来得及学习。

但是上述代码有点问题,如果将悬浮窗的初始位置设为Gravity.CENTER,在拖动的最开始会有个抖动。如果将主题设为没有title的主题,然后出去界面的宽高,在拖动的时候减去宽高的一半,则没有抖动。可是加上有title的主题后,会有微小的抖动。这一bug暂未修复。

为什么悬浮窗的属性要设置成TYPE_TOAST,请看这里

1、使用 type 值为 WindowManager.LayoutParams.TYPE_PHONE 和 WindowManager.LayoutParams.TYPE_SYSTEM_ALERT 需要申请 android.permission.SYSTEM_ALERT_WINDOW 权限,否则无法显示,报错:

E/AndroidRuntime: android.view.WindowManager$BadTokenException: Unable to add window android.view.ViewRoot$W@b64b5458 -- permission denied for this window type

2、type 值为 WindowManager.LayoutParams.TYPE_TOAST 显示的 System overlay view 不需要权限,即可在任何平台显示。

但仅在 API level >= 19 时可以达到目的。API level 19 以下因无法接收无法接收触摸(点击)和按键事件,故无法达到目的。

3、对于 API level < 19 的机器(MIUI除外),想要达到目的,需要:

要有 android.permission.SYSTEM_ALERT_WINDOW 权限

将 type 设置为 WindowManager.LayoutParams.TYPE_PHONE 或者 WindowManager.LayoutParams.TYPE_SYSTEM_ALERT

具体参考如下两篇博客

Android悬浮窗的小结:http://liaohuqiu.net/cn/posts/android-windows-manager/

Android悬浮窗TYPE_TOAST小结: 源码分析:http://www.jianshu.com/p/634cd056b90c

Android悬浮窗及其拖动事件的更多相关文章

  1. Android 悬浮窗、悬浮球开发

    原文:Android 悬浮窗.悬浮球开发 1.权限管理 直接看我另外一篇博客吧,传送门: https://my.oschina.net/u/1462828/blog/1933162 2.Base类Ba ...

  2. Android悬浮窗实现 使用WindowManager

    Android悬浮窗实现 使用WindowManager WindowManager介绍 通过Context.getSystemService(Context.WINDOW_SERVICE)可以获得  ...

  3. Android 悬浮窗 System Alert Window

    悬浮窗能显示在其他应用上方.桌面系统例如Windows,macOS,Ubuntu,打开的程序能以窗口形式显示在屏幕上. 受限于屏幕大小,安卓系统中主要使用多任务切换的方式和分屏的方式.视频播放,视频对 ...

  4. Android 悬浮窗权限校验

    原文:Android 悬浮窗权限校验 悬浮窗权限: <uses-permission android:name="android.permission.SYSTEM_ALERT_WIN ...

  5. Android长按及拖动事件探究

    Android中长按拖动还是比较常见的.比如Launcher中的图标拖动及屏幕切换,ListView中item顺序的改变,新闻类App中新闻类别的顺序改变等.下面就这个事件做一下分析. 就目前而言,A ...

  6. Android 悬浮窗

    悬浮窗是一种比较常见的需求.例如把视频通话界面缩小成一个悬浮窗,然后用户可以在其他界面上处理事情. 本文给出一个简单的悬浮窗实现.可缩小activity和还原大小.可悬浮在其他activity上.使用 ...

  7. Android 悬浮窗权限各机型各系统适配大全

    这篇博客主要介绍的是 Android 主流各种机型和各种版本的悬浮窗权限适配,但是由于碎片化的问题,所以在适配方面也无法做到完全的主流机型适配,这个需要大家的一起努力,这个博客的名字永远都是一个将来时 ...

  8. Android 使用WindowManager实现Android悬浮窗

    WindowManager介绍 通过Context.getSystemService(Context.WINDOW_SERVICE)可以获得 WindowManager对象. 每一个WindowMan ...

  9. 关于Android悬浮窗要获取按键响应的问题

    要在Android中实现顶层的窗口弹出,一般都会用WindowsManager来实现,但是几乎所有的网站资源都是说弹出的悬浮窗不用接受任何按键响应. 而问题就是,我们有时候需要他响应按键,比如电视上的 ...

随机推荐

  1. ef 吐糟

    方法:从dbcontext到objectcontext DbContext及其相关的API是EF4.1中一大新特性.简单说,DbContext就是之前EF的核心类ObjectContext的抽象封装. ...

  2. html文件form表单action调用servlet连接mysql数据库实例

    web.xml文件 <?xml version="1.0" encoding="UTF-8"?> <web-app xmlns:xsi=&qu ...

  3. modbus tcp 入门详解

    Modbus tcp 格式说明 通讯机制 附C#测试工具用于学习,测试   前言: 之前的博客介绍了如何用C#来读写modbus tcp服务器的数据,文章:http://www.cnblogs.com ...

  4. ALGO-27_蓝桥杯_算法训练_FBI树(树,递归)

    问题描述 我们可以把由“”和“”组成的字符串分为三类:全“”串称为B串,全“”串称为I串,既含“”又含“”的串则称为F串. FBI树是一种二叉树,它的结点类型也包括F结点,B结点和I结点三种.由一个长 ...

  5. html框内文字垂直居中的方法

    由于无法知道框内文字的高度,很难确定垂直空间的位置.vertical-align:middle仅对td元素有效,无论单行和多行均可实现垂直居中.

  6. 【Git】Git使用--常用命令

    查看所有分支 git branch -a 查看本地分支 git branch 切换分支 git checkout test demo git checkout release_1.3.1 (切换到re ...

  7. Python——pandas数据处理(python programming)

  8. HTML5绘制饼图示例(一)

    原文地址:http://www.2cto.com/kf/201108/100251.html HTML5引入Canvas元素,用于图形的绘制,我们可以仅仅基于HTML和JavaScript就能绘制出原 ...

  9. grafana 指标视图嵌入到其他html网页

    我们开发了一套管理平台用来监控整个系统环境的运行情况,但是在指标信息这块不想重新开发,而想直接拿grafana来用,刚开始的时候我们的管理平台和grafana是完全独立的,只能从我们平台跳转到graf ...

  10. JVM底层又是如何实现synchronized的【转载】

    目前在Java中存在两种锁机制:synchronized和Lock,Lock接口及其实现类是JDK5增加的内容,其作者是大名鼎鼎的并发专家Doug Lea.本文并不比较synchronized与Loc ...