Android自定义覆盖层控件 悬浮窗控件

在我们移动应用开发过程中,偶尔有可能会接到这种需求:

1、在手机桌面创建一个窗口,类似于360的悬浮窗口,点击这个窗口可以响应(至于窗口拖动我们可以后面再扩展)。

2、自己开发的应用去启动一个非本应用B,在B应用的某个界面增加一个引导窗口。

3、在应用的页面上触发启动这个窗口,该窗口悬浮在这个页面上,但又不会影响界面的其他操作。即不像PopupWindow那样要么窗口消失要么页面不可响应

以上需求都有几个共同特点,1、窗口的承载页面不一定不是本应用页面(Activity),即不是类似dialog, PopupWindow之类的页面。2、窗口的显示不会影响用户对其他界面的操作。

根据以上特点,我们发现这类的窗口其不影响其他界面操作特点有点像Toast,但又不完全是,因为Toast是自己消失的。其界面可以恒显示又有点像popupwindow,只当调用了消失方法才会消失。所以我们在做这样的控件的时候可以去参考一下Toast和PopupWIndow如何实现。最主要的时候Toast。好了说了这么多大概的思路我们已经明白了。

透过Toast,PopupWindow源码我们发现,Toast,Popup的实现都是通过windowManager的addview和removeView以及通过设置LayoutParams实现的。因此后面设计就该从这里入手,废话不说了----去实现。

第一步设计类似Toast的类FloatWindow

package com.floatwindowtest.john.floatwindowtest.wiget; 

import android.app.Activity;
import android.content.Context;
import android.graphics.PixelFormat;
import android.view.Gravity;
import android.view.KeyEvent;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.view.WindowManager;
import android.widget.FrameLayout;
import android.widget.LinearLayout; 

import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;
import static android.view.WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
import static android.view.WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH; 

/**
 * Created by john on 2017/3/10.
 */
class FloatWindow {
 private final Context mContext;
 private WindowManager windowManager;
 private View floatView;
 private WindowManager.LayoutParams params; 

 public FloatWindow(Context mContext) {
  this.mContext = mContext;
  this.params = new WindowManager.LayoutParams();
 } 

 /**
  * 显示浮动窗口
  * @param view
  * @param x view距离左上角的x距离
  * @param y view距离左上角的y距离
  */
 void show(View view, int x, int y) {
  this.windowManager = (WindowManager) this.mContext.getSystemService(Context.WINDOW_SERVICE);
  params.height = WindowManager.LayoutParams.WRAP_CONTENT;
  params.width = WindowManager.LayoutParams.WRAP_CONTENT;
  params.gravity = Gravity.TOP | Gravity.LEFT;
  params.format = PixelFormat.TRANSLUCENT;
  params.x = x;
  params.y = y;
  params.type = WindowManager.LayoutParams.TYPE_TOAST;
  params.flags = WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON | FLAG_NOT_FOCUSABLE | FLAG_WATCH_OUTSIDE_TOUCH
    | WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM;
  floatView = view;
  windowManager.addView(floatView, params);
 } 

 /**
  * 显示浮动窗口
  * @param view
  * @param x
  * @param y
  * @param listener 窗体之外的监听
  * @param backListener 返回键盘监听
  */ 

 void show(View view, int x, int y, OutsideTouchListener listener, KeyBackListener backListener) {
  this.windowManager = (WindowManager) this.mContext.getSystemService(Context.WINDOW_SERVICE);
  final FloatWindowContainerView containerView = new FloatWindowContainerView(this.mContext, listener, backListener);
  containerView.addView(view, WRAP_CONTENT, WRAP_CONTENT);
  params.height = WindowManager.LayoutParams.WRAP_CONTENT;
  params.width = WindowManager.LayoutParams.WRAP_CONTENT;
  params.gravity = Gravity.TOP | Gravity.LEFT;
  params.format = PixelFormat.TRANSLUCENT;
  params.x = x;
  params.y = y;
  params.type = WindowManager.LayoutParams.TYPE_TOAST;
//
//  params.flags = WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON
//    | WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM | WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH
//    | WindowManager.LayoutParams. FLAG_NOT_FOCUSABLE ; 

  params.flags = WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON
    | WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM | WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH
    | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL; 

  floatView = containerView;
  windowManager.addView(floatView, params);
 } 

 /**
  * 更新view对象文职
  *
  * @param offset_X x偏移量
  * @param offset_Y Y偏移量
  */
 public void updateWindowLayout(float offset_X, float offset_Y) {
  params.x += offset_X;
  params.y += offset_Y;
  windowManager.updateViewLayout(floatView, params);
 } 

 /**
  * 关闭界面
  */
 void dismiss() {
  if (this.windowManager == null) {
   this.windowManager = (WindowManager) this.mContext.getSystemService(Context.WINDOW_SERVICE);
  }
  if (floatView != null) {
   windowManager.removeView(floatView);
  }
  floatView = null;
 } 

 public void justHideWindow() {
  this.floatView.setVisibility(View.GONE);
 } 

 private class FloatWindowContainerView extends FrameLayout { 

  private OutsideTouchListener listener;
  private KeyBackListener backListener; 

  public FloatWindowContainerView(Context context, OutsideTouchListener listener, KeyBackListener backListener) {
   super(context);
   this.listener = listener;
   this.backListener = backListener;
  } 

  @Override
  public boolean dispatchKeyEvent(KeyEvent event) {
   if (event.getKeyCode() == KeyEvent.KEYCODE_BACK) {
    if (getKeyDispatcherState() == null) {
     if (backListener != null) {
      backListener.onKeyBackPressed();
     }
     return super.dispatchKeyEvent(event);
    } 

    if (event.getAction() == KeyEvent.ACTION_DOWN && event.getRepeatCount() == 0) {
     KeyEvent.DispatcherState state = getKeyDispatcherState();
     if (state != null) {
      state.startTracking(event, this);
     }
     return true;
    } else if (event.getAction() == KeyEvent.ACTION_UP) {
     KeyEvent.DispatcherState state = getKeyDispatcherState();
     if (state != null && state.isTracking(event) && !event.isCanceled()) {
      System.out.println("dsfdfdsfds");
      if (backListener != null) {
       backListener.onKeyBackPressed();
      }
      return super.dispatchKeyEvent(event);
     }
    }
    return super.dispatchKeyEvent(event);
   } else {
    return super.dispatchKeyEvent(event);
   }
  } 

  @Override
  public boolean onTouchEvent(MotionEvent event) {
   final int x = (int) event.getX();
   final int y = (int) event.getY(); 

   if ((event.getAction() == MotionEvent.ACTION_DOWN)
     && ((x < 0) || (x >= getWidth()) || (y < 0) || (y >= getHeight()))) {
    return true;
   } else if (event.getAction() == MotionEvent.ACTION_OUTSIDE) {
    if (listener != null) {
     listener.onOutsideTouch();
    }
    System.out.println("dfdf");
    return true;
   } else {
    return super.onTouchEvent(event);
   }
  }
 }
} 

大家可能会注意到

//  params.flags = WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON
//    | WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM | WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH
//    | WindowManager.LayoutParams. FLAG_NOT_FOCUSABLE ; 

  params.flags = WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON
    | WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM | WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH
    | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL;

这些设置有所不同,这就是我们要实现既能够监听窗口之外的触目事件,又不会影响他们自己的操作的关键地方 ,同时| WindowManager.LayoutParams. FLAG_NOT_FOCUSABLE ;和| WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL; 窗体是否监听到返回键的关键设置  需要指出的是一旦窗体监听到返回键事件,则当前Activity不会再监听到返回按钮事件了,所以大家可根据自己的实际情况出发做出选择。

为了方便管理这些浮动窗口的显示和消失,还写了一个管理窗口显示的类FloatWindowManager。这是一个单例模式 对应的显示窗口也是只显示一个。大家可以根据自己的需求是改变 这里不再明细。

package com.floatwindowtest.john.floatwindowtest.wiget; 

import android.content.Context;
import android.view.View; 

/**
 *
 * Created by john on 2017/3/10.
 */ 

public class FloatWindowManager {
 private static FloatWindowManager manager;
 private FloatWindow floatWindow; 

 private FloatWindowManager(){ 

 }
 public static synchronized FloatWindowManager getInstance(){
  if(manager==null){
   manager=new FloatWindowManager();
  }
  return manager;
 } 

 public void showFloatWindow(Context context, View view,int x,int y){
  if(floatWindow!=null){
   floatWindow.dismiss();
  }
  floatWindow=new FloatWindow(context);
  floatWindow.show(view,x,y);
 } 

 public void showFloatWindow(Context context, View view, int x, int y, OutsideTouchListener listener,KeyBackListener backListener){
  if(floatWindow!=null){
   floatWindow.dismiss();
  }
  floatWindow=new FloatWindow(context);
  floatWindow.show(view,0,0,listener,backListener);
 } 

 public void dismissFloatWindow(){
  if(floatWindow!=null){
   floatWindow.dismiss();
  }
 } 

 public void justHideWindow(){
  floatWindow.justHideWindow();
 }
 /**
  * 更新位置
  * @param offsetX
  * @param offsetY
  */
 public void updateWindowLayout(float offsetX, float offsetY){
  floatWindow.updateWindowLayout(offsetX,offsetY);
 };
} 

还有设计类似悬浮球的窗口等 大家可以自己运行一遍比这里看千遍更有用。

附件:Android浮动窗口

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持我们。

(0)

相关推荐

  • Android手机悬浮窗口小案例

    本文实例为大家分享了Android九宫格图片展示的具体代码,供大家参考,具体内容如下 –主页面--– //布局中就一个Button public class MainActivity extends Activity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main);

  • Android 悬浮窗权限各机型各系统适配大全(总结)

    这篇博客主要介绍的是 Android 主流各种机型和各种版本的悬浮窗权限适配,但是由于碎片化的问题,所以在适配方面也无法做到完全的主流机型适配,这个需要大家的一起努力,这个博客的名字永远都是一个将来时. 悬浮窗适配 悬浮窗适配有两种方法:第一种是按照正规的流程,如果系统没有赋予 APP 弹出悬浮窗的权限,就先跳转到权限授权界面,等用户打开该权限之后,再去弹出悬浮窗,比如 QQ 等一些主流应用就是这么做得:第二种就是利用系统的漏洞,绕过权限的申请,简单粗暴,这种方法我不是特别建议,但是现在貌似有些

  • android 添加随意拖动的桌面悬浮窗口

    用过新版本android 360手机助手都人都对 360中只在桌面显示一个小小悬浮窗口羡慕不已吧? 其实实现这种功能,主要有两步: 1.判断当前显示的是为桌面.这个内容我在前面的帖子里面已经有过介绍,如果还没看过的赶快稳步看一下哦. 2.使用windowManager往最顶层添加一个View .这个知识点就是为本文主要讲解的内容哦.在本文的讲解中,我们还会讲到下面的知识点: a.如果获取到状态栏的高度 b.悬浮窗口的拖动 c.悬浮窗口的点击事件 有开始之前,我们先来看一下效果图:  接下来我们来

  • Android中悬浮窗口的实现原理实例分析

    本文实例讲述了Android中悬浮窗口的实现原理.分享给大家供大家参考.具体如下: 用了我一个周末的时间,个中愤懑就不说了,就这个问题,我翻遍全球网络没有一篇像样的资料,现在将实现原理简单叙述如下: 调用WindowManager,并设置WindowManager.LayoutParams的相关属性,通过WindowManager的addView方法创建View,这样产生出来的View根据WindowManager.LayoutParams属性不同,效果也就不同了.比如创建系统顶级窗口,实现悬浮

  • android编程实现悬浮窗体的方法

    本文实例讲述了android编程实现悬浮窗体的方法.分享给大家供大家参考,具体如下: 突然对悬浮窗体感兴趣,查资料做了个小Demo,效果是点击按钮后,关闭当前Activity,显示悬浮窗口,窗口可以拖动,双击后消失.效果图如下: 它的使用原理很简单,就是借用了WindowManager这个管理类来实现的. 1.首先在AndroidManifest.xml中添加使用权限: 复制代码 代码如下: <uses-permission android:name="android.permission

  • Android学习教程之悬浮窗菜单制作(9)

    本文实例为大家分享了Android悬浮窗菜单的具体代码,供大家参考,具体内容如下 MainActivity.java代码: package siso.multilistview; import android.os.Build; import android.support.v7.app.AppCompatActivity; import android.os.Bundle; import android.view.View; public class MainActivity extends

  • Android实现带磁性的悬浮窗体效果

    本文实例讲述了Android实现带磁性的悬浮窗体效果.分享给大家供大家参考,具体如下: 带磁性的悬浮窗体,类似于360绿色小人 主要实现的是: 1.悬浮所有窗体之上 2.有吸引力,吸附于屏幕边上 3.有点击效果 下面我就实现上面三点,简单封装了个FloatView 先看下本次Demo的效果图,然后再看代码, 效果图: FloatView代码如下 package com.manymore13.flowwindowdemo; import android.content.Context; impor

  • Android应用内悬浮窗的实现方案示例

    1.悬浮窗的基本介绍 悬浮窗,大家应该也不陌生,凌驾于应用之上的一个小弹窗,实现上很简单,就是添加一个系统级别的窗口,Android中通过WindowManagerService( WMS)来管理所有的窗口,对于WMS来说,管你是Activity.Toast.Dialog,都不过是通过WindowManagerGlobal.addView()添加的一个个View. Android中的窗口分为三个级别: 1.1 应用窗口,比如Activity的窗口; 1.2 子窗口,依赖于父窗口,比如PopupW

  • Android自定义覆盖层控件 悬浮窗控件

    在我们移动应用开发过程中,偶尔有可能会接到这种需求: 1.在手机桌面创建一个窗口,类似于360的悬浮窗口,点击这个窗口可以响应(至于窗口拖动我们可以后面再扩展). 2.自己开发的应用去启动一个非本应用B,在B应用的某个界面增加一个引导窗口. 3.在应用的页面上触发启动这个窗口,该窗口悬浮在这个页面上,但又不会影响界面的其他操作.即不像PopupWindow那样要么窗口消失要么页面不可响应 以上需求都有几个共同特点,1.窗口的承载页面不一定不是本应用页面(Activity),即不是类似dialog

  • Android自定义View之简约风歌词控件实战指南

    目录 前言 一. 歌词解析 1.歌词实体类LrcBean 2. 解析歌词工具类LrcUtil 二.歌词绘制 1.设置自定View属性,在代码中设置默认值 2. 初始化两支画笔 3. 重复执行onDraw方法 1.获得控件的测量后的宽高 2. 得到当前歌词的位置 4. 歌词同步滑动 5.不断重绘 三 .使用 总结 前言 最近重构了之前的音乐播放器,添加了许多功能,比如歌词,下载功能等.这篇文章就让我们聊聊歌词控件的实现,先上效果图,如果感觉海星,就继续瞧下去! 看到这里,估计你对这个控件还有点感兴

  • Android自定义ViewGroup实现朋友圈九宫格控件

    目录 一.简介 1.1.效果图如下 1.2.主要功能如下 二.使用 2.1.自定义属性如下 2.2.布局中使用自定义NineImageLayout 2.3.Adapter方式绑定数据和UI 2.4.列表里面使用 三.源码地址 四.总结 一.简介 最近项目里有个类似微信朋友圈的九图控件的需求,Github找了一下,发现都不太满足需求,我需要单张图片的时候可以按照图片宽高比列在一定范围内自适应,而大多开源项目单张图片也是一个小正方形,所以,干脆自己动手写一个 1.1.效果图如下 1.2.主要功能如下

  • Android仿微信视屏悬浮窗效果

    在项目中需要对接入的腾讯云音视频,可以悬浮窗显示,悬浮窗可拖拽,并且在悬浮窗不影响其他的activity的焦点. 这个大神的文章Android基于腾讯云实时音视频仿微信视频通话最小化悬浮,他讲的是视频通话时,将远端视频以悬浮窗形式展示,根据他的代码我进行了部分简化 1.悬浮窗效果:点击缩小按钮,将当前远端视屏加载进悬浮窗,且悬浮窗可拖拽,不影响其他界面焦点:点击悬浮窗可返回原来的Activity 2.实现悬浮窗需要: 在androidManifest中申请悬浮窗权限<uses-permissio

  • Android自定义RecyclerView Item头部悬浮吸顶

    本文实例为大家分享了Android自定义RecyclerView Item头部悬浮吸顶的具体代码,供大家参考,具体内容如下 概述 1.自定义了一个FrameLayout,引入条目的头部布局加入到自定义FrameLayout中. 2.将RecyclerView加入FrameLayout 3.条目头部View的Alpha动画以及设置透明和不透明这个时机大多是通过打log来确定的,硬推理还是有些难. 4.当屏幕显示区域的第二条Item距离控件顶端的距离小于条目头部View高度时,就开始移动条目头部Vi

  • Android 获取判断是否有悬浮窗权限的方法

    现在很多应用都会用到悬浮窗,很多国产rom把悬浮窗权限加入控制了,你就需要判断是否有悬浮窗权限,然后做对应操作. Android 原生有自带权限管理的,只是被隐藏了.看android源码在android.app下就有个AppOpsManager类. 类说明如下: /** * API for interacting with "application operation" tracking. * * <p>This API is not generally intended

  • Android自定义View实现随手势滑动控件

    本文控件为大家分享了Android随手势滑动控件的具体代码,供大家参考,具体内容如下 1.新建自定义控件类:MyView public class MyView extends Button{ //记录上次滑动后的坐标值 private int lastX; private int lastY; public MyView(Context context) { super(context); // TODO Auto-generated constructor stub } public MyV

  • Android自定义View实现多图片选择控件

    前言 相信很多朋友在开发中都会遇到图片上传的情况,尤其是多图上传,最经典的莫过于微信的图片选择了.所有很多情况下会使用到多图选择,所以就有了这篇文章,今天抽点时间写了个控件.  •支持自定义选择图片的样式  •支持设置图片选择数量  •支持图片预览,删除  •支持图片拍照 先来看看效果 实现分析 假如不定义控件,我们要实现这样一个功能,无非是写个GridView在item点击的时候去显示图片进行选择,在返回界面的时候进行GridView的数据刷新.我们把这些逻辑写在我们自定义的GridView中

  • Android自定义view实现水波进度条控件

    通过自定义view实现了一个水滴滴落到水波面,溅起水花并且水波流动上涨的进度条控件.之前看到过好多水波流动的进度条,感觉欠缺些东西,就想到了水滴到水平面,溅起水花然后水流动上涨的进度条效果,于是自己动手写了出来.效果如下,视频录制有些卡顿,实际会流畅很多. 一.用法 1.布局文件中添加WaveProgressView,circleColor属性为圆环颜色,waterColor属性为水波水滴的颜色,progress属性为初始的进度 <com.yhongm.wave_progress_view.Wa

  • Android自定义商品购买数量加减控件

    在购买商品时,大家可以自定义数字加减控件,来确定购买商品的实际数量,如何实现此控件,请参考下文: 1.自定义数字加减控件的要求 创建Module -NumberAddSubView A_输入的只能是数字,而且不能通过键盘输入 B_通过加减按钮操作数字 C_监听加减按钮 D_数组有最小值和最大值的限制 E_自定义属性 2.提供接口,让外界监听到数字的变化 1_设置接口 @Override public void onClick(View v) { if (v.getId() == R.id.btn

随机推荐