在桌面添加可拖动/点击的悬浮窗口
用过新版本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的应用:
LayoutParams.FLAG_NOT_TOUCH_MODAL 不影响后面的事件
LayoutParams.FLAG_NOT_FOCUSABLE 不可聚焦
LayoutParams.FLAG_NOT_TOUCHABLE 不可触摸
最后我们在onDestroy()中移除到悬浮窗口。所以,我们测试的时候,记得按Home键来切换到桌面。
最后千万记得,在androidManifest.xml中来申明我们需要用到的android.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>