Android仿IOS系统悬浮窗效果
程序员文章站
2022-06-15 11:58:29
在一些场合里,我们使用悬浮窗会有很大的便利,比如ios系统的悬浮窗,360或者其他手机卫士的悬浮窗等等。本篇博客,我们创造出两个悬浮窗,通过点击小悬浮窗打开或者关闭大悬浮窗(一个播放控制器)。代码如下...
在一些场合里,我们使用悬浮窗会有很大的便利,比如ios系统的悬浮窗,360或者其他手机卫士的悬浮窗等等。
本篇博客,我们创造出两个悬浮窗,通过点击小悬浮窗打开或者关闭大悬浮窗(一个播放控制器)。
代码如下:
在这之前,我们需要在manifest中申请权限:
<uses-permission android:name="android.permission.system_alert_window" />
并且,悬浮窗这个权限我们需要手动在手机找到应用权限管理,允许这个权限才行
小悬浮窗的界面代码float_normal_view.xml:
<?xml version="1.0" encoding="utf-8"?> <linearlayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="65dp" android:layout_height="65dp" android:id="@+id/ll_float_normal" android:background="@drawable/float_bg" android:gravity="center" android:orientation="vertical"> <imageview android:id="@+id/iv_show_control_view" android:layout_gravity="center" android:background="@drawable/white_ring" android:layout_width="35dp" android:orientation="vertical" android:layout_height="35dp" > </imageview> </linearlayout>
大悬浮窗的界面代码float_control_view:
<?xml version="1.0" encoding="utf-8"?> <linearlayout xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/ll_float_control" android:layout_width="300dp" android:layout_height="100dp" android:background="@drawable/float_bg" android:gravity="center_horizontal|bottom" android:orientation="vertical"> <seekbar android:id="@+id/timeline" android:paddingtop="3dp" android:paddingbottom="3dp" android:layout_width="match_parent" android:layout_height="wrap_content" android:focusable="true" android:maxheight="5.0dip" android:minheight="5.0dip" android:paddingleft="16.0dip" android:paddingright="16.0dip" android:progressdrawable="@drawable/po_seekbar" android:thumb="@drawable/seekbar_thumb" /> <relativelayout android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_gravity="bottom" android:paddingbottom="10dp" android:paddingend="20dp" android:paddingstart="20dp" android:paddingtop="10dp"> <imagebutton android:id="@+id/ibt_rewind" android:layout_width="40dp" android:layout_height="40dp" android:layout_centervertical="true" android:layout_marginend="20dp" android:layout_marginright="20dp" android:layout_toleftof="@+id/ibt_play" android:layout_tostartof="@+id/ibt_play" android:background="@drawable/rewind" /> <imagebutton android:id="@+id/ibt_play" android:layout_width="50dp" android:layout_height="50dp" android:layout_centerhorizontal="true" android:background="@drawable/pause" /> <imagebutton android:id="@+id/ibt_forward" android:layout_width="40dp" android:layout_height="40dp" android:layout_centervertical="true" android:layout_marginleft="20dp" android:layout_marginstart="20dp" android:layout_toendof="@+id/ibt_play" android:layout_torightof="@+id/ibt_play" android:background="@drawable/forward" /> </relativelayout> </linearlayout>
入口activity(floatactivity ):
public class floatactivity extends activity { mywindowmanager mywindowmanager; @override protected void oncreate(@nullable bundle savedinstancestate) { super.oncreate(savedinstancestate); setcontentview(r.layout.activity_main); mywindowmanager = mywindowmanager.getinstance(); mywindowmanager.createnormalview(this.getapplicationcontext()); } }
悬浮窗管理器mywindowmanager:
/** * created by shiweixian on 2017/3/7. * 悬浮窗管理器 * 创建,移除 * 单例模式 */ public class mywindowmanager { private floatnormalview normalview; private floatcontrolview controlview; private static mywindowmanager instance; private windowmanager windowmanager; private mywindowmanager() { } public static mywindowmanager getinstance() { if (instance == null) instance = new mywindowmanager(); return instance; } private windowmanager getwindowmanager(context context) { if (windowmanager == null) windowmanager = (windowmanager) context.getsystemservice(context.window_service); return windowmanager; } /** * 判断小悬浮窗是否存在 * * @return */ public boolean isnormalviewexists() { return normalview != null; } /** * 判断播放器这个大悬浮窗是否存在 * * @return */ public boolean iscontrolviewexists() { return controlview != null; } /** * 创建小型悬浮窗 */ public void createnormalview(context context) { if (normalview == null) { normalview = new floatnormalview(context); } } /** * 移除悬浮窗 * * @param context */ public void removenormalview(context context) { if (normalview != null) { windowmanager.removeview(normalview); normalview = null; } } /** * 创建小型悬浮窗 */ public void createcontrolview(context context) { if (controlview == null) controlview = new floatcontrolview(context); } /** * 移除悬浮窗 * * @param context */ public void removecontrolview(context context) { if (controlview != null) { windowmanager windowmanager = (windowmanager) context.getsystemservice(context.window_service); windowmanager.removeview(controlview); controlview = null; } } }
小悬浮窗floatnormalview:
/** * created by shiwe on 2017/3/7. * 缩小的悬浮窗 */ public class floatnormalview extends linearlayout { /** * 记录小悬浮窗的宽度 */ public static int viewwidth; /** * 记录小悬浮窗的高度 */ public static int viewheight; /** * 记录系统状态栏的高度 */ private static int statusbarheight; /** * 用于更新小悬浮窗的位置 */ private windowmanager windowmanager; /** * 小悬浮窗的参数 */ private windowmanager.layoutparams mparams; /** * 记录当前手指位置在屏幕上的横坐标值 */ private float xinscreen; /** * 记录当前手指位置在屏幕上的纵坐标值 */ private float yinscreen; /** * 记录手指按下时在屏幕上的横坐标的值 */ private float xdowninscreen; /** * 记录手指按下时在屏幕上的纵坐标的值 */ private float ydowninscreen; /** * 记录手指按下时在小悬浮窗的view上的横坐标的值 */ private float xinview; /** * 记录手指按下时在小悬浮窗的view上的纵坐标的值 */ private float yinview; public floatnormalview(context context) { super(context); windowmanager = (windowmanager) context.getsystemservice(context.window_service); layoutinflater.from(context).inflate(r.layout.float_normal_view, this); view view = findviewbyid(r.id.ll_float_normal); viewwidth = view.getlayoutparams().width; viewheight = view.getlayoutparams().height; initlayoutparams(); } /** * 初始化参数 */ private void initlayoutparams() { //屏幕宽高 int screenwidth = windowmanager.getdefaultdisplay().getwidth(); int screenheight = windowmanager.getdefaultdisplay().getheight(); mparams = new windowmanager.layoutparams(); //总是出现在应用程序窗口之上。 mparams.type = windowmanager.layoutparams.type_phone; // flag_not_touch_modal不阻塞事件传递到后面的窗口 // flag_not_focusable 悬浮窗口较小时,后面的应用图标由不可长按变为可长按,不设置这个flag的话,home页的划屏会有问题 mparams.flags = windowmanager.layoutparams.flag_not_focusable | windowmanager.layoutparams.flag_not_touch_modal; //悬浮窗默认显示的位置 mparams.gravity = gravity.start | gravity.top; //指定位置 mparams.x = screenwidth - viewwidth * 2; mparams.y = screenheight / 2 + viewheight * 2; //悬浮窗的宽高 mparams.width = windowmanager.layoutparams.wrap_content; mparams.height = windowmanager.layoutparams.wrap_content; mparams.format = pixelformat.transparent; windowmanager.addview(this, mparams); } @override public boolean ontouchevent(motionevent event) { switch (event.getaction()) { case motionevent.action_down: // 手指按下时记录必要数据,纵坐标的值都需要减去状态栏高度 xinview = event.getx(); yinview = event.gety(); xdowninscreen = event.getrawx(); ydowninscreen = event.getrawy() - getstatusbarheight(); xinscreen = event.getrawx(); yinscreen = event.getrawy() - getstatusbarheight(); break; case motionevent.action_move: xinscreen = event.getrawx(); yinscreen = event.getrawy() - getstatusbarheight(); // 手指移动的时候更新小悬浮窗的位置 updateviewposition(); break; case motionevent.action_up: // 如果手指离开屏幕时,xdowninscreen和xinscreen相等,且ydowninscreen和yinscreen相等,则视为触发了单击事件。 if (xdowninscreen == xinscreen && ydowninscreen == yinscreen) { openorclosecontrolview(); } break; default: break; } return true; } /** * 将小悬浮窗的参数传入,用于更新小悬浮窗的位置。 * * @param params 小悬浮窗的参数 */ public void setparams(windowmanager.layoutparams params) { mparams = params; } /** * 更新小悬浮窗在屏幕中的位置。 */ private void updateviewposition() { mparams.x = (int) (xinscreen - xinview); mparams.y = (int) (yinscreen - yinview); windowmanager.updateviewlayout(this, mparams); } /** * 打开或关闭大悬浮窗。 */ private void openorclosecontrolview() { mywindowmanager manager = mywindowmanager.getinstance(); if (!manager.iscontrolviewexists()) manager.createcontrolview(getcontext()); else manager.removecontrolview(getcontext()); } /** * 用于获取状态栏的高度。 * * @return 返回状态栏高度的像素值。 */ private int getstatusbarheight() { if (statusbarheight == 0) { try { class<?> c = class.forname("com.android.internal.r$dimen"); object o = c.newinstance(); field field = c.getfield("status_bar_height"); int x = (integer) field.get(o); statusbarheight = getresources().getdimensionpixelsize(x); } catch (exception e) { e.printstacktrace(); } } return statusbarheight; } }
大悬浮窗floatcontrolview:
/** * created by shiwe on 2017/3/7. * 缩小的悬浮窗 */ public class floatnormalview extends linearlayout { /** * 记录小悬浮窗的宽度 */ public static int viewwidth; /** * 记录小悬浮窗的高度 */ public static int viewheight; /** * 记录系统状态栏的高度 */ private static int statusbarheight; /** * 用于更新小悬浮窗的位置 */ private windowmanager windowmanager; /** * 小悬浮窗的参数 */ private windowmanager.layoutparams mparams; /** * 记录当前手指位置在屏幕上的横坐标值 */ private float xinscreen; /** * 记录当前手指位置在屏幕上的纵坐标值 */ private float yinscreen; /** * 记录手指按下时在屏幕上的横坐标的值 */ private float xdowninscreen; /** * 记录手指按下时在屏幕上的纵坐标的值 */ private float ydowninscreen; /** * 记录手指按下时在小悬浮窗的view上的横坐标的值 */ private float xinview; /** * 记录手指按下时在小悬浮窗的view上的纵坐标的值 */ private float yinview; public floatnormalview(context context) { super(context); windowmanager = (windowmanager) context.getsystemservice(context.window_service); layoutinflater.from(context).inflate(r.layout.float_normal_view, this); view view = findviewbyid(r.id.ll_float_normal); viewwidth = view.getlayoutparams().width; viewheight = view.getlayoutparams().height; initlayoutparams(); } /** * 初始化参数 */ private void initlayoutparams() { //屏幕宽高 int screenwidth = windowmanager.getdefaultdisplay().getwidth(); int screenheight = windowmanager.getdefaultdisplay().getheight(); mparams = new windowmanager.layoutparams(); //总是出现在应用程序窗口之上。 mparams.type = windowmanager.layoutparams.type_phone; // flag_not_touch_modal不阻塞事件传递到后面的窗口 // flag_not_focusable 悬浮窗口较小时,后面的应用图标由不可长按变为可长按,不设置这个flag的话,home页的划屏会有问题 mparams.flags = windowmanager.layoutparams.flag_not_focusable | windowmanager.layoutparams.flag_not_touch_modal; //悬浮窗默认显示的位置 mparams.gravity = gravity.start | gravity.top; //指定位置 mparams.x = screenwidth - viewwidth * 2; mparams.y = screenheight / 2 + viewheight * 2; //悬浮窗的宽高 mparams.width = windowmanager.layoutparams.wrap_content; mparams.height = windowmanager.layoutparams.wrap_content; mparams.format = pixelformat.transparent; windowmanager.addview(this, mparams); } @override public boolean ontouchevent(motionevent event) { switch (event.getaction()) { case motionevent.action_down: // 手指按下时记录必要数据,纵坐标的值都需要减去状态栏高度 xinview = event.getx(); yinview = event.gety(); xdowninscreen = event.getrawx(); ydowninscreen = event.getrawy() - getstatusbarheight(); xinscreen = event.getrawx(); yinscreen = event.getrawy() - getstatusbarheight(); break; case motionevent.action_move: xinscreen = event.getrawx(); yinscreen = event.getrawy() - getstatusbarheight(); // 手指移动的时候更新小悬浮窗的位置 updateviewposition(); break; case motionevent.action_up: // 如果手指离开屏幕时,xdowninscreen和xinscreen相等,且ydowninscreen和yinscreen相等,则视为触发了单击事件。 if (xdowninscreen == xinscreen && ydowninscreen == yinscreen) { openorclosecontrolview(); } break; default: break; } return true; } /** * 将小悬浮窗的参数传入,用于更新小悬浮窗的位置。 * * @param params 小悬浮窗的参数 */ public void setparams(windowmanager.layoutparams params) { mparams = params; } /** * 更新小悬浮窗在屏幕中的位置。 */ private void updateviewposition() { mparams.x = (int) (xinscreen - xinview); mparams.y = (int) (yinscreen - yinview); windowmanager.updateviewlayout(this, mparams); } /** * 打开或关闭大悬浮窗。 */ private void openorclosecontrolview() { mywindowmanager manager = mywindowmanager.getinstance(); if (!manager.iscontrolviewexists()) manager.createcontrolview(getcontext()); else manager.removecontrolview(getcontext()); } /** * 用于获取状态栏的高度。 * * @return 返回状态栏高度的像素值。 */ private int getstatusbarheight() { if (statusbarheight == 0) { try { class<?> c = class.forname("com.android.internal.r$dimen"); object o = c.newinstance(); field field = c.getfield("status_bar_height"); int x = (integer) field.get(o); statusbarheight = getresources().getdimensionpixelsize(x); } catch (exception e) { e.printstacktrace(); } } return statusbarheight; } }
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持。
上一篇: JavaScript 数组去重详解
下一篇: VS Nuget的使用实践