版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领
文档简介
【移动应用开发技术】Android中怎么实现桌面悬浮
Android中怎么实现桌面悬浮,相信很多没有经验的人对此束手无策,为此本文总结了问题出现的原因和解决方法,通过这篇文章希望你能解决这个问题。实现原理
这种桌面悬浮窗的效果很类似与Widget,但是它比Widget要灵活的多。主要是通过WindowManager这个类来实现的,调用这个类的addView方法用于添加一个悬浮窗,updateViewLayout方法用于更新悬浮窗的参数,removeView用于移除悬浮窗。其中悬浮窗的参数有必要详细说明一下。WindowManager.LayoutParams这个类用于提供悬浮窗所需的参数,其中有几个经常会用到的变量:type值用于确定悬浮窗的类型,一般设为2002,表示在所有应用程序之上,但在状态栏之下。flags值用于确定悬浮窗的行为,比如说不可聚焦,非模态对话框等等,属性非常多,大家可以查看文档。gravity值用于确定悬浮窗的对齐方式,一般设为左上角对齐,这样当拖动悬浮窗的时候方便计算坐标。x值用于确定悬浮窗的位置,如果要横向移动悬浮窗,就需要改变这个值。y值用于确定悬浮窗的位置,如果要纵向移动悬浮窗,就需要改变这个值。width值用于指定悬浮窗的宽度。height值用于指定悬浮窗的高度。创建悬浮窗这种窗体需要向用户申请权限才可以的,因此还需要在AndroidManifest.xml中加入<uses-permission
android:name="android.permission.SYSTEM_ALERT_WINDOW"
/>
<uses-permission
android:name="android.permission.GET_TASKS"
/>codeactivity_main.xml<LinearLayout
xmlns:android="/apk/res/android"
xmlns:tools="/tools"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
tools:context=".MainActivity"
android:orientation="vertical"
>
<Button
android:id="@+id/start_float_window"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="打开悬浮"
>
</Button>
</LinearLayout>float_window_small.xml,用于做为小悬浮窗的布局<?xml
version="1.0"
encoding="UTF-8"?>
<LinearLayout
xmlns:android="/apk/res/android"
android:id="@+id/small_window_layout"
android:layout_width="60dip"
android:layout_height="25dip"
android:background="@drawable/bg_small"
>
<TextView
android:id="@+id/percent"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:gravity="center"
android:textColor="#ffffff"
/>
</LinearLayout>float_window_big.xml,用于做为大悬浮窗的布局<?xml
version="1.0"
encoding="UTF-8"?>
<LinearLayout
xmlns:android="/apk/res/android"
android:id="@+id/big_window_layout"
android:layout_width="200dip"
android:layout_height="100dip"
android:background="@drawable/bg_big"
android:orientation="vertical"
>
<Button
android:id="@+id/close"
android:layout_width="100dip"
android:layout_height="40dip"
android:layout_gravity="center_horizontal"
android:layout_marginTop="12dip"
android:text="关闭悬浮窗"
/>
<Button
android:id="@+id/back"
android:layout_width="100dip"
android:layout_height="40dip"
android:layout_gravity="center_horizontal"
android:text="返回"
/>
</LinearLayout>然后打开MainActivity.java,这是项目的主界面@Override
protected
void
onCreate(Bundle
savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Button
startFloatWindow
=
(Button)
findViewById(R.id.start_float_window);
startFloatWindow.setOnClickListener(new
OnClickListener()
{
@Override
public
void
onClick(View
arg0)
{
//
开启服务
Intent
intent
=
new
Intent(MainActivity.this,
FloatWindowService.class);
startService(intent);
finish();
}
});
}MainActivity的代码就是对开启悬浮窗的按钮注册了一个点击事件,用于打开一个服务,然后关闭当前Activity。新建一个名为FloatWindowService的类,这个类继承自Servicepublic
class
FloatWindowService
extends
Service
{
/**
*
用于在线程中创建或移除悬浮窗。
*/
private
Handler
handler
=
new
Handler();
/**
*
定时器,定时进行检测当前应该创建还是移除悬浮窗。
*/
private
Timer
timer;
/**
*
不是绑定的开启服务
*/
@Override
public
IBinder
onBind(Intent
intent)
{
return
null;
}
/**
*
Service的生命周期中会调用此函数
*/
@Override
public
int
onStartCommand(Intent
intent,
int
flags,
int
startId)
{
//
开启定时器,每隔0.5秒刷新一次
if
(timer
==
null)
{
timer
=
new
Timer();
timer.scheduleAtFixedRate(new
RefreshTask(),
0,
500);
}
return
super.onStartCommand(intent,
flags,
startId);
}
/**
*
Service终止
*/
@Override
public
void
onDestroy()
{
super.onDestroy();
//
Service被终止的同时也停止定时器继续运行
timer.cancel();
timer
=
null;
}
/**
*
判断桌面上悬浮框状态
*
*
@author
sansung
*
*/
class
RefreshTask
extends
TimerTask
{
@Override
public
void
run()
{
//
当前界面是桌面,且没有悬浮窗显示,则创建悬浮窗。
if
(isHome()
&&
!MyWindowManager.isWindowShowing())
{
handler.post(new
Runnable()
{
@Override
public
void
run()
{
MyWindowManager
.createSmallWindow(getApplicationContext());
}
});
}
//
当前界面不是桌面,且有悬浮窗显示,则移除悬浮窗。
else
if
(!isHome()
&&
MyWindowManager.isWindowShowing())
{
handler.post(new
Runnable()
{
@Override
public
void
run()
{
MyWindowManager
.removeSmallWindow(getApplicationContext());
MyWindowManager
.removeBigWindow(getApplicationContext());
}
});
}
//
当前界面是桌面,且有悬浮窗显示,则更新内存数据。
else
if
(isHome()
&&
MyWindowManager.isWindowShowing())
{
handler.post(new
Runnable()
{
@Override
public
void
run()
{
MyWindowManager
.updateUsedPercent(getApplicationContext());
}
});
}
}
}
/**
*
判断当前界面是否是桌面
*/
private
boolean
isHome()
{
ActivityManager
mActivityManager
=
(ActivityManager)
getSystemService(Context.ACTIVITY_SERVICE);
List<RunningTaskInfo>
rti
=
mActivityManager.getRunningTasks(1);
//
如果在桌面,输出的是桌面的包名
//
不在桌面的话输出的是打开的app的包名
//
System.out.println("getHomes().contains(rti.get(0).topActivity.getPackageName())+++"+rti.get(0).topActivity.getPackageName());
return
getHomes().contains(rti.get(0).topActivity.getPackageName());
}
/**
*
获得属于桌面的应用的应用包名称
*
*
@return
返回包含所有包名的字符串列表
*/
private
List<String>
getHomes()
{
List<String>
names
=
new
ArrayList<String>();
PackageManager
packageManager
=
this.getPackageManager();
Intent
intent
=
new
Intent(Intent.ACTION_MAIN);
intent.addCategory(Intent.CATEGORY_HOME);
List<ResolveInfo>
resolveInfo
=
packageManager.queryIntentActivities(
intent,
PackageManager.MATCH_DEFAULT_ONLY);
for
(ResolveInfo
ri
:
resolveInfo)
{
//
桌面包名
//
System.out.println("ri.activityInfo.packageName++++"+ri.activityInfo.packageName);
names.add(ri.activityInfo.packageName);
}
return
names;
}
}FloatWindowService的onStartCommand方法中开启了一个定时器,每隔500毫秒就会执行RefreshTask。在RefreshTask当中,要进行判断,如果手机当前是在桌面的话,就应该显示悬浮窗,如果手机打开了某一个应用程序,就应该移除悬浮窗,如果手机在桌面的话,还应该更新内存使用百分比的数据。而当FloatWindowService被销毁的时候,应该将定时器停止,否则它还会一直运行。创建和移除悬浮窗,以及更新悬浮窗内的数据,都是由MyWindowManager这个类来管理的,比起直接把这些代码写在Activity或Service当中,使用一个专门的工具类来管理要好的多。不过要想创建悬浮窗,还是先要把悬浮窗的View写出来。新建一个名叫FloatWindowSmallView的类,继承自LinearLayout。新建一个名叫FloatWindowBigView的类,也继承自LinearLayout。FloatWindowSmallViewpublic
class
FloatWindowSmallView
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;
/**
*
构造函数
*
*
@param
context
*
上下文
*/
public
FloatWindowSmallView(Context
context)
{
super(context);
//
得到windowManager
windowManager
=
(WindowManager)
context
.getSystemService(Context.WINDOW_SERVICE);
LayoutInflater.from(context).inflate(R.layout.float_window_small,
this);
View
view
=
findViewById(R.id.small_window_layout);
viewWidth
=
view.getLayoutParams().width;
viewHeight
=
view.getLayoutParams().height;
TextView
percentView
=
(TextView)
findViewById(R.id.percent);
percentView.setText(MyWindowManager.getUsedPercentValue(context));
}
@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)
{
openBigWindow();
}
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
openBigWindow()
{
MyWindowManager.createBigWindow(getContext());
MyWindowManager.removeSmallWindow(getContext());
}
/**
*
用于获取状态栏的高度。
*
*
@return
返回状态栏高度的像素值。
*/
private
int
getStatusBarHeight()
{
if
(statusBarHeight
==
0)
{
try
{
Class<?>
c
=
Class.forName("ernal.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;
}
}其中,对这个View的onTouchEvent事件进行了重写,用于实现拖动和点击的效果。如果发现用户触发了ACTION_DOWN事件,会记录按下时的坐标等数据。如果发现用户触发了ACTION_MOVE事件,则根据当前移动的坐标更新悬浮窗在屏幕中的位置。如果发现用户触发了ACTION_UP事件,会和ACTION_DOWN中记下的坐标对比,如果发现是相同的,则视为用户对悬浮窗进行了点击。点击小悬浮窗则打开大悬浮窗,然后我们来实现大悬浮窗的View。FloatWindowBigViewpublic
class
FloatWindowBigView
extends
LinearLayout
{
/**
*
记录大悬浮窗的宽度
*/
public
static
int
viewWidth;
/**
*
记录大悬浮窗的高度
*/
public
static
int
viewHeight;
public
FloatWindowBigView(final
Context
context)
{
super(context);
LayoutInflater.from(context).inflate(R.layout.float_window_big,
this);
View
view
=
findViewById(R.id.big_window_layout);
viewWidth
=
view.getLayoutParams().width;
viewHeight
=
view.getLayoutParams().height;
Button
close
=
(Button)
findViewById(R.id.close);
Button
back
=
(Button)
findViewById(R.id.back);
close.setOnClickListener(new
OnClickListener()
{
@Override
public
void
onClick(View
v)
{
//
点击关闭悬浮窗的时候,移除所有悬浮窗,并停止Service
MyWindowManager.removeBigWindow(context);
MyWindowManager.removeSmallWindow(context);
Intent
intent
=
new
Intent(getContext(),
FloatWindowService.class);
context.stopService(intent);
}
});
back.setOnClickListener(new
OnClickListener()
{
@Override
public
void
onClick(View
v)
{
//
点击返回的时候,移除大悬浮窗,创建小悬浮窗
MyWindowManager.removeBigWindow(context);
MyWindowManager.createSmallWindow(context);
}
});
}
}比起FloatWindowSmallView,FloatWindowBigView要简单的多,其中只有两个按钮,点击close按钮,将悬浮窗全部移除,并将Service终止。单击back按钮则移除大悬浮窗,重新创建小悬浮窗。现在两个悬浮窗的View都已经写好了,我们来创建MyWindowManagerpublic
class
MyWindowManager
{
/**
*
小悬浮窗View的实例
*/
private
static
FloatWindowSmallView
smallWindow;
/**
*
大悬浮窗View的实例
*/
private
static
FloatWindowBigView
bigWindow;
/**
*
小悬浮窗View的参数
*/
private
static
LayoutParams
smallWindowParams;
/**
*
大悬浮窗View的参数
*/
private
static
LayoutParams
bigWindowParams;
/**
*
用于控制在屏幕上添加或移除悬浮窗
*/
private
static
WindowManager
mWindowManager;
/**
*
用于获取手机可用内存
*/
private
static
ActivityManager
mActivityManager;
/**
*
创建一个小悬浮窗。初始位置为屏幕的右部中间位置。
*
*
@param
context
*
必须为应用程序的Context.
*/
public
static
void
createSmallWindow(Context
context)
{
//
创建windowmanager
WindowManager
windowManager
=
getWindowManager(context);
//
得到window长宽
int
screenWidth
=
windowManager.getDefaultDisplay().getWidth();
int
screenHeight
=
windowManager.getDefaultDisplay().getHeight();
if
(smallWindow
==
null)
{
smallWindow
=
new
FloatWindowSmallView(context);
if
(smallWindowParams
==
null)
{
smallWindowParams
=
new
LayoutParams();
smallWindowParams.type
=
LayoutParams.TYPE_PHONE;
smallWindowParams.format
=
PixelFormat.RGBA_8888;
smallWindowParams.flags
=
LayoutParams.FLAG_NOT_TOUCH_MODAL
|
LayoutParams.FLAG_NOT_FOCUSABLE;
smallWindowParams.gravity
=
Gravity.LEFT
|
Gravity.TOP;
smallWindowParams.width
=
FloatWindowSmallView.viewWidth;
smallWindowParams.height
=
FloatWindowSmallView.viewHeight;
smallWindowParams.x
=
screenWidth;
smallWindowParams.y
=
screenHeight
/
2;
}
//
配置
smallWindow.setParams(smallWindowParams);
//
在windowmanager中添加
windowManager.addView(smallWindow,
smallWindowParams);
}
}
/**
*
将小悬浮窗从屏幕上移除。
*
*
@param
context
*
必须为应用程序的Context.
*/
public
static
void
removeSmallWindow(Context
context)
{
if
(smallWindow
!=
null)
{
WindowManager
windowManager
=
getWindowManager(context);
windowManager.removeView(smallWindow);
smallWindow
=
null;
}
}
/**
*
创建一个大悬浮窗。位置为屏幕正中间。
*
*
@param
context
*
必须为应用程序的Context.
*/
public
static
void
createBigWindow(Context
context)
{
WindowManager
windowManager
=
getWindowManager(context);
int
screenWidth
=
windowManager.getDefaultDisplay().getWidth();
int
screenHeight
=
windowManager.getDefaultDisplay().getHeight();
if
(bigWindow
==
null)
{
bigWindow
=
new
FloatWindowBigView(context);
if
(bigWindowParams
==
null)
{
bigWindowParams
=
new
LayoutParams();
bigWindowParams.x
=
screenWidth
/
2
-
FloatWindowBigView.viewWidth
/
2;
bigWindowParams.y
=
screenHeight
/
2
-
FloatWindowBigView.viewHeight
/
2;
bigWindowParams.type
=
LayoutParams.TYPE_PHONE;
bigWindowParams.format
=
PixelFormat.RGBA_8888;
bigWindowParams.gravity
=
Gravity.LEFT
|
Gravity.TOP;
bigWindowParams.width
=
FloatWindowBigView.viewWidth;
bigWindowParams.height
=
FloatWindowBigView.viewHeight;
}
windowManager.addView(bigWindow,
bigWindowParams);
}
}
/**
*
将大悬浮窗从屏幕上移除。
*
*
@param
context
*
必须为应用程序的Context.
*/
public
static
void
removeBigWindow(Context
context)
{
if
(bigWindow
!=
null)
{
WindowManager
windowManager
=
getWindowManager(context);
windowManager.removeView(bigWindow);
bigWindow
=
null;
}
}
/**
*
更新小悬浮窗的TextView上的数据,显示内存使用的百分比。
*
*
@param
context
*
可传入应用程序上下文。
*/
public
static
void
updateUsedPercent(Context
context)
{
if
(smallWindow
!=
null)
{
TextView
percentView
=
(TextView)
smallWindow
.findViewById(R.id.percent);
percentView.setText(getUsedPercentValue(context));
}
}
/**
*
是否有悬浮窗(包括小悬浮窗和大悬浮窗)显示在屏幕上。
*
*
@return
有悬浮窗显示在桌面上返回true,没有的话返回false。
*/
public
static
boolean
isWindowShowing()
{
return
smallWindow
!=
null
||
bigWindow
!=
null;
}
/**
*
如果WindowManager还未创建,则创建一个新的WindowManager返回。否则返回当前已创建的WindowManager。
*
*
@param
context
*
必须为应用程序的Context.
*
@return
WindowManager的实例,用于控制在屏幕上添加或移除悬浮窗。
*/
private
static
WindowManager
getWindowManager(Context
context)
{
if
(mWindowManager
==
null)
{
温馨提示
- 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
- 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
- 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
- 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
- 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
- 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
- 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。
最新文档
- 1.6 解决简单的周期问题(分层作业) 人教版数学(2024)二年级下册(含答案)
- 2026年南阳科技职业学院单招职业适应性考试题库附参考答案详解(夺分金卷)
- 2026年包头铁道职业技术学院单招职业技能考试题库附答案详解(巩固)
- 2026年兰州航空职业技术学院单招职业技能考试题库附参考答案详解(黄金题型)
- 2026年北京科技大学天津学院单招职业技能测试题库附参考答案详解(巩固)
- 2026年南昌交通学院单招职业适应性测试题库及答案详解(网校专用)
- 2026年内江职业技术学院单招职业技能考试题库带答案详解(基础题)
- 2026年兰州资源环境职业技术大学单招职业倾向性考试题库及答案详解(夺冠系列)
- 某省市某省市及公共服务平台解决方案
- 液化气体生产工常识水平考核试卷含答案
- 2026年《必背60题》党校教师高频面试题包含详细解答
- 安全监察队伍培训班课件
- 儿科用药错误PDCA根因与改进
- 2026年青岛港湾职业技术学院单招综合素质笔试备考试题附答案详解
- 2025年重庆基层法律服务考试真题及答案
- 《化肥产品生产许可证实施细则(一)》(复肥产品部分)
- 2026年人教部编版小学四年级语文下册教学计划(含进度表)
- 2025年吉林省纪委监委公开遴选公务员笔试试题及答案解析
- 农业服务中心培训课件
- 肺挫伤合并肋骨骨折护理
- 在学习进阶中促进学生语言与思维协同发展
评论
0/150
提交评论