Android悬浮窗

4,003 阅读3分钟

一、不使用权限的悬浮窗

implementation 'com.github.Doonkey:DkFloatingView:1.0'

项目地址:github.com/Doonkey/DkF…

初始化,我放在了 BaseApplication 中

WindowManager.LayoutParams params = new WindowManager.LayoutParams();
params.width = WindowManager.LayoutParams.MATCH_PARENT;
params.height = WindowManager.LayoutParams.WRAP_CONTENT;
params.flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
params.gravity = Gravity.BOTTOM;
params.y = 100;
//不使用权限的悬浮窗
FloatWindow.with(this)//application上下文
        .setLayoutId(R.layout.popupwindow)//悬浮布局
        //.setFilter(Test1_1Activity.class)//过滤activity
        .setLayoutParam(params)//设置悬浮布局layoutParam
        .build();

显示

FloatWindow.get().show();//显示
FloatWindow.get().hide();//隐藏

二、需要权限的悬浮窗

使用权限的悬浮窗

import android.content.Context;
import android.graphics.PixelFormat;
import android.os.Build;
import android.util.Log;
import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.View;
import android.view.WindowManager;
import android.widget.TextView;

/**
 * 使用权限的悬浮窗
 */
public class WindowUtils {
    private static final String LOG_TAG = "WindowUtils";
    private static View mView = null;
    private static WindowManager mWindowManager = null;
    private static Context mContext = null;
    public static Boolean isShown = false;

    public interface NeedShowTipWindow {
        /**
         * 某些页面不要显示,重写此方法,返回false
         */
        boolean needShow();
    }

    /**
     * 显示弹出框
     *
     * @param context
     * @param pageNeedShow 设置某个页面不显示
     */
    public static void showPopupWindow(final Context context, boolean pageNeedShow) {
        if (!pageNeedShow) {
            return;
        }
        if (isShown) {
//            Log.i(LOG_TAG, "return cause already shown");
            return;
        }
        isShown = true;
        Log.i(LOG_TAG, "showPopupWindow");
        // 获取应用的Context
        mContext = context.getApplicationContext();
        // 获取WindowManager
        mWindowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
        mView = setUpView(context);
        final WindowManager.LayoutParams params = new WindowManager.LayoutParams();

        // 设置window type
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
            //6.0+
            params.type = WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
        } else {
            params.type = WindowManager.LayoutParams.TYPE_PHONE;
        }

        // 设置flag
        int flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
        // | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
        // 如果设置了WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE,弹出的View收不到Back键的事件
        params.flags = flags;
        // 不设置这个弹出框的透明遮罩显示为黑色
        params.format = PixelFormat.TRANSLUCENT;
        // FLAG_NOT_TOUCH_MODAL 不阻塞事件传递到后面的窗口
        // 设置 FLAG_NOT_FOCUSABLE 悬浮窗口较小时,后面的应用图标由不可长按变为可长按
        // 不设置这个flag的话,home页的划屏会有问题
        params.width = WindowManager.LayoutParams.MATCH_PARENT;
        params.height = WindowManager.LayoutParams.WRAP_CONTENT;
        params.gravity = Gravity.BOTTOM;
        params.y = 100;

        mWindowManager.addView(mView, params);
        Log.i(LOG_TAG, "add view");
    }

    /**
     * 隐藏弹出框
     */
    public static void hidePopupWindow() {
        Log.i(LOG_TAG, "hide " + isShown + ", " + mView);
        if (isShown && null != mView) {
            Log.i(LOG_TAG, "hidePopupWindow");
            mWindowManager.removeView(mView);
            isShown = false;
        }
    }

    private static View setUpView(final Context context) {
        Log.i(LOG_TAG, "setUp view");
        View view = LayoutInflater.from(context).inflate(R.layout.popupwindow,
                null);
        TextView positiveBtn = view.findViewById(R.id.tv_circle_tips);
        positiveBtn.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Log.i(LOG_TAG, "ok on click");
                // 隐藏弹窗
                WindowUtils.hidePopupWindow();
            }
        });
        TextView close = view.findViewById(R.id.tv_close);
        close.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Log.i(LOG_TAG, "cancel on click");
                WindowUtils.hidePopupWindow();
            }
        });
        // 点击窗口外部区域可消除
        // 这点的实现主要将悬浮窗设置为全屏大小,外层有个透明背景,中间一部分视为内容区域
        // 所以点击内容区域外部视为点击悬浮窗外部
//        final View popupWindowView = view.findViewById(R.id.popup_window);// 非透明的内容区域
//        view.setOnTouchListener(new View.OnTouchListener() {
//            @Override
//            public boolean onTouch(View v, MotionEvent event) {
//                Log.i(LOG_TAG, "onTouch");
//                int x = (int) event.getX();
//                int y = (int) event.getY();
//                Rect rect = new Rect();
//                popupWindowView.getGlobalVisibleRect(rect);
//                if (!rect.contains(x, y)) {
//                    WindowUtils.hidePopupWindow();
//                }
//                Log.i(LOG_TAG, "onTouch : " + x + ", " + y + ", rect: "
//                        + rect);
//                return false;
//            }
//        });
//        // 点击back键可消除
//        view.setOnKeyListener(new View.OnKeyListener() {
//            @Override
//            public boolean onKey(View v, int keyCode, KeyEvent event) {
//                switch (keyCode) {
//                    case KeyEvent.KEYCODE_BACK:
//                        WindowUtils.hidePopupWindow();
//                        return true;
//                    default:
//                        return false;
//                }
//            }
//        });
        return view;
    }
}

popupwindow.xml

<?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="wrap_content"
    android:background="#FEFCEC"
    android:orientation="horizontal">

    <TextView
        android:id="@+id/tv_circle_tips"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_weight="1"
        android:ellipsize="marquee"
        android:focusable="true"
        android:focusableInTouchMode="true"
        android:marqueeRepeatLimit="marquee_forever"
        android:paddingStart="10dp"
        android:paddingTop="5dp"
        android:paddingEnd="10dp"
        android:paddingBottom="5dp"
        android:singleLine="true"
        android:text="阿斯发生的风景阿斯发生的风景阿斯发生的风景阿斯发生的风景阿斯发生的风景阿斯发生的风景"
        android:textColor="#FF6E0E"
        android:textSize="13sp" />
    <!--android:drawableEnd="@drawable/ic_next"-->
    <!--android:drawablePadding="@dimen/dp_16"-->

    <TextView
        android:id="@+id/tv_close"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:padding="10dp"
        android:text="X" />
</LinearLayout>

main页面判断权限

//请求悬浮窗权限
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
    if (!Settings.canDrawOverlays(this)) {
         //若未授权则请求权限
         getOverlayPermission();
    }
}

//请求悬浮窗权限
@TargetApi(Build.VERSION_CODES.M)
private void getOverlayPermission() {
    Intent intent = new Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION);
    intent.setData(Uri.parse("package:" + getPackageName()));
    startActivityForResult(intent, 0);
}

基类里添加显示方法,所有页面都显示

import android.os.Bundle;
import android.support.annotation.Nullable;
import android.support.v7.app.AppCompatActivity;

import com.example.simpledemo.WindowUtils;
public class BaseActivity extends AppCompatActivity implements WindowUtils.NeedShowTipWindow {//

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

    }

    @Override
    protected void onResume() {
        super.onResume();
        WindowUtils.showPopupWindow(this, needShow());

    }

    @Override
    public boolean needShow() {
        return true;
    }
}

监听APP在前台还是后台,前台显示,后台隐藏

import android.app.Activity;
import android.app.Application;
import android.os.Bundle;

import com.example.simpledemo.WindowUtils;
import com.example.simpledemo.util.Utils;

public class BaseApplication extends Application {

   public int count;

   @Override
   public void onCreate() {
       super.onCreate();

       registerActivityLifecycleCallbacks(new ActivityLifecycleCallbacks() {

           @Override
           public void onActivityCreated(Activity activity, Bundle savedInstanceState) {
           }

           @Override
           public void onActivityStarted(Activity activity) {
               // 执行onStart方法切count仍然为0时,说明程序从后台转到了前台
               if (count == 0) {
                   Utils.LogD("CMUApplication 进入前台");
                   if (!WindowUtils.isShown) {
                       WindowUtils.showPopupWindow(getApplicationContext(), true);
                   }
               }
               count++;
           }

           @Override
           public void onActivityResumed(Activity activity) {

           }

           @Override
           public void onActivityPaused(Activity activity) {
           }

           @Override
           public void onActivityStopped(Activity activity) {
               count--;
               // 当count为0时,程序才有可能在后台运行
               if (count == 0) {
                   Utils.LogD("CMUApplication 进入前台");
                   if (WindowUtils.isShown) {
                       WindowUtils.hidePopupWindow();
                   }
               }
           }

           @Override
           public void onActivitySaveInstanceState(Activity activity, Bundle outState) {

           }

           @Override
           public void onActivityDestroyed(Activity activity) {

           }
       });
   }
}

123213122.png