[摘要]本文介绍Android在桌面添加可拖动、点击的悬浮窗口,并提供详细的示例代码供参考。
用过新版本android 360手机助手都人都对 360中只在桌面显示一个小小悬浮窗口羡慕不已吧?
其实实现这种功能,主要有两步:
1.判断当前显示的是为桌面。这个内容我在前面的帖子里面已经有过介绍,如果还没看过的赶快稳步看一下哦。
2.使用windowManager往最顶层添加一个View
这个知识点就是为本文主要讲解的内容哦。在本文的讲解中,我们还会讲到下面的知识点:
a.如果获取到状态栏的高度
b.悬浮窗口的拖动
c.悬浮窗口的点击事件
有开始之前,我们先来看一下效果图:
接下来我们来看看FloatView的代码:
1 | public class FloatView extends ImageView{ |
2 | private float mTouchX; |
3 | private float mTouchY; |
4 | private float x; |
5 | private float y; |
6 | private float mStartX; |
7 | private float mStartY; |
8 | private OnClickListener mClickListener; |
9 | |
10 | private WindowManager windowManager = (WindowManager) getContext() |
11 | .getApplicationContext().getSystemService(Context.WINDOW_SERVICE); |
12 | // 此windowManagerParams变量为获取的全局变量,用以保存悬浮窗口的属性 |
13 | private WindowManager.LayoutParams windowManagerParams = ((FloatApplication) getContext() |
14 | .getApplicationContext()).getWindowParams(); |
15 | |
16 | public FloatView(Context context) { |
17 | super(context); |
18 | } |
19 | |
20 | @Override |
21 | public boolean onTouchEvent(MotionEvent event) { |
22 | //获取到状态栏的高度 |
23 | Rect frame = new Rect(); |
24 | getWindowVisibleDisplayFrame(frame); |
25 | int statusBarHeight = frame.top; |
26 | System.out.println("statusBarHeight:"+statusBarHeight); |
27 | // 获取相对屏幕的坐标,即以屏幕左上角为原点 |
28 | x = event.getRawX(); |
29 | y = event.getRawY() - statusBarHeight; // statusBarHeight是系统状态栏的高度 |
30 | Log.i("tag", "currX" + x + "====currY" + y); |
31 | switch (event.getAction()) { |
32 | case MotionEvent.ACTION_DOWN: // 捕获手指触摸按下动作 |
33 | // 获取相对View的坐标,即以此View左上角为原点 |
34 | mTouchX = event.getX(); |
35 | mTouchY = event.getY(); |
36 | mStartX = x; |
37 | mStartY = y; |
38 | Log.i("tag", "startX" + mTouchX + "====startY" |
39 | + mTouchY); |
40 | break; |
41 | |
42 | case MotionEvent.ACTION_MOVE: // 捕获手指触摸移动动作 |
43 | updateViewPosition(); |
44 | break; |
45 | |
46 | case MotionEvent.ACTION_UP: // 捕获手指触摸离开动作 |
47 | updateViewPosition(); |
48 | mTouchX = mTouchY = 0; |
49 | if ((x - mStartX) < 5 && (y - mStartY) < 5) { |
50 | if(mClickListener!=null) { |
51 | mClickListener.onClick(this); |
52 | } |
53 | } |
54 | break; |
55 | } |
56 | return true; |
57 | } |
58 | @Override |
59 | public void setOnClickListener(OnClickListener l) { |
60 | this.mClickListener = l; |
61 | } |
62 | private void updateViewPosition() { |
63 | // 更新浮动窗口位置参数 |
64 | windowManagerParams.x = (int) (x - mTouchX); |
65 | windowManagerParams.y = (int) (y - mTouchY); |
66 | windowManager.updateViewLayout(this, windowManagerParams); // 刷新显示 |
67 | } |
68 | } |
代码解释:
int statusBarHeight = frame.top;
为获取状态栏的高度,为什么在event.getRawY()的时候减去状态栏的高度呢?
因为我们的悬浮窗口不可能显示到状态栏中去,而后getRawY为获取到屏幕原点的距离。当我们屏幕处于全屏模式时,获取到的状态栏高度会变成0 。
(x - mStartX) < 5 && (y - mStartY) < 5
如果我们在触摸过程中,移动距离少于5 ,则视为点击,触发点击的回调。
另外我们需要自定义一个application:
1 | public class FloatApplication extends Application { |
2 | private WindowManager.LayoutParams windowParams = new WindowManager.LayoutParams(); |
3 | |
4 | public WindowManager.LayoutParams getWindowParams() { |
5 | return windowParams; |
6 | } |
7 | } |
代码解释:
自定义application的目的是为了保存windowParams的值 ,因为我们在拖动悬浮窗口的时候,如果每次都重新new一个layoutParams的话,在update的时候会在异常发现。
windowParams的值也不一定非得在自定义application里面来保存,只要是全局的都行。
最后我们再来看看Activity中的实现。
1 | public class MainActivity extends Activity implements OnClickListener{ |
2 | private WindowManager windowManager = null; |
3 | private WindowManager.LayoutParams windowManagerParams = null; |
4 | private FloatView floatView = null; |
5 | |
6 | @Override |
7 | public void onCreate(Bundle savedInstanceState) { |
8 | super.onCreate(savedInstanceState); |
9 | requestWindowFeature(Window.FEATURE_NO_TITLE);//取消标题栏 |
10 | getWindow().setFlags(WindowManager.LayoutParams. FLAG_FULLSCREEN , |
11 | WindowManager.LayoutParams. FLAG_FULLSCREEN);//全屏 |
12 | setContentView(R.layout.activity_main); |
13 | createView(); |
14 | } |
15 | |
16 | @Override |
17 | public boolean onCreateOptionsMenu(Menu menu) { |
18 | getMenuInflater().inflate(R.menu.activity_main, menu); |
19 | return true; |
20 | } |
21 | |
22 | public void onDestroy() { |
23 | super.onDestroy(); |
24 | // 在程序退出(Activity销毁)时销毁悬浮窗口 |
25 | windowManager.removeView(floatView); |
26 | } |
27 | |
28 | private void createView() { |
29 | floatView = new FloatView(getApplicationContext()); |
30 | floatView.setOnClickListener(this); |
31 | floatView.setImageResource(R.drawable.ic_launcher); // 这里简单的用自带的icon来做演示 |
32 | // 获取WindowManager |
33 | windowManager = (WindowManager) getApplicationContext().getSystemService(Context.WINDOW_SERVICE); |
34 | // 设置LayoutParams(全局变量)相关参数 |
35 | windowManagerParams = ((FloatApplication) getApplication()).getWindowParams(); |
36 | |
37 | windowManagerParams.type = LayoutParams.TYPE_PHONE; // 设置window type |
38 | windowManagerParams.format = PixelFormat.RGBA_8888; // 设置图片格式,效果为背景透明 |
39 | // 设置Window flag |
40 | windowManagerParams.flags = LayoutParams.FLAG_NOT_TOUCH_MODAL |
41 | | LayoutParams.FLAG_NOT_FOCUSABLE; |
42 | /* |
43 | * 注意,flag的值可以为: |
44 | * LayoutParams.FLAG_NOT_TOUCH_MODAL 不影响后面的事件 |
45 | * LayoutParams.FLAG_NOT_FOCUSABLE 不可聚焦 |
46 | * LayoutParams.FLAG_NOT_TOUCHABLE 不可触摸 |
47 | */ |
48 | // 调整悬浮窗口至左上角,便于调整坐标 |
49 | windowManagerParams.gravity = Gravity.LEFT | Gravity.TOP; |
50 | // 以屏幕左上角为原点,设置x、y初始值 |
51 | windowManagerParams.x = 0; |
52 | windowManagerParams.y = 0; |
53 | // 设置悬浮窗口长宽数据 |
54 | windowManagerParams.width = LayoutParams.WRAP_CONTENT; |
55 | windowManagerParams.height = LayoutParams.WRAP_CONTENT; |
56 | // 显示myFloatView图像 |
57 | windowManager.addView(floatView, windowManagerParams); |
58 | } |
59 | |
60 | public void onClick(View v) { |
61 | Toast.makeText(this, "Clicked", Toast.LENGTH_SHORT).show(); |
62 | } |
63 | } |
代码解释:
在activity中我们主要是添加悬浮窗,并且设置他的位置。另外需要注意flags的应用:
1 | LayoutParams.FLAG_NOT_TOUCH_MODAL 不影响后面的事件 |
2 | LayoutParams.FLAG_NOT_FOCUSABLE 不可聚焦 |
3 | LayoutParams.FLAG_NOT_TOUCHABLE 不可触摸 |
最后我们在onDestroy()中移除到悬浮窗口。所以,我们测试的时候,记得按Home键来切换到桌面。
最后千万记得,在androidManifest.xml中来申明我们需要用到的ndroid.permission.SYSTEM_ALERT_WINDOW权限,并且记得申明我们自定义的application哦。
AndroidManifest.xml代码如下:
1 | <manifest xmlns:android="http://schemas.android.com/apk/res/android" |
2 | package="com.krislq.floating" |
3 | android:versionCode="1" |
4 | android:versionName="1.0" > |
5 | |
6 | <uses-sdk |
7 | android:minSdkVersion="8" |
8 | android:targetSdkVersion="15" /> |
9 | <uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" /> |
10 | <application |
11 | android:icon="@drawable/ic_launcher" |
12 | android:label="@string/app_name" |
13 | android:theme="@style/AppTheme" android:name="FloatApplication"> |
14 | <activity |
15 | android:name=".MainActivity" |
16 | android:label="@string/title_activity_main" > |
17 | <intent-filter> |
18 | <action android:name="android.intent.action.MAIN" /> |
19 | |
20 | <category android:name="android.intent.category.LAUNCHER" /> |
21 | </intent-filter> |
22 | </activity> |
23 | </application> |
24 | </manifest> |