寫在前面:本文僅個人開發(fā)時遇到的問題及個人解決辦法的記錄。
國內(nèi)各個手機廠商對ROM魔改的比較嚴(yán)重抡柿,還沒有做兼容性測試舔琅,所以碰到沙雕的機子的時候,請再去尋找適配方法
最近項目開發(fā)中洲劣,需要實現(xiàn)一個懸浮窗备蚓,說一下實現(xiàn)方式,做一下記錄囱稽。
????????首先郊尝,簡單的藐視就是:實現(xiàn)懸浮窗是用的WindowManager。利用context.getSystemService(Context.WINDOW_SERVICE)獲取到WindowManger對象,調(diào)用里面的windowManager.addView(floatView, layoutParams)方法。floatView就是要展示的懸浮窗的View layoutParams是一些參數(shù)設(shè)置婉徘。
? ? ? ? 下面介紹一下實現(xiàn)步驟(懶得看的可以下滑到最下面看代碼):
? ? ? ? 申請權(quán)限弹灭,這是必須的一步习绢。
? ? ? ? ? ?在你的清單文件中添加如下權(quán)限代碼
? ? ? ? ? ?<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
然后在代碼里面使用下面的方法判斷是否有懸浮窗權(quán)限:
????????????Settings.canDrawOverlays(context)
如果沒有權(quán)限本姥,跳轉(zhuǎn)到權(quán)限開啟頁面稠曼,打開懸浮窗權(quán)限芥丧。確切的說是跳轉(zhuǎn)到開啟?允許顯示在其他應(yīng)用上層??的權(quán)限
startActivityForResult(new Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION, Uri.parse("package:" + getPackageName())), 10086);
一切準(zhǔn)備工作完成后開始我們的正式任務(wù)暗笕蕖C朴!L哦АA肝搿!3跽D尽!I萑搿筝闹!
第一步,獲取到WindowManager對象腥光;
(WindowManager) context.getSystemService(Context.WINDOW_SERVICE)
第二步关顷,創(chuàng)建一個WindowManager.LayoutParams對象,用于設(shè)置一些懸浮view的參數(shù)
// 設(shè)置LayoutParam
layoutParams =new WindowManager.LayoutParams();
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
layoutParams.type = WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
}else {
layoutParams.type = WindowManager.LayoutParams.TYPE_PHONE;
}
//懸浮窗彈出的位置
layoutParams.gravity = Gravity.LEFT|Gravity.CENTER;
//注意:這一個flags的設(shè)置武福,之前搜索很多實現(xiàn)都沒有設(shè)置這個议双,出現(xiàn)的情況就是在懸浮的view出現(xiàn)后? 點擊窗口其它地方?jīng)]有反應(yīng),是因為不設(shè)置這個參數(shù)捉片,懸浮窗彈出來后就占據(jù)整個窗口的焦點平痰。
layoutParams.flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
layoutParams.format = PixelFormat.RGBA_8888;
layoutParams.width = WindowManager.LayoutParams.WRAP_CONTENT;
layoutParams.height = WindowManager.LayoutParams.WRAP_CONTENT;
layoutParams.x =0;
layoutParams.y =0;
第三步 獲取到需要懸浮顯示的view對象
LayoutInflater layoutInflater = LayoutInflater.from(context);
View floatView = layoutInflater.inflate(R.layout.floating_view, null);
第四步,將懸浮view和layoutParams調(diào)用windowmanager的方法addView顯示出來
// 將懸浮窗控件添加到WindowManager
windowManager.addView(floatView, layoutParams);
如果你需要對懸浮窗里不同view設(shè)置一些點擊事件
我們在上面第三步里面獲取到了懸浮窗的View對象伍纫,可以使用view.findviewById方法根據(jù)id拿到各個view宗雇,針對不同的view設(shè)置不同的事件。
對懸浮窗添加拖動事件
同樣上面第三步我們獲取到的view對象莹规,設(shè)置觸摸事件
//設(shè)置觸摸事件
floatView.setOnTouchListener(new FloatingOnTouchListener());
//因為我的懸浮窗需求比較簡單赔蒲,所以沒有那么多復(fù)雜的操作。只是拖動后良漱,讓懸浮view靠邊停著舞虱。
private class FloatingOnTouchListenerimplements View.OnTouchListener {
private int x;
? ? private int y;
? ? //標(biāo)記是否執(zhí)行move事件 如果執(zhí)行了move事件? 在up事件的時候判斷懸浮窗的位置讓懸浮窗處于屏幕左邊或者右邊
? ? private boolean isScroll;
? ? //標(biāo)記懸浮窗口是否移動了? 防止設(shè)置點擊事件的時候 窗口移動松手后觸發(fā)點擊事件
? ? private boolean isMoved;
? ? //事件開始時和結(jié)束的時候? X和Y坐標(biāo)位置
? ? private int startX;
? ? private int startY;
? ? @Override
? ? public boolean onTouch(View view, MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
x = (int) event.getRawX();
? ? ? ? ? ? ? ? y = (int) event.getRawY();
? ? ? ? ? ? ? ? isMoved =false;
? ? ? ? ? ? ? ? isScroll =false;
? ? ? ? ? ? ? ? startX = (int) event.getRawX();
? ? ? ? ? ? ? ? startY = (int) event.getRawY();
break;
? ? ? ? ? ? case MotionEvent.ACTION_MOVE:
int nowX = (int) event.getRawX();
? ? ? ? ? ? ? ? int nowY = (int) event.getRawY();
? ? ? ? ? ? ? ? int movedX = nowX -x;
? ? ? ? ? ? ? ? int movedY = nowY -y;
? ? ? ? ? ? ? ? x = nowX;
? ? ? ? ? ? ? ? y = nowY;
? ? ? ? ? ? ? ? layoutParams.x =layoutParams.x + movedX;
? ? ? ? ? ? ? ? layoutParams.y =layoutParams.y + movedY;
? ? ? ? ? ? ? ? // 更新懸浮窗控件布局
? ? ? ? ? ? ? ? windowManager.updateViewLayout(view, layoutParams);
? ? ? ? ? ? ? ? isScroll =true;
break;
? ? ? ? ? ? case MotionEvent.ACTION_UP:
int stopX = (int) event.getRawX();
? ? ? ? ? ? ? ? int stopY = (int) event.getRawY();
? ? ? ? ? ? ? ? if (Math.abs(startX - stopX) >=1 || Math.abs(startY - stopY) >=1) {
isMoved =true;
? ? ? ? ? ? ? ? }
if (isScroll){
autoView(view);
? ? ? ? ? ? ? ? }
break;
? ? ? ? }
return isMoved;
? ? }
//懸浮窗view自動停靠在屏幕左邊或者右邊
? ? private void autoView(View view) {
// 得到view在屏幕中的位置
? ? ? ? int[] location =new int[2];
? ? ? ? view.getLocationOnScreen(location);
? ? ? ? if (location[0]
layoutParams.x =0;
? ? ? ? }else {
layoutParams.x = DensityUtils.getScreenSize(false).x - view.getWidth();
? ? ? ? }
windowManager.updateViewLayout(view, layoutParams);
? ? }
}
最后债热,放出來一個簡單處理的類砾嫉,有需求的可以根據(jù)需求自己修改
代碼:
public class FloatingWindowUtils {
private Contextcontext;
? ? private int screenWidth;
? ? private WindowManager.LayoutParamslayoutParams;
? ? private WindowManagerwindowManager;
? ? private ViewfloatView;
? ? private FloatingWindowUtils() {
}
private static class InstanceHolder {
@SuppressLint("StaticFieldLeak")
private static final FloatingWindowUtilssInstance =new FloatingWindowUtils();
? ? ? ? private InstanceHolder() {
}
}
public static FloatingWindowUtilsgetInstance() {
return FloatingWindowUtils.InstanceHolder.sInstance;
? ? }
public void init(Context context) {
this.context = context;
? ? ? ? if (windowManager !=null)return;
? ? ? ? // 獲取WindowManager服務(wù)
? ? ? ? windowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
? ? ? ? //獲取屏寬
? ? ? ? screenWidth = DensityUtils.getScreenSize(false).x;
? ? }
/**
? ? * 展示懸浮窗
? ? * @param layoutId 懸浮窗布局文件id
*/
? ? @SuppressLint("RtlHardcoded")
public void showFloatingWindow(@LayoutRes int layoutId){
// 新建懸浮窗控件
? ? ? ? LayoutInflater layoutInflater = LayoutInflater.from(context);
//? ? ? ? View floatView = layoutInflater.inflate(R.layout.floating_view, null);
? ? ? ? View floatView = layoutInflater.inflate(layoutId, null);
? ? ? ? if (floatView ==null){
throw new NullPointerException("懸浮窗view為null 檢查布局文件是否可用");
? ? ? ? }
showFloatingWindow(floatView);
? ? }
/**
? ? * 展示懸浮窗
? ? * @param floatView 懸浮窗view
*/
? ? @SuppressLint("RtlHardcoded")
public void showFloatingWindow(@NonNull View floatView){
if (this.floatView !=null)return;//有懸浮窗在顯示 不再顯示新的懸浮窗
? ? ? ? // 新建懸浮窗控件
? ? ? ? if (floatView ==null){
throw new NullPointerException("懸浮窗view為null 確認(rèn)view不為null");
? ? ? ? }
this.floatView = floatView;
? ? ? ? //設(shè)置觸摸事件
? ? ? ? floatView.setOnTouchListener(new FloatingOnTouchListener());
? ? ? ? //懸浮窗設(shè)置點擊事件
? ? ? ? floatView.setOnClickListener(new View.OnClickListener() {
@Override
? ? ? ? ? ? public void onClick(View v) {
Toast.makeText(context, "點擊了懸浮窗", Toast.LENGTH_SHORT).show();
? ? ? ? ? ? }
});
? ? ? ? // 設(shè)置LayoutParam
? ? ? ? layoutParams =new WindowManager.LayoutParams();
? ? ? ? if (Build.VERSION.SDK_INT? >= Build.VERSION_CODES.O) {
layoutParams.type = WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
? ? ? ? }
layoutParams.gravity = Gravity.LEFT|Gravity.CENTER;
? ? ? ? //設(shè)置flags 不然懸浮窗出來后整個屏幕都無法獲取焦點,
? ? ? ? layoutParams.flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
? ? ? ? layoutParams.format = PixelFormat.RGBA_8888;
? ? ? ? layoutParams.width = WindowManager.LayoutParams.WRAP_CONTENT;
? ? ? ? layoutParams.height = WindowManager.LayoutParams.WRAP_CONTENT;
? ? ? ? layoutParams.x =0;
? ? ? ? layoutParams.y =0;
? ? ? ? // 將懸浮窗控件添加到WindowManager
? ? ? ? windowManager.addView(floatView, layoutParams);
? ? }
/**
? ? * 隱藏懸浮窗
? ? */
? ? public void hideFloatWindow(){
if (floatView !=null){
windowManager.removeViewImmediate(floatView);
? ? ? ? ? ? floatView =null;
? ? ? ? }
}
public void unInit() {
hideFloatWindow();
? ? ? ? this.context =null;
? ? ? ? // 獲取WindowManager服務(wù)
? ? ? ? windowManager =null;
? ? }
private class FloatingOnTouchListenerimplements View.OnTouchListener {
private int x;
? ? ? ? private int y;
? ? ? ? //標(biāo)記是否執(zhí)行move事件 如果執(zhí)行了move事件? 在up事件的時候判斷懸浮窗的位置讓懸浮窗處于屏幕左邊或者右邊
? ? ? ? private boolean isScroll;
? ? ? ? //標(biāo)記懸浮窗口是否移動了? 防止設(shè)置點擊事件的時候 窗口移動松手后觸發(fā)點擊事件
? ? ? ? private boolean isMoved;
? ? ? ? //事件開始時和結(jié)束的時候? X和Y坐標(biāo)位置
? ? ? ? private int startX;
? ? ? ? private int startY;
? ? ? ? @Override
? ? ? ? public boolean onTouch(View view, MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
x = (int) event.getRawX();
? ? ? ? ? ? ? ? ? ? y = (int) event.getRawY();
? ? ? ? ? ? ? ? ? ? isMoved =false;
? ? ? ? ? ? ? ? ? ? isScroll =false;
? ? ? ? ? ? ? ? ? ? startX = (int) event.getRawX();
? ? ? ? ? ? ? ? ? ? startY = (int) event.getRawY();
break;
? ? ? ? ? ? ? ? case MotionEvent.ACTION_MOVE:
int nowX = (int) event.getRawX();
? ? ? ? ? ? ? ? ? ? int nowY = (int) event.getRawY();
? ? ? ? ? ? ? ? ? ? int movedX = nowX -x;
? ? ? ? ? ? ? ? ? ? int movedY = nowY -y;
? ? ? ? ? ? ? ? ? ? x = nowX;
? ? ? ? ? ? ? ? ? ? y = nowY;
? ? ? ? ? ? ? ? ? ? layoutParams.x =layoutParams.x + movedX;
? ? ? ? ? ? ? ? ? ? layoutParams.y =layoutParams.y + movedY;
? ? ? ? ? ? ? ? ? ? // 更新懸浮窗控件布局
? ? ? ? ? ? ? ? ? ? windowManager.updateViewLayout(view, layoutParams);
? ? ? ? ? ? ? ? ? ? isScroll =true;
break;
? ? ? ? ? ? ? ? case MotionEvent.ACTION_UP:
int stopX = (int) event.getRawX();
? ? ? ? ? ? ? ? ? ? int stopY = (int) event.getRawY();
? ? ? ? ? ? ? ? ? ? if (Math.abs(startX - stopX) >=1 || Math.abs(startY - stopY) >=1) {
isMoved =true;
? ? ? ? ? ? ? ? ? ? }
if (isScroll){
autoView(view);
? ? ? ? ? ? ? ? ? ? }
break;
? ? ? ? ? ? }
return isMoved;
? ? ? ? }
//懸浮窗view自動椭侠椋靠在屏幕左邊或者右邊
? ? ? ? private void autoView(View view) {
// 得到view在屏幕中的位置
? ? ? ? ? ? int[] location =new int[2];
? ? ? ? ? ? view.getLocationOnScreen(location);
? ? ? ? ? ? if (location[0]
layoutParams.x =0;
? ? ? ? ? ? }else {
layoutParams.x = DensityUtils.getScreenSize(false).x - view.getWidth();
? ? ? ? ? ? }
windowManager.updateViewLayout(view, layoutParams);
? ? ? ? }
}
public ViewgetFloatView(){
return floatView;
? ? }
}
最后給大家放一個介紹比較全面的帖子焕刮。
http://www.reibang.com/p/3246f7289704