Android实现类似360,QQ管家那样的悬浮窗
程序员文章站
2023-12-03 14:45:40
一、前言: 我手机从来不装这些东西,不过,有次看到同事的android手机上,有个qq管家在桌面上浮着,同事拖动管家时,管家就变成一只鸟,桌面下方还有个弹弓,桌面顶部有只乌...
一、前言:
我手机从来不装这些东西,不过,有次看到同事的android手机上,有个qq管家在桌面上浮着,同事拖动管家时,管家就变成一只鸟,桌面下方还有个弹弓,桌面顶部有只乌鸦,把管家也就是鸟拖动到弹弓那,然后,松手,鸟就飞出去。这个过程是动画过程,做的事,实际上是清楚内存。
二:原理:
其实,没什么原理,用到的就是windowmanager以及windowmanager.layoutparams,对这个layoutparams做文章,当设置为属性后,然后,创建一个view,将这个view添加到windowmanager中就行。
package com.chris.floats.window;
import android.os.bundle;
import android.util.displaymetrics;
import android.view.gravity;
import android.view.windowmanager;
import android.app.activity;
import android.content.context;
public class mainactivity extends activity {
private static windowmanager mwindowmgr = null;
private windowmanager.layoutparams mwindowmgrparams = null;
private static floatswindowview mfloatswindowview = null;
@override
protected void oncreate(bundle savedinstancestate) {
super.oncreate(savedinstancestate);
setcontentview(r.layout.activity_main);
}
/*
* 显示应用主界面时,去除悬浮层
*/
@override
public void onwindowfocuschanged(boolean hasfocus) {
if(hasfocus){
if(mfloatswindowview != null){
mwindowmgr.removeview(mfloatswindowview);
mfloatswindowview = null;
}
}else{
getwindowlayout();
}
}
private void initparams(){
displaymetrics dm = getresources().getdisplaymetrics();
mwindowmgrparams.x = dm.widthpixels - 136;
mwindowmgrparams.y = 300;
mwindowmgrparams.width = 136;
mwindowmgrparams.height = 136;
}
private void getwindowlayout(){
if(mfloatswindowview == null){
mwindowmgr = (windowmanager)getbasecontext().getsystemservice(context.window_service);
mwindowmgrparams = new windowmanager.layoutparams();
/*
* 2003 在指悬浮在所有界面之上
* (4.0+系统中,在下拉菜单下面,而在2.3中,在上拉菜单之上)
*/
mwindowmgrparams.type = 2003;
mwindowmgrparams.format = 1;
/*
* 代码实际是wmparams.flags |= flag_not_focusable;
* 40的由来是wmparams的默认属性(32)+ flag_not_focusable(8)
*/
mwindowmgrparams.flags = 40;
mwindowmgrparams.gravity = gravity.left | gravity.top;
initparams();
mfloatswindowview = new floatswindowview(this);
mwindowmgr.addview(mfloatswindowview, mwindowmgrparams);
}
}
}
上面代码,主要在getwindowlayout函数中,最后两行就是创建一个view,并加入到windowmanager中。
继承view的悬浮view:
package com.chris.floats.window;
import android.content.context;
import android.content.intent;
import android.graphics.drawable.animationdrawable;
import android.util.attributeset;
import android.util.displaymetrics;
import android.view.gravity;
import android.view.motionevent;
import android.view.view;
import android.view.viewtreeobserver.onpredrawlistener;
import android.view.windowmanager;
public class floatswindowview extends view {
private context mcontext = null;
private windowmanager mwindowmgr = null;
private windowmanager.layoutparams mwindowmgrparams = null;
private animationdrawable manimationdrawable = null;
private int iposx = 0;
private int iposy = 0;
private int ilastposx = 0;
private int ilastposy = 0;
private boolean bmoved = false;
public floatswindowview(context context) {
this(context, null, 0);
}
public floatswindowview(context context, attributeset attrs) {
this(context, attrs, 0);
}
public floatswindowview(context context, attributeset attrs, int defstyle) {
super(context, attrs, defstyle);
mcontext = context;
mwindowmgr = (windowmanager)getcontext().getapplicationcontext().getsystemservice("window");
mwindowmgrparams = new windowmanager.layoutparams();
initparams();
manimationdrawable = new animationdrawable();
for(int i = 0; i < 4; i++){
int id = getresources().getidentifier("a"+ i, "drawable", mcontext.getpackagename());
manimationdrawable.addframe(getresources().getdrawable(id), 100);
}
manimationdrawable.setoneshot(false);
this.setbackgrounddrawable(manimationdrawable);
onpredrawlistener listener = new onpredrawlistener(){
@override
public boolean onpredraw() {
manimationdrawable.start();
return true;
}
};
this.getviewtreeobserver().addonpredrawlistener(listener);
}
private void initparams(){
displaymetrics dm = getresources().getdisplaymetrics();
mwindowmgrparams.x = dm.widthpixels - 136;
mwindowmgrparams.y = 300;
mwindowmgrparams.width = 136;
mwindowmgrparams.height = 136;
}
@override
public boolean ontouchevent(motionevent event) {
switch(event.getaction()){
case motionevent.action_down:
iposx = (int)event.getx();
iposy = (int)event.gety();
bmoved = false;
break;
case motionevent.action_move:
bmoved = true;
ilastposx = (int)event.getx();
ilastposy = (int)event.gety();
updatepostion(ilastposx - iposx, ilastposy - iposy);
break;
case motionevent.action_up:
if(!bmoved){
intent it=new intent(mcontext, mainactivity.class);
mcontext.startactivity(it);
}
break;
default:
break;
}
return true;
}
private void updatepostion(int x, int y){
mwindowmgrparams.type = 2003;
mwindowmgrparams.format = 1;
mwindowmgrparams.flags = 40;
mwindowmgrparams.gravity = gravity.left | gravity.top;
mwindowmgrparams.x += x;
mwindowmgrparams.y += y;
mwindowmgr.updateviewlayout(this, mwindowmgrparams);
}
}
之所以将updateposition中的参数与activity中设置一样,是为了确保在move时,造成相对位置的不一样,而导致闪砾,大家要是不理解,可以实验下。
三、小结:
这篇文章实现了简单的悬浮窗口动画效果,如果要想做成像360,qq管家那样,还需要一些其它的操作:
1. 比如启动一个后台服务来监控系统信息;
2. action_down时,修改悬浮窗口上的图片;
3. action_move时窗口跟随;
4. action_up时,创建一个线程,来完成释放后,向上运动的动画过程等;
我手机从来不装这些东西,不过,有次看到同事的android手机上,有个qq管家在桌面上浮着,同事拖动管家时,管家就变成一只鸟,桌面下方还有个弹弓,桌面顶部有只乌鸦,把管家也就是鸟拖动到弹弓那,然后,松手,鸟就飞出去。这个过程是动画过程,做的事,实际上是清楚内存。
二:原理:
其实,没什么原理,用到的就是windowmanager以及windowmanager.layoutparams,对这个layoutparams做文章,当设置为属性后,然后,创建一个view,将这个view添加到windowmanager中就行。
复制代码 代码如下:
package com.chris.floats.window;
import android.os.bundle;
import android.util.displaymetrics;
import android.view.gravity;
import android.view.windowmanager;
import android.app.activity;
import android.content.context;
public class mainactivity extends activity {
private static windowmanager mwindowmgr = null;
private windowmanager.layoutparams mwindowmgrparams = null;
private static floatswindowview mfloatswindowview = null;
@override
protected void oncreate(bundle savedinstancestate) {
super.oncreate(savedinstancestate);
setcontentview(r.layout.activity_main);
}
/*
* 显示应用主界面时,去除悬浮层
*/
@override
public void onwindowfocuschanged(boolean hasfocus) {
if(hasfocus){
if(mfloatswindowview != null){
mwindowmgr.removeview(mfloatswindowview);
mfloatswindowview = null;
}
}else{
getwindowlayout();
}
}
private void initparams(){
displaymetrics dm = getresources().getdisplaymetrics();
mwindowmgrparams.x = dm.widthpixels - 136;
mwindowmgrparams.y = 300;
mwindowmgrparams.width = 136;
mwindowmgrparams.height = 136;
}
private void getwindowlayout(){
if(mfloatswindowview == null){
mwindowmgr = (windowmanager)getbasecontext().getsystemservice(context.window_service);
mwindowmgrparams = new windowmanager.layoutparams();
/*
* 2003 在指悬浮在所有界面之上
* (4.0+系统中,在下拉菜单下面,而在2.3中,在上拉菜单之上)
*/
mwindowmgrparams.type = 2003;
mwindowmgrparams.format = 1;
/*
* 代码实际是wmparams.flags |= flag_not_focusable;
* 40的由来是wmparams的默认属性(32)+ flag_not_focusable(8)
*/
mwindowmgrparams.flags = 40;
mwindowmgrparams.gravity = gravity.left | gravity.top;
initparams();
mfloatswindowview = new floatswindowview(this);
mwindowmgr.addview(mfloatswindowview, mwindowmgrparams);
}
}
}
上面代码,主要在getwindowlayout函数中,最后两行就是创建一个view,并加入到windowmanager中。
继承view的悬浮view:
复制代码 代码如下:
package com.chris.floats.window;
import android.content.context;
import android.content.intent;
import android.graphics.drawable.animationdrawable;
import android.util.attributeset;
import android.util.displaymetrics;
import android.view.gravity;
import android.view.motionevent;
import android.view.view;
import android.view.viewtreeobserver.onpredrawlistener;
import android.view.windowmanager;
public class floatswindowview extends view {
private context mcontext = null;
private windowmanager mwindowmgr = null;
private windowmanager.layoutparams mwindowmgrparams = null;
private animationdrawable manimationdrawable = null;
private int iposx = 0;
private int iposy = 0;
private int ilastposx = 0;
private int ilastposy = 0;
private boolean bmoved = false;
public floatswindowview(context context) {
this(context, null, 0);
}
public floatswindowview(context context, attributeset attrs) {
this(context, attrs, 0);
}
public floatswindowview(context context, attributeset attrs, int defstyle) {
super(context, attrs, defstyle);
mcontext = context;
mwindowmgr = (windowmanager)getcontext().getapplicationcontext().getsystemservice("window");
mwindowmgrparams = new windowmanager.layoutparams();
initparams();
manimationdrawable = new animationdrawable();
for(int i = 0; i < 4; i++){
int id = getresources().getidentifier("a"+ i, "drawable", mcontext.getpackagename());
manimationdrawable.addframe(getresources().getdrawable(id), 100);
}
manimationdrawable.setoneshot(false);
this.setbackgrounddrawable(manimationdrawable);
onpredrawlistener listener = new onpredrawlistener(){
@override
public boolean onpredraw() {
manimationdrawable.start();
return true;
}
};
this.getviewtreeobserver().addonpredrawlistener(listener);
}
private void initparams(){
displaymetrics dm = getresources().getdisplaymetrics();
mwindowmgrparams.x = dm.widthpixels - 136;
mwindowmgrparams.y = 300;
mwindowmgrparams.width = 136;
mwindowmgrparams.height = 136;
}
@override
public boolean ontouchevent(motionevent event) {
switch(event.getaction()){
case motionevent.action_down:
iposx = (int)event.getx();
iposy = (int)event.gety();
bmoved = false;
break;
case motionevent.action_move:
bmoved = true;
ilastposx = (int)event.getx();
ilastposy = (int)event.gety();
updatepostion(ilastposx - iposx, ilastposy - iposy);
break;
case motionevent.action_up:
if(!bmoved){
intent it=new intent(mcontext, mainactivity.class);
mcontext.startactivity(it);
}
break;
default:
break;
}
return true;
}
private void updatepostion(int x, int y){
mwindowmgrparams.type = 2003;
mwindowmgrparams.format = 1;
mwindowmgrparams.flags = 40;
mwindowmgrparams.gravity = gravity.left | gravity.top;
mwindowmgrparams.x += x;
mwindowmgrparams.y += y;
mwindowmgr.updateviewlayout(this, mwindowmgrparams);
}
}
之所以将updateposition中的参数与activity中设置一样,是为了确保在move时,造成相对位置的不一样,而导致闪砾,大家要是不理解,可以实验下。
三、小结:
这篇文章实现了简单的悬浮窗口动画效果,如果要想做成像360,qq管家那样,还需要一些其它的操作:
1. 比如启动一个后台服务来监控系统信息;
2. action_down时,修改悬浮窗口上的图片;
3. action_move时窗口跟随;
4. action_up时,创建一个线程,来完成释放后,向上运动的动画过程等;