本文講解的是Android的懸浮窗機制,這個懸浮窗在不少第三方ROM會被屏蔽,像是小米,錘子上都沒法顯示。小米卻是能夠經過開關開啓,但在錘子上根本連開的機會都沒有,真是無奈啊…… 雖然懸浮窗在實際中比較難以推廣,但學習方面仍是沒問題的啦。android
1、常規懸浮窗windows
思路:app
1.創建一個服務,而且在裏面生成一個WindowManager對象,經過它來加載一個視圖做爲懸浮窗。框架
2.設置WindowManager的參數Paramside
3.設置一個容器來找到懸浮窗的父控件,並綁定到windowManager中去佈局
4.經過父控件來加載懸浮窗的視圖學習
實現:this
佈局文件:spa
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="wrap_content" android:layout_height="wrap_content" android:orientation="vertical" > <ImageButton android:id="@+id/floating_imageView" android:layout_width="wrap_content" android:layout_height="wrap_content" android:src="@drawable/ic_launcher" /> </LinearLayout>
JAVA代碼:.net
/** * 定義浮動窗口布局 */ LinearLayout mlayout; /** * 懸浮窗控件 */ ImageView mfloatingIv; /** * 懸浮窗的佈局 */ WindowManager.LayoutParams wmParams; LayoutInflater inflater; /** * 建立浮動窗口設置佈局參數的對象 */ WindowManager mWindowManager;
1.初始化windowManager,而且找到控件
/** * 初始化windowManager */ private void initWindow() { mWindowManager = (WindowManager) getApplication().getSystemService(Context.WINDOW_SERVICE); wmParams = getParams(wmParams);//設置好懸浮窗的參數 // 懸浮窗默認顯示以左上角爲起始座標 wmParams.gravity = Gravity.LEFT| Gravity.TOP; //懸浮窗的開始位置,由於設置的是從左上角開始,因此屏幕左上角是x=0;y=0 wmParams.x = 50; wmParams.y = 50; //獲得容器,經過這個inflater來得到懸浮窗控件 inflater = LayoutInflater.from(getApplication()); // 獲取浮動窗口視圖所在佈局 mlayout = (LinearLayout) inflater.inflate(R.layout.floating_layout, null); // 添加懸浮窗的視圖 mWindowManager.addView(mlayout, wmParams); } /** 對windowManager進行設置 * @param wmParams * @return */ public WindowManager.LayoutParams getParams(WindowManager.LayoutParams wmParams){ wmParams = new WindowManager.LayoutParams(); //設置window type 下面變量2002是在屏幕區域顯示,2003則能夠顯示在狀態欄之上 //wmParams.type = LayoutParams.TYPE_PHONE; //wmParams.type = LayoutParams.TYPE_SYSTEM_ALERT; wmParams.type = LayoutParams.TYPE_SYSTEM_ERROR; //設置圖片格式,效果爲背景透明 wmParams.format = PixelFormat.RGBA_8888; //設置浮動窗口不可聚焦(實現操做除浮動窗口外的其餘可見窗口的操做) //wmParams.flags = LayoutParams.FLAG_NOT_FOCUSABLE; //設置能夠顯示在狀態欄上 wmParams.flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE| WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL| WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN| WindowManager.LayoutParams.FLAG_LAYOUT_INSET_DECOR| WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH; //設置懸浮窗口長寬數據 wmParams.width = WindowManager.LayoutParams.WRAP_CONTENT; wmParams.height = WindowManager.LayoutParams.WRAP_CONTENT; return wmParams; }
2.在懸浮窗控件中設置事件
/** * 找到懸浮窗的圖標,而且設置事件 * 設置懸浮窗的點擊、滑動事件 */ private void initFloating() { mfloatingIv = (ImageButton) mlayout.findViewById(R.id.floating_imageView); mfloatingIv.getBackground().setAlpha(150); mGestureDetector = new GestureDetector(this, new MyOnGestureListener()); //設置監聽器 mfloatingIv.setOnTouchListener(new FloatingListener()); }
3.設置懸浮窗的拖拽事件和點擊事件
//觸摸監聽器 GestureDetector mGestureDetector;
//開始觸控的座標,移動時的座標(相對於屏幕左上角的座標) private int mTouchStartX,mTouchStartY,mTouchCurrentX,mTouchCurrentY; //開始時的座標和結束時的座標(相對於自身控件的座標) private int mStartX,mStartY,mStopX,mStopY; private boolean isMove;//判斷懸浮窗是否移動 /** * @author:金凱 * @tips :本身寫的懸浮窗監聽器 * @date :2014-3-28 */ private class FloatingListener implements OnTouchListener{ @Override public boolean onTouch(View arg0, MotionEvent event) { int action = event.getAction(); switch(action){ case MotionEvent.ACTION_DOWN: isMove = false; mTouchStartX = (int)event.getRawX(); mTouchStartY = (int)event.getRawY(); mStartX = (int)event.getX(); mStartY = (int)event.getY(); break; case MotionEvent.ACTION_MOVE: mTouchCurrentX = (int) event.getRawX(); mTouchCurrentY = (int) event.getRawY(); wmParams.x += mTouchCurrentX - mTouchStartX; wmParams.y += mTouchCurrentY - mTouchStartY; mWindowManager.updateViewLayout(mlayout, wmParams); mTouchStartX = mTouchCurrentX; mTouchStartY = mTouchCurrentY; break; case MotionEvent.ACTION_UP: mStopX = (int)event.getX(); mStopY = (int)event.getY(); //System.out.println("|X| = "+ Math.abs(mStartX - mStopX)); //System.out.println("|Y| = "+ Math.abs(mStartY - mStopY)); if(Math.abs(mStartX - mStopX) >= 1 || Math.abs(mStartY - mStopY) >= 1){ isMove = true; } break; } return mGestureDetector.onTouchEvent(event); //此處必須返回false,不然OnClickListener獲取不到監聽 } } /** * @author:金凱 * @tips :本身定義的手勢監聽類 * @date :2014-3-29 */ class MyOnGestureListener extends SimpleOnGestureListener { @Override public boolean onSingleTapConfirmed(MotionEvent e) { if (!isMove) { Toast.makeText(getApplicationContext(), "你點擊了懸浮窗", 0).show(); System.out.println("onclick"); } return super.onSingleTapConfirmed(e); } }
4.服務的框架
@Override public IBinder onBind(Intent intent) { return null; } @Override public void onCreate() { super.onCreate(); initWindow();//設置窗口的參數 } @Override public int onStartCommand(Intent intent, int flags, int startId) { initFloating();//設置懸浮窗圖標 return super.onStartCommand(intent, flags, startId); } @Override public void onDestroy() { super.onDestroy(); if (mlayout != null) { // 移除懸浮窗口 mWindowManager.removeView(mlayout); } }
所有代碼:
package com.kale.testfloating; import android.app.Service; import android.content.Context; import android.content.Intent; import android.graphics.PixelFormat; import android.os.IBinder; import android.view.GestureDetector; import android.view.GestureDetector.SimpleOnGestureListener; import android.view.Gravity; import android.view.LayoutInflater; import android.view.MotionEvent; import android.view.View; import android.view.View.OnTouchListener; import android.view.WindowManager; import android.view.WindowManager.LayoutParams; import android.widget.ImageButton; import android.widget.ImageView; import android.widget.LinearLayout; import android.widget.Toast; /** * @author:Jack Tony * * 重要:注意要申請權限!!!! * <!-- 懸浮窗的權限 --> <uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" /> * * @tips :思路: * 1.得到一個windowManager類 * 2.經過wmParams設置好windows的各類參數 * 3.得到一個視圖的容器,找到懸浮窗視圖的父控件,好比linearLayout * 4.將父控件添加到WindowManager中去 * 5.經過這個父控件找到要顯示的懸浮窗圖標,並進行拖動或點擊事件的設置 * @date :2014-9-25 */ public class FloatingService extends Service{ /** * 定義浮動窗口布局 */ LinearLayout mlayout; /** * 懸浮窗控件 */ ImageView mfloatingIv; /** * 懸浮窗的佈局 */ WindowManager.LayoutParams wmParams; LayoutInflater inflater; /** * 建立浮動窗口設置佈局參數的對象 */ WindowManager mWindowManager; //觸摸監聽器 GestureDetector mGestureDetector; @Override public IBinder onBind(Intent intent) { return null; } @Override public void onCreate() { super.onCreate(); initWindow();//設置窗口的參數 } @Override public int onStartCommand(Intent intent, int flags, int startId) { initFloating();//設置懸浮窗圖標 return super.onStartCommand(intent, flags, startId); } @Override public void onDestroy() { super.onDestroy(); if (mlayout != null) { // 移除懸浮窗口 mWindowManager.removeView(mlayout); } } /////////////////////////////////////////////////////////////////////// /** * 初始化windowManager */ private void initWindow() { mWindowManager = (WindowManager) getApplication().getSystemService(Context.WINDOW_SERVICE); wmParams = getParams(wmParams);//設置好懸浮窗的參數 // 懸浮窗默認顯示以左上角爲起始座標 wmParams.gravity = Gravity.LEFT| Gravity.TOP; //懸浮窗的開始位置,由於設置的是從左上角開始,因此屏幕左上角是x=0;y=0 wmParams.x = 50; wmParams.y = 50; //獲得容器,經過這個inflater來得到懸浮窗控件 inflater = LayoutInflater.from(getApplication()); // 獲取浮動窗口視圖所在佈局 mlayout = (LinearLayout) inflater.inflate(R.layout.floating_layout, null); // 添加懸浮窗的視圖 mWindowManager.addView(mlayout, wmParams); } /** 對windowManager進行設置 * @param wmParams * @return */ public WindowManager.LayoutParams getParams(WindowManager.LayoutParams wmParams){ wmParams = new WindowManager.LayoutParams(); //設置window type 下面變量2002是在屏幕區域顯示,2003則能夠顯示在狀態欄之上 //wmParams.type = LayoutParams.TYPE_PHONE; //wmParams.type = LayoutParams.TYPE_SYSTEM_ALERT; wmParams.type = LayoutParams.TYPE_SYSTEM_ERROR; //設置圖片格式,效果爲背景透明 wmParams.format = PixelFormat.RGBA_8888; //設置浮動窗口不可聚焦(實現操做除浮動窗口外的其餘可見窗口的操做) //wmParams.flags = LayoutParams.FLAG_NOT_FOCUSABLE; //設置能夠顯示在狀態欄上 wmParams.flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE| WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL| WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN| WindowManager.LayoutParams.FLAG_LAYOUT_INSET_DECOR| WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH; //設置懸浮窗口長寬數據 wmParams.width = WindowManager.LayoutParams.WRAP_CONTENT; wmParams.height = WindowManager.LayoutParams.WRAP_CONTENT; return wmParams; } /** * 找到懸浮窗的圖標,而且設置事件 * 設置懸浮窗的點擊、滑動事件 */ private void initFloating() { mfloatingIv = (ImageButton) mlayout.findViewById(R.id.floating_imageView); mfloatingIv.getBackground().setAlpha(150); mGestureDetector = new GestureDetector(this, new MyOnGestureListener()); //設置監聽器 mfloatingIv.setOnTouchListener(new FloatingListener()); } //開始觸控的座標,移動時的座標(相對於屏幕左上角的座標) private int mTouchStartX,mTouchStartY,mTouchCurrentX,mTouchCurrentY; //開始時的座標和結束時的座標(相對於自身控件的座標) private int mStartX,mStartY,mStopX,mStopY; private boolean isMove;//判斷懸浮窗是否移動 /** * @author:金凱 * @tips :本身寫的懸浮窗監聽器 * @date :2014-3-28 */ private class FloatingListener implements OnTouchListener{ @Override public boolean onTouch(View arg0, MotionEvent event) { int action = event.getAction(); switch(action){ case MotionEvent.ACTION_DOWN: isMove = false; mTouchStartX = (int)event.getRawX(); mTouchStartY = (int)event.getRawY(); mStartX = (int)event.getX(); mStartY = (int)event.getY(); break; case MotionEvent.ACTION_MOVE: mTouchCurrentX = (int) event.getRawX(); mTouchCurrentY = (int) event.getRawY(); wmParams.x += mTouchCurrentX - mTouchStartX; wmParams.y += mTouchCurrentY - mTouchStartY; mWindowManager.updateViewLayout(mlayout, wmParams); mTouchStartX = mTouchCurrentX; mTouchStartY = mTouchCurrentY; break; case MotionEvent.ACTION_UP: mStopX = (int)event.getX(); mStopY = (int)event.getY(); //System.out.println("|X| = "+ Math.abs(mStartX - mStopX)); //System.out.println("|Y| = "+ Math.abs(mStartY - mStopY)); if(Math.abs(mStartX - mStopX) >= 1 || Math.abs(mStartY - mStopY) >= 1){ isMove = true; } break; } return mGestureDetector.onTouchEvent(event); //此處必須返回false,不然OnClickListener獲取不到監聽 } } /** * @author:金凱 * @tips :本身定義的手勢監聽類 * @date :2014-3-29 */ class MyOnGestureListener extends SimpleOnGestureListener { @Override public boolean onSingleTapConfirmed(MotionEvent e) { if (!isMove) { Toast.makeText(getApplicationContext(), "你點擊了懸浮窗", 0).show(); System.out.println("onclick"); } return super.onSingleTapConfirmed(e); } } }
記得加權限和註冊服務:
<!-- 懸浮窗的權限 --> <uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
<service android:name="com.kale.testfloating.FloatingService"/>
2、對話框懸浮窗
由於對話框自己就是WindowManager造成的,參數也已經設置好了,因此通常不必再更改它的參數。
直接貼上所有代碼,你們會發現對話框的實現是相對簡單的。
佈局文件:
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" > <ImageView android:id="@+id/imageView" android:layout_width="100dp" android:layout_height="100dp" android:layout_gravity="center_horizontal" android:src="@drawable/ic_launcher" /> </LinearLayout>
Java代碼:
package com.kale.testfloating; import android.app.Dialog; import android.app.Service; import android.content.Intent; import android.os.IBinder; import android.view.LayoutInflater; import android.view.View; import android.view.View.OnClickListener; import android.view.WindowManager; import android.widget.ImageView; import android.widget.Toast; public class DialogFloatingService extends Service { /** * 定義浮動窗口布局 */ Dialog mDialog; /** * 懸浮窗的佈局 */ WindowManager.LayoutParams wmParams; LayoutInflater inflater; /** * 建立浮動窗口設置佈局參數的對象 */ WindowManager mWindowManager; @Override public IBinder onBind(Intent intent) { // TODO 自動生成的方法存根 return null; } @Override public void onCreate() { // TODO 自動生成的方法存根 super.onCreate(); } @Override public int onStartCommand(Intent intent, int flags, int startId) { // TODO 自動生成的方法存根 initWindow(); return super.onStartCommand(intent, flags, startId); } @Override public void onDestroy() { super.onDestroy(); if(mDialog != null){ mDialog.dismiss(); } } /** * 初始化 */ private void initWindow() { mDialog = new Dialog(DialogFloatingService.this); mDialog.getWindow().setType((WindowManager.LayoutParams.TYPE_SYSTEM_ALERT)); //獲得容器,經過這個inflater來得到懸浮窗控件 inflater = LayoutInflater.from(getApplication()); // 獲取浮動窗口視圖所在佈局 View view = inflater.inflate(R.layout.dialog_layout, null); ImageView iv = (ImageView)view.findViewById(R.id.imageView); iv.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { // TODO 自動生成的方法存根 Toast.makeText(getApplicationContext(), "ImageView onclick", 0).show(); } }); // 添加懸浮窗的視圖 mDialog.setContentView(view); mDialog.setTitle("對話框懸浮窗"); mDialog.setCanceledOnTouchOutside(true); mDialog.show(); } }
3、使用方式
很簡單,就是開啓或者關閉服務~
package com.kale.testfloating; import android.app.Activity; import android.content.Intent; import android.os.Bundle; import android.view.View; public class MainActivity extends Activity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); } public void buttonListener(View v) { Intent intent = new Intent(MainActivity.this,FloatingService.class); switch (v.getId()) { case R.id.open_button: startService(intent); break; case R.id.close_button: stopService(intent); break; case R.id.open_dialog_button: startService(new Intent(MainActivity.this,DialogFloatingService.class)); break; case R.id.close_dialog_button: stopService(new Intent(MainActivity.this,DialogFloatingService.class)); break; default: break; } } }
佈局文件:
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" tools:context="${relativePackage}.${activityClass}" > <Button android:id="@+id/open_button" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignParentTop="true" android:layout_centerHorizontal="true" android:layout_marginTop="137dp" android:onClick="buttonListener" android:text="開啓懸浮窗" /> <Button android:id="@+id/close_button" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignLeft="@+id/open_button" android:layout_centerVertical="true" android:onClick="buttonListener" android:text="關閉懸浮窗" /> <Button android:id="@+id/open_dialog_button" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_below="@+id/close_button" android:layout_centerHorizontal="true" android:layout_marginTop="46dp" android:onClick="buttonListener" android:text="開啓對話框懸浮窗" /> <Button android:id="@+id/close_dialog_button" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_below="@+id/open_dialog_button" android:layout_centerHorizontal="true" android:layout_marginTop="24dp" android:text="關閉對話框懸浮窗" /> </RelativeLayout>
更加牛逼的設計方案:http://weibo.com/p/1001603823661684514597