• Android悬浮窗及其拖动事件


    主页面布局很简单,只有一个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

  • 相关阅读:
    Ansible 和 Playbook 暂存
    nginx 和keepalived的使用
    关于 群晖 docker 百度云盘下载的使用心得
    文件夹共享
    转:轻松把玩HttpClient之封装HttpClient工具类(一)(现有网上分享中的最强大的工具类)
    Maven学习笔记一
    转:Maven常用命令
    转:MySQL下载安装、配置与使用(win7x64)
    转:SQL SERVER 2014 安装图解(含 SQL SERVER 2014 安装程序共享)
    转:java 解析excel,带合并单元的excel
  • 原文地址:https://www.cnblogs.com/xl-phoenix/p/7639596.html
Copyright © 2020-2023  润新知