Android之项目级悬浮窗开发教程
来源:互联网 发布:淘宝买家好评语大全50 编辑:程序博客网 时间:2024/05/17 17:43
在我们玩手机游戏时能看到,很多游戏的登录界面两侧往往会有一个小小的悬浮窗,可以提供相应功能菜单项,简洁实用且不影响游戏体验。具体效果如下图所示。这篇博客将带大家开发一个可以直接用在项目中的悬浮窗实例。
一、需实现的功能分析
1.悬浮窗可在屏幕范围内自由拖动;
2.悬浮窗拖动到屏幕中任意位置后放开手指,它会向近的一侧移动并停靠;
3.停靠在屏幕两侧时,经过一定时间后能自动切换成半隐藏状态;
4.点击半隐藏状态的悬浮窗,显示悬浮窗,再次点击则显示悬浮窗侧拉菜单。
二、开发所涉及知识点
1.WindowManager悬浮窗控制类
2.点击触摸事件处理
3.简单的多线程编程
4.PopupWindow
三、开发实现过程
1.实现应用内简单固定的悬浮窗
(1)实现悬浮窗布局
我通过自定义一个扩展自LinearLayout的类来实现悬浮窗
public class MainFloatWindow extends LinearLayout
悬浮窗的布局很简单,直接就是两张图片,对应两种状态(半隐藏和显示),所以我直接用Java代码实现(资源在我的源码中有)
private void initWindowView(){ ivDefaultWindow = new ImageView(mCtx); this.ivDefaultWindow.setImageResource(ResourceUtil.getDrawableId(mCtx, "uac_gameassist_photo_default")); this.ivDefaultWindow.setBackgroundResource(ResourceUtil.getDrawableId(mCtx, "uac_gameassist_photo_bg")); ivHideWindow = new ImageView(mCtx); ivHideWindow.setImageResource(ResourceUtil.getDrawableId(mCtx, "uac_gameassist_photo_brand_left")); //显示ivDefaultWindow,隐藏ivHideWindow setWindowHide(false); }
然后在MainFloatWindow的构造函数中把这些初始化的类addview到MainFloatWindow,从而实现布局
this.addView(ivDefaultWindow); this.addView(ivHideWindow);
(2)通过WindowManager让它显示出来
悬浮窗权限
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
初始化WindowManager的参数
WindowManager mWindowManager = (WindowManager) mCtx.getApplicationContext().getSystemService(Context.WINDOW_SERVICE);WindowManager.LayoutParams params = new WindowManager.LayoutParams();params.type = WindowManager.LayoutParams.TYPE_PHONE;params.flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;params.format = PixelFormat.TRANSLUCENT;params.width = WindowManager.LayoutParams.WRAP_CONTENT;params.height = WindowManager.LayoutParams.WRAP_CONTENT;params.gravity = Gravity.LEFT |Gravity.TOP;//悬浮窗初始位置params.y = WindowUtil.getScreenHeight(mCtx)/2;
把之前实现的MainFloatWindow作为悬浮窗视图,WindowManager控制显示隐藏。其实就是简单的addView和removeView方法。
public void showFloatWindow(){ if(isShow){ return; } mWindowManager.addView(mainFloatWindow,params); isShow = true; } public void hideFloatWindow(){ if(!isShow){ return; } mWindowManager.removeView(mainFloatWindow); isShow = false; }
2.让悬浮窗动起来(实现拖动和点击)
要实现悬浮窗随手指进行拖动,从代码角度理解,就是触摸事件位置的改变,实时更新悬浮窗视图的位置。(1)更新悬浮窗位置用到了WindowManager的updateViewLayout方法,通过改变传入的params实例的x、y参数,实现位置改变。
mParams.x = (int) x;mParams.y = (int) y;mWindowManager.updateViewLayout(MainFloatWindow.this, mParams);
这里需要讲个注意的地方,MotionEvent.getX()是获得相对当前触摸的视图的x左边,而MotionEvent.getRawX()是获得相对屏幕的x坐标。
/**相对于View的x、y坐标 **/ private float xInView, yInView; /**在屏幕中的x、y坐标 **/ private float xDown, yDown; @Override public boolean onTouchEvent(MotionEvent event) { switch (event.getAction()){ case MotionEvent.ACTION_DOWN: xInView = event.getX(); yInView = event.getY(); xDown = event.getRawX(); yDown = event.getRawY() - WindowUtil.getStatusBarHeight(mCtx); break; case MotionEvent.ACTION_MOVE: //更新悬浮窗位置 Message message = new Message(); message.what = MSG_UPDATE_POS; message.arg1 = (int) (event.getRawX() - xInView); message.arg2 = (int) (event.getRawY() - WindowUtil.getStatusBarHeight(mCtx)); mHandler.sendMessage(message); break; case MotionEvent.ACTION_UP: if(xDown == event.getRawX() && yDown == event.getRawY() - WindowUtil.getStatusBarHeight(mCtx)){ //点击事件 onClick(); } break; } return true; }
因为重写的onTouchEvent()返回true,所以会覆盖了该视图的点击监听事件(具体原因不在此赘述,可以看Android触摸事件处理机制),我们需要自行判断动作监听点击事件。
3.实现悬浮窗自动平移到两侧
/** * 自动平移到两侧停靠 */ private void autoMoveToSide() { new Thread() { @Override public void run() { //保存x、y坐标 int[] location = new int[2]; getLocationOnScreen(location); isOnLeft = location[0]+getWidth()/2 < WindowUtil.getScreenWidth(mCtx)/2; mSubFloatWindow.setOnLeft(isOnLeft); int moveParam = isOnLeft ? -10 : 10; //每次移动10个像素 while (true) { location[0] = location[0] + moveParam; int newX = location[0]; int newY = location[1]; if (isOnLeft && newX<=0) { //已移至最左侧 newX = 0; Message message = new Message(); message.what = MSG_UPDATE_POS; message.arg1 = newX; message.arg2 = newY; mHandler.sendMessage(message); break; }else if(!isOnLeft && newX>=WindowUtil.getScreenWidth(mCtx)){ //已移至最右侧 newX = WindowUtil.getScreenWidth(mCtx); Message message = new Message(); message.what = MSG_UPDATE_POS; message.arg1 = newX; message.arg2 = newY; mHandler.sendMessage(message); break; } else { Message message = new Message(); message.what = MSG_UPDATE_POS; message.arg1 = newX; message.arg2 = newY; mHandler.sendMessage(message); } try { Thread.sleep(100/ SPEED); } catch (InterruptedException e) { e.printStackTrace(); } } } }.start(); }开启一条子线程计算自动平移时新状态的位置,并通过Thread.sleep()模拟每隔一段时间移动一段距离的。
4.半隐藏状态的切换
当悬浮窗停靠在屏幕两侧时,隔一段时间没有进行操作时,需要自动切换成半隐藏状态。半隐藏状态不能被拖动,点击此状态下的悬浮窗,可以显示出完整的悬浮窗。
实现此功能需要为悬浮窗添加两个状态判断标识,第一个是悬浮窗能否隐藏标识,当悬浮窗菜单打开时不能隐藏,当悬浮窗被拖动时不能隐藏;第二个是悬浮窗是否处于隐藏状态,当处于隐藏状态时,保证悬浮窗不能被拖动。
/** * 等待一定时间后隐藏悬浮窗 */ private void waitToHideWindow(){ if(!canHide){ return; } new Thread(){ @Override public void run() { try { Thread.sleep(WAIT_TIME); } catch (InterruptedException e) { e.printStackTrace(); } if(canHide) { mHandler.sendEmptyMessage(MSG_WINDOW_HIDE); } } }.start(); }此处在子线程启动前判断能否隐藏,然后在线程运行时再判断一次能否隐藏,做双重锁,是为了防止线程睡眠前悬浮窗可以隐藏,但等线程睡眠结束后处于不能隐藏的状态,却把悬浮窗切换成隐藏状态了。举一个实际情况,当线程停靠在两侧,应用调用waitToHideWindow()方法等待一段时候后隐藏悬浮窗,但等待过程中用户拖动了悬浮窗,等待结束后还处于被拖动状态,这时按需求是不应该隐藏悬浮窗的,故需要对状态加以判断。
5.实现悬浮窗的子菜单
子菜单我是采用PopupWindow来做的,原因是想到了它可以在显示在父视图的特定位置,悬浮窗和菜单分开处理,便于操作。
public SubFloatWindow(Context context){ mCtx = context; setContentView(getWindowView()); setWidth(WindowUtil.dip2px(mCtx, 220)); setHeight(ViewGroup.LayoutParams.WRAP_CONTENT); setFocusable(true); setOutsideTouchable(true); // 实例化一个ColorDrawable颜色为半透明 ColorDrawable dw = new ColorDrawable(0000000000); // 点back键和其他地方使其消失,设置了这个才能触发OnDismisslistener ,设置其他控件变化等操作 this.setBackgroundDrawable(dw); }PopupWindow的初始化很简单,设置几个基本属性即好。
if (isOnLeft) { mSubFloatWindow.showAtLocation(this, Gravity.CENTER | Gravity.START, getWidth(), 0); ivDefaultWindow.setBackgroundResource(ResourceUtil.getDrawableId(mCtx, "uac_gameassist_photo_right_bg")); } else { mSubFloatWindow.showAtLocation(this, Gravity.CENTER | Gravity.END, getWidth(), 0); ivDefaultWindow.setBackgroundResource(ResourceUtil.getDrawableId(mCtx, "uac_gameassist_photo_left_bg")); }子菜单的显示,需要用到该方法
public void showAtLocation(android.view.View parent, int gravity, int x, int y)需要注意的是在左侧时,gravity参数要为
Gravity.CENTER | Gravity.START在右侧时,则是
Gravity.CENTER | Gravity.END对齐方式不同,子菜单的位置也会有所改变。
至此,悬浮窗的关键实现都基本写完,如有写得不好的地方,欢迎指教~
最后,附上该悬浮窗源码分享:http://download.csdn.net/detail/weixin_35796901/9596322
- Android之项目级悬浮窗开发教程
- android 开发教程之日历项目实践
- Android开发之使用WindowManager开发悬浮窗口
- Android项目之——Activity悬浮并可拖动
- Android项目之——Activity悬浮并可拖动
- Android项目之——Activity悬浮并可拖动(访悬浮歌词)
- Android常用控件之悬浮窗
- Android常用控件之悬浮窗
- Android之悬浮窗和WindowManager
- Android之仿IOS悬浮窗
- Android开发笔记之自定义View 悬浮球的实现
- 悬浮窗开发Demo
- android 开发实现悬浮窗体
- android开发中悬浮窗被禁用,无权限开启悬浮窗的解决方案
- Html5页面开发app之查询按钮悬浮窗
- Html5页面开发app之查询按钮悬浮窗
- Html5页面开发app之查询按钮悬浮窗
- Android 悬浮窗显示毫秒级时间
- 网络IP地址的分类
- Java实现Base64加密解密
- ^H 终端退格键
- Ubuntu配置apt-get源
- NSScanner类的基本用法
- Android之项目级悬浮窗开发教程
- Java对象序列化详解
- redis安装及配置
- Linux ssh 免密码登陆
- Android studio默认安装路径在哪里?
- 领导者-追随者模式
- 关于论文通信作者
- Linux修改密码
- React Native学习之自定义NavigationBar