Android實現(xiàn)桌面懸浮窗、蒙板效果實例代碼
現(xiàn)在很多安全類的軟件,比如360手機助手,百度手機助手等等,都有一個懸浮窗,可以飄浮在桌面上,方便用戶使用一些常用的操作。
今天這篇文章,就是介紹如何實現(xiàn)桌面懸浮窗效果的。
首先,看一下效果圖。
懸浮窗一共分為兩個部分,一個是平常顯示的小窗口,另外一個是點擊小窗口顯示出來的二級懸浮窗口。
首先,先看一下這個項目的目錄結(jié)構(gòu)。
最關鍵的就是紅框內(nèi)的四個類。
首先,F(xiàn)loatWindowService是一個后臺的服務類,主要負責在后臺不斷的刷新桌面上的小懸浮窗口,否則會導致更換界面之后,懸浮窗口也會隨之消失,因此需要不斷的刷新。下面是實現(xiàn)代碼。
package com.qust.floatwindow; import java.util.Timer; import java.util.TimerTask; import android.app.Service; import android.content.Context; import android.content.Intent; import android.os.Handler; import android.os.IBinder; /** * 懸浮窗后臺服務 * * @author zhaokaiqiang * */ public class FloatWindowService extends Service { public static final String LAYOUT_RES_ID = "layoutResId"; public static final String ROOT_LAYOUT_ID = "rootLayoutId"; // 用于在線程中創(chuàng)建/移除/更新懸浮窗 private Handler handler = new Handler(); private Context context; private Timer timer; // 小窗口布局資源id private int layoutResId; // 布局根布局id private int rootLayoutId; @Override public int onStartCommand(Intent intent, int flags, int startId) { context = this; layoutResId = intent.getIntExtra(LAYOUT_RES_ID, 0); rootLayoutId = intent.getIntExtra(ROOT_LAYOUT_ID, 0); if (layoutResId == 0 || rootLayoutId == 0) { throw new IllegalArgumentException( "layoutResId or rootLayoutId is illegal"); } if (timer == null) { timer = new Timer(); // 每500毫秒就執(zhí)行一次刷新任務 timer.scheduleAtFixedRate(new RefreshTask(), 0, 500); } return super.onStartCommand(intent, flags, startId); } @Override public void onDestroy() { super.onDestroy(); // Service被終止的同時也停止定時器繼續(xù)運行 timer.cancel(); timer = null; } private class RefreshTask extends TimerTask { @Override public void run() { // 當前界面沒有懸浮窗顯示,則創(chuàng)建懸浮 if (!FloatWindowManager.getInstance(context).isWindowShowing()) { handler.post(new Runnable() { @Override public void run() { FloatWindowManager.getInstance(context) .createSmallWindow(context, layoutResId, rootLayoutId); } }); } } } @Override public IBinder onBind(Intent intent) { return null; } }
除了后臺服務之外,我們還需要兩個自定義的布局,分別是FloatWindowSmallView和FloatWindowBigView,這兩個自定義的布局,主要負責懸浮窗的前臺顯示,我們分別看一下代碼實現(xiàn)。
首先是FloatWindowSmallView類的實現(xiàn)。
package com.qust.floatwindow; import java.lang.reflect.Field; import android.annotation.SuppressLint; import android.content.Context; import android.graphics.PixelFormat; import android.view.Gravity; import android.view.LayoutInflater; import android.view.MotionEvent; import android.view.View; import android.view.WindowManager; import android.widget.LinearLayout; import android.widget.TextView; import com.qust.demo.ScreenUtils; import com.qust.floatingwindow.R; /** * 小懸浮窗,用于初始顯示 * * @author zhaokaiqiang * */ public class FloatWindowSmallView extends LinearLayout { // 小懸浮窗的寬 public int viewWidth; // 小懸浮窗的高 public int viewHeight; // 系統(tǒng)狀態(tài)欄的高度 private static int statusBarHeight; // 用于更新小懸浮窗的位置 private WindowManager windowManager; // 小懸浮窗的布局參數(shù) public WindowManager.LayoutParams smallWindowParams; // 記錄當前手指位置在屏幕上的橫坐標 private float xInScreen; // 記錄當前手指位置在屏幕上的縱坐標 private float yInScreen; // 記錄手指按下時在屏幕上的橫坐標,用來判斷單擊事件 private float xDownInScreen; // 記錄手指按下時在屏幕上的縱坐標,用來判斷單擊事件 private float yDownInScreen; // 記錄手指按下時在小懸浮窗的View上的橫坐標 private float xInView; // 記錄手指按下時在小懸浮窗的View上的縱坐標 private float yInView; // 單擊接口 private OnClickListener listener; /** * 構(gòu)造函數(shù) * * @param context * 上下文對象 * @param layoutResId * 布局資源id * @param rootLayoutId * 根布局id */ public FloatWindowSmallView(Context context, int layoutResId, int rootLayoutId) { super(context); windowManager = (WindowManager) context .getSystemService(Context.WINDOW_SERVICE); LayoutInflater.from(context).inflate(layoutResId, this); View view = findViewById(rootLayoutId); viewWidth = view.getLayoutParams().width; viewHeight = view.getLayoutParams().height; statusBarHeight = getStatusBarHeight(); TextView percentView = (TextView) findViewById(R.id.percent); percentView.setText("懸浮窗"); smallWindowParams = new WindowManager.LayoutParams(); // 設置顯示類型為phone smallWindowParams.type = WindowManager.LayoutParams.TYPE_PHONE; // 顯示圖片格式 smallWindowParams.format = PixelFormat.RGBA_8888; // 設置交互模式 smallWindowParams.flags = WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE; // 設置對齊方式為左上 smallWindowParams.gravity = Gravity.LEFT | Gravity.TOP; smallWindowParams.width = viewWidth; smallWindowParams.height = viewHeight; smallWindowParams.x = ScreenUtils.getScreenWidth(context); smallWindowParams.y = ScreenUtils.getScreenHeight(context) / 2; } @SuppressLint("ClickableViewAccessibility") @Override public boolean onTouchEvent(MotionEvent event) { switch (event.getAction()) { // 手指按下時記錄必要的數(shù)據(jù),縱坐標的值都減去狀態(tài)欄的高度 case MotionEvent.ACTION_DOWN: // 獲取相對與小懸浮窗的坐標 xInView = event.getX(); yInView = event.getY(); // 按下時的坐標位置,只記錄一次 xDownInScreen = event.getRawX(); yDownInScreen = event.getRawY() - statusBarHeight; break; case MotionEvent.ACTION_MOVE: // 時時的更新當前手指在屏幕上的位置 xInScreen = event.getRawX(); yInScreen = event.getRawY() - statusBarHeight; // 手指移動的時候更新小懸浮窗的位置 updateViewPosition(); break; case MotionEvent.ACTION_UP: // 如果手指離開屏幕時,按下坐標與當前坐標相等,則視為觸發(fā)了單擊事件 if (xDownInScreen == event.getRawX() && yDownInScreen == (event.getRawY() - getStatusBarHeight())) { if (listener != null) { listener.click(); } } break; } return true; } /** * 設置單擊事件的回調(diào)接口 */ public void setOnClickListener(OnClickListener listener) { this.listener = listener; } /** * 更新小懸浮窗在屏幕中的位置 */ private void updateViewPosition() { smallWindowParams.x = (int) (xInScreen - xInView); smallWindowParams.y = (int) (yInScreen - yInView); windowManager.updateViewLayout(this, smallWindowParams); } /** * 獲取狀態(tài)欄的高度 * * @return */ private int getStatusBarHeight() { try { Class<?> c = Class.forName("com.android.internal.R$dimen"); Object o = c.newInstance(); Field field = c.getField("status_bar_height"); int x = (Integer) field.get(o); return getResources().getDimensionPixelSize(x); } catch (Exception e) { e.printStackTrace(); } return 0; } /** * 單擊接口 * * @author zhaokaiqiang * */ public interface OnClickListener { public void click(); } }
在這個類里面,主要的工作是實現(xiàn)懸浮窗口在桌面前端的實現(xiàn),還有就是位置的移動和單擊事件的判斷以及處理。這里使用的是主要是WindowManager類的一些方法和屬性,下一篇會詳細說明,這篇只說實現(xiàn)。
除了小懸浮窗之外,點擊之后彈出的二級懸浮窗也是類似的方式添加到桌面上,下面是二級懸浮窗的代碼。
package com.qust.floatwindow; import android.content.Context; import android.graphics.PixelFormat; import android.view.Gravity; import android.view.LayoutInflater; import android.view.View; import android.view.WindowManager; import android.widget.LinearLayout; import android.widget.TextView; import com.qust.demo.ScreenUtils; import com.qust.floatingwindow.R; public class FloatWindowBigView extends LinearLayout { // 記錄大懸浮窗的寬 public int viewWidth; // 記錄大懸浮窗的高 public int viewHeight; public WindowManager.LayoutParams bigWindowParams; private Context context; public FloatWindowBigView(Context context) { super(context); this.context = 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; bigWindowParams = new WindowManager.LayoutParams(); // 設置顯示的位置,默認的是屏幕中心 bigWindowParams.x = ScreenUtils.getScreenWidth(context) / 2 - viewWidth / 2; bigWindowParams.y = ScreenUtils.getScreenHeight(context) / 2 - viewHeight / 2; bigWindowParams.type = WindowManager.LayoutParams.TYPE_PHONE; bigWindowParams.format = PixelFormat.RGBA_8888; // 設置交互模式 bigWindowParams.flags = WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE; bigWindowParams.gravity = Gravity.LEFT | Gravity.TOP; bigWindowParams.width = viewWidth; bigWindowParams.height = viewHeight; initView(); } private void initView() { TextView tv_back = (TextView) findViewById(R.id.tv_back); tv_back.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { FloatWindowManager.getInstance(context).removeBigWindow(); } }); } }
這些基本的類建立起來之后,剩下的就是最重要的類FloatWindowManager的實現(xiàn)。這個類實現(xiàn)的就是對懸浮窗的操作。
package com.qust.floatwindow; import android.content.Context; import android.content.Intent; import android.view.WindowManager; /** * 懸浮窗管理器 * * @author zhaokaiqiang * */ public class FloatWindowManager { // 小懸浮窗對象 private FloatWindowSmallView smallWindow; // 大懸浮窗對象 private FloatWindowBigView bigWindow; // 用于控制在屏幕上添加或移除懸浮窗 private WindowManager mWindowManager; // FloatWindowManager的單例 private static FloatWindowManager floatWindowManager; // 上下文對象 private Context context; private FloatWindowManager(Context context) { this.context = context; } public static FloatWindowManager getInstance(Context context) { if (floatWindowManager == null) { floatWindowManager = new FloatWindowManager(context); } return floatWindowManager; } /** * 創(chuàng)建小懸浮窗 * * @param context * 必須為應用程序的Context. */ public void createSmallWindow(Context context, int layoutResId, int rootLayoutId) { WindowManager windowManager = getWindowManager(); if (smallWindow == null) { smallWindow = new FloatWindowSmallView(context, layoutResId, rootLayoutId); windowManager.addView(smallWindow, smallWindow.smallWindowParams); } } /** * 將小懸浮窗從屏幕上移除 * * @param context */ public void removeSmallWindow() { if (smallWindow != null) { WindowManager windowManager = getWindowManager(); windowManager.removeView(smallWindow); smallWindow = null; } } public void setOnClickListener(FloatWindowSmallView.OnClickListener listener) { if (smallWindow != null) { smallWindow.setOnClickListener(listener); } } /** * 創(chuàng)建大懸浮窗 * * @param context * 必須為應用程序的Context. */ public void createBigWindow(Context context) { WindowManager windowManager = getWindowManager(); if (bigWindow == null) { bigWindow = new FloatWindowBigView(context); windowManager.addView(bigWindow, bigWindow.bigWindowParams); } } /** * 將大懸浮窗從屏幕上移除 * * @param context */ public void removeBigWindow() { if (bigWindow != null) { WindowManager windowManager = getWindowManager(); windowManager.removeView(bigWindow); bigWindow = null; } } public void removeAll() { context.stopService(new Intent(context, FloatWindowService.class)); removeSmallWindow(); removeBigWindow(); } /** * 是否有懸浮窗顯示(包括小懸浮窗和大懸浮) * * @return 有懸浮窗顯示在桌面上返回true,沒有的話返回false */ public boolean isWindowShowing() { return smallWindow != null || bigWindow != null; } /** * 如果WindowManager還未創(chuàng)建,則創(chuàng)建新的WindowManager返回。否則返回當前已創(chuàng)建的WindowManager * * @param context * @return */ private WindowManager getWindowManager() { if (mWindowManager == null) { mWindowManager = (WindowManager) context .getSystemService(Context.WINDOW_SERVICE); } return mWindowManager; } }
還有個獲取屏幕寬高的幫助類。
package com.qust.demo; import android.content.Context; import android.view.WindowManager; /** * 屏幕幫助類 * * @author zhaokaiqiang * */ public class ScreenUtils { /** * 獲取屏幕寬度 * * @return */ @SuppressWarnings("deprecation") public static int getScreenWidth(Context context) { return ((WindowManager) context .getSystemService(Context.WINDOW_SERVICE)).getDefaultDisplay() .getWidth(); } /** * 獲取屏幕寬度 * * @return */ @SuppressWarnings("deprecation") public static int getScreenHeight(Context context) { return ((WindowManager) context .getSystemService(Context.WINDOW_SERVICE)).getDefaultDisplay() .getHeight(); } }
完成這些,我們就可以直接用了。
package com.qust.demo; import android.app.Activity; import android.content.Context; import android.content.Intent; import android.os.Bundle; import android.view.KeyEvent; import android.view.View; import com.qust.floatingwindow.R; import com.qust.floatwindow.FloatWindowManager; import com.qust.floatwindow.FloatWindowService; import com.qust.floatwindow.FloatWindowSmallView.OnClickListener; /** * 示例 * * @ClassName: com.qust.demo.MainActivity * @Description: * @author zhaokaiqiang * @date 2014-10-23 下午11:30:13 * */ public class MainActivity extends Activity { private FloatWindowManager floatWindowManager; private Context context; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); context = this; floatWindowManager = FloatWindowManager.getInstance(context); } /** * 顯示小窗口 * * @param view */ public void show(View view) { // 需要傳遞小懸浮窗布局,以及根布局的id,啟動后臺服務 Intent intent = new Intent(context, FloatWindowService.class); intent.putExtra(FloatWindowService.LAYOUT_RES_ID, R.layout.float_window_small); intent.putExtra(FloatWindowService.ROOT_LAYOUT_ID, R.id.small_window_layout); startService(intent); } /** * 顯示二級懸浮窗 * * @param view */ public void showBig(View view) { // 設置小懸浮窗的單擊事件 floatWindowManager.setOnClickListener(new OnClickListener() { @Override public void click() { floatWindowManager.createBigWindow(context); } }); } /** * 移除所有的懸浮窗 * * @param view */ public void remove(View view) { floatWindowManager.removeAll(); } @Override public boolean onKeyDown(int keyCode, KeyEvent event) { // 返回鍵移除二級懸浮窗 if (keyCode == KeyEvent.KEYCODE_BACK && event.getAction() == KeyEvent.ACTION_DOWN) { floatWindowManager.removeBigWindow(); return true; } return super.onKeyDown(keyCode, event); } }
項目下載地址:https://github.com/ZhaoKaiQiang/FloatWindow
在上面文章中,我們介紹了如何實現(xiàn)桌面懸浮窗口,在這個效果的實現(xiàn)過程中,最重要的一個類就是WindowManager,今天這篇文章,將對WindowManager的使用進行介紹,并且實現(xiàn)一個使用WindowManager來實現(xiàn)用戶打開APP,顯示首次使用教學蒙板的效果。
WindowManager類實現(xiàn)了ViewManager接口,ViewManager接口允許我們在Activity上添加或者是移除view,因此WindowManager也允許我們在Activity上進行View的添加和移除操作。
我們可以通過下面的方法獲取一個WindowManager對象
Context.getSystemService(Context.WINDOW_SERVICE)
在Activity之中,我們可以直接通過getWindowManager()獲取到一個WindowManager對象。
每一個WindowManager實例都被綁定到一個獨有的Display對象上面,如果我們想獲取不同Display的WindowManager對象,我們可以通過createDisplayContext(Display)獲取到這個Display的Context對象,然后使用上面的方法,也可以獲取到一個WindowManager對象。
我們在使用WindowManager類的時候,通常使用下面的幾個方法:
windowManager.addView(View,WindowManager.LayoutParam); windowManager.removeView(); windowManager.getDefaultDisplay();
windowManager.addView()方法用來向當前的窗口上添加View對象,需要接受兩個參數(shù),View是要添加到窗口的View對象,而WindowManager.LayoutParam則是添加的窗口的參數(shù),在上一篇添加懸浮窗的操作的時候,需要對LayoutParam設置很多參數(shù),下面我們看一下常用的設置
// 設置LayoutParams參數(shù) LayoutParams params = new WindowManager.LayoutParams(); //設置顯示的類型,TYPE_PHONE指的是來電話的時候會被覆蓋,其他時候會在最前端,顯示位置在stateBar下面,其他更多的值請查閱文檔 params.type = WindowManager.LayoutParams.TYPE_PHONE; //設置顯示格式 params.format = PixelFormat.RGBA_8888; //設置對齊方式 params.gravity = Gravity.LEFT | Gravity.TOP; //設置寬高 params.width = ScreenUtils.getScreenWidth(this); params.height = ScreenUtils.getScreenHeight(this); //設置顯示的位置 params.x; params.y;
設置好LayoutParam之后,我們就可以通過windowManager.addView(View,WindowManager.LayoutParam)將View添加到窗口之上,不過,我們需要申明權(quán)限
<uses-permissionAndroid:name="android.permission.SYSTEM_ALERT_WINDOW"/>
添加完成之后,我們就可以在窗口上看到我們添加的View對象了。如果我們想將添加的View移除,我們只需要調(diào)用windowManager.removeView()即可,參數(shù)就是我們前面使用的View對象,使用很簡單。除了這個方法,還有個windowManager.removeViewImmediate(),也可以將View移除,但是文檔中說,這個方法并不是給一般程序調(diào)用的,因此需要小心使用,我們開發(fā)的都屬于一般程序,建議不要使用這個方法。
除了這兩個方法之外,我們最常用的另外一個方法就是windowManager.getDefaultDisplay(),通過這個方法,我們可以獲取到當前界面的Display的一個對象,然后我們就可以獲取到當前屏幕的一些參數(shù),比如說寬高。
下面是我常用的一個工具類。
package com.qust.teachmask; import android.content.Context; import android.view.WindowManager; /** * 屏幕幫助類 * * @author zhaokaiqiang * */ public class ScreenUtils { /** * 獲取屏幕寬度 * * @return */ @SuppressWarnings("deprecation") public static int getScreenWidth(Context context) { return ((WindowManager) context .getSystemService(Context.WINDOW_SERVICE)).getDefaultDisplay() .getWidth(); } /** * 獲取屏幕寬度 * * @return */ @SuppressWarnings("deprecation") public static int getScreenHeight(Context context) { return ((WindowManager) context .getSystemService(Context.WINDOW_SERVICE)).getDefaultDisplay() .getHeight(); } }
知道上面這些之后,我們就可以實現(xiàn)教學模板效果了,首先看效果圖。
下面是代碼實現(xiàn)
package com.qust.teachmask; import android.app.Activity; import android.graphics.PixelFormat; import android.os.Bundle; import android.view.Gravity; import android.view.View; import android.view.View.OnClickListener; import android.view.WindowManager; import android.view.WindowManager.LayoutParams; import android.widget.ImageView; import android.widget.ImageView.ScaleType; public class MainActivity extends Activity { private ImageView img; private WindowManager windowManager; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); windowManager = getWindowManager(); // 動態(tài)初始化圖層 img = new ImageView(this); img.setLayoutParams(new LayoutParams( android.view.ViewGroup.LayoutParams.MATCH_PARENT, android.view.ViewGroup.LayoutParams.MATCH_PARENT)); img.setScaleType(ScaleType.FIT_XY); img.setImageResource(R.drawable.guide); // 設置LayoutParams參數(shù) LayoutParams params = new WindowManager.LayoutParams(); // 設置顯示的類型,TYPE_PHONE指的是來電話的時候會被覆蓋,其他時候會在最前端,顯示位置在stateBar下面,其他更多的值請查閱文檔 params.type = WindowManager.LayoutParams.TYPE_PHONE; // 設置顯示格式 params.format = PixelFormat.RGBA_8888; // 設置對齊方式 params.gravity = Gravity.LEFT | Gravity.TOP; // 設置寬高 params.width = ScreenUtils.getScreenWidth(this); params.height = ScreenUtils.getScreenHeight(this); // 添加到當前的窗口上 windowManager.addView(img, params); // 點擊圖層之后,將圖層移除 img.setOnClickListener(new OnClickListener() { @Override public void onClick(View arg0) { windowManager.removeView(img); } }); } }
本文非原創(chuàng),轉(zhuǎn)載于:http://blog.csdn.net/zhaokaiqiang1992
以上所述是小編給大家介紹的Android實現(xiàn)桌面懸浮窗、蒙板效果實例代碼,希望對大家有所幫助!
相關文章
Android如何在root設備上開啟ViewServer詳解
這篇文章主要給大家介紹了關于Android中如何在root設備上開啟ViewServer的相關資料,文中通過示例代碼介紹的非常詳細,對各位Android開發(fā)者具有一定的參考學習價值,需要的朋友們下面來一起看看吧。2017-12-12Windows下快速搭建安卓開發(fā)環(huán)境Android studio
這篇文章主要介紹了Windows下快速搭建安卓開發(fā)環(huán)境Android studio的相關資料,感興趣的小伙伴們可以參考一下2016-07-07Android使用ShareSDK實現(xiàn)應用分享的功能
這篇文章主要為大家詳細介紹了Android使用ShareSDK實現(xiàn)應用分享的功能,具有一定的參考價值,感興趣的小伙伴們可以參考一下2018-05-05Android開發(fā)微信APP支付功能的要點小結(jié)
微信支付現(xiàn)在在日常生活中隨處可見,而關于Android開發(fā)微信支付的文章網(wǎng)上也很多,所以這篇文章主要介紹的是在Android開發(fā)微信APP支付功能的要注意的要點,有需要的可以參考借鑒。2016-08-08Android開發(fā)之微信底部菜單欄實現(xiàn)的幾種方法匯總
這篇文章主要介紹了Android開發(fā)之微信底部菜單欄實現(xiàn)的幾種方法,下面小編把每種方法通過實例逐一給大家介紹,需要的朋友可以參考下2016-09-09Android開發(fā)vsts?agent支持自定義task過程詳解
這篇文章主要介紹了Android開發(fā)vsts?agent支持自定義task過程詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步早日升職加薪2022-04-04Android性能優(yōu)化之捕獲java crash示例解析
這篇文章主要介紹了Android性能優(yōu)化之捕獲java crash示例解析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪2022-09-09