欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  移动技术

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

程序员文章站 2023-11-24 14:21:22
在我们移动应用开发过程中,偶尔有可能会接到这种需求: 1、在手机桌面创建一个窗口,类似于360的悬浮窗口,点击这个窗口可以响应(至于窗口拖动我们可以后面再扩展)。...

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

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浮动窗口

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