2018-04-21 11:17:55 u014133383 阅读数 5729

android 全局dialog,并且兼容android8.0 系统。


<!-- 浮动通知权限 -->
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />

初始化dialog

public static void showLogoutDialog(final Context context) {
    AlertDialog.Builder builder = new AlertDialog.Builder(context);
    /*true 代表点击空白可消失   false代表点击空白哦不可消失 */
    builder.setCancelable(false);
    View view = View.inflate(context, R.layout.dialog_logout, null);
  
    TextView tvOk =   view.findViewById(R.id.id_logout_tv_ok);
    TextView tvCancel =   view.findViewById(R.id.id_logout_tv_cancel);


    builder.setView(view);
    final AlertDialog dialog = builder.create();
    
    //设置弹出全局对话框,但是这句话并不能解决在android的其他手机上能弹出来(例如用户华为p10 就无法弹框)
    // dialog.getWindow().setType(WindowManager.LayoutParams.TYPE_TOAST);
    
    //只有这样才能弹框
    if (Build.VERSION.SDK_INT>=26) {//8.0新特性
        dialog.getWindow().setType(WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY);
    }else{
        dialog.getWindow().setType(WindowManager.LayoutParams.TYPE_SYSTEM_ALERT);
    }

    
    tvOk.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            dialog.dismiss();
        }
    });

    tvCancel.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            dialog.dismiss();
        }
    });

    dialog.show();
}

调用dialog

showLogoutDialog(mContext);


  


这样就能简单的把一个dialog 全局显示出了,然而如果你的dialog 界面是有很多逻辑的,例如新年卡片等推送的界面


这种情况是在下面这句设置 style,大哭大哭大哭

AlertDialog.Builder builder = new AlertDialog.Builder(context,R.style.Dialog);

这是因为AlertDialog 是很难设置 style,看到这里是不是很扎心了,老铁抓狂

我也是很扎心,我也是查了很多资料,也下载很多次其他人的demo,说实话效果不咋地却花了不少的下载资源,后来就放弃了,过了一天想了一些,结合以前做过的demo,于是乎放弃了 AlertDialog.

花了不少时间才做成下面的效果,不容易啊哭


上面的效果是是 启动service + 定时器 + 广播,显示的效果 





下载连接地址:

https://download.csdn.net/download/u014133383/10364495







2017-12-12 15:10:42 ruyi366 阅读数 565

浮动页面的需求是:通过直接的函数调用,在当前页面之上显示一个类似的广告页面。


1、WindowManager

搜索网上大多都是基于WindowManager:WindowManager是全局的,整个系统只有一个WindowManager。它是显示View的最底层。WindowManager主要用来管理窗口的一些状态、属性、view(增加、删除、更新)、窗口顺序、消息收集和处理等。通过Context.getSystemService(Context.WINDOW_SERVICE)的方式可以获得WindowManager的实例.悬浮在手机桌面,不受Activity界面的影响,说明该悬浮窗口是不隶属于Activity界面的,也就是说,它是隶属于启动它的应用程序所在进程。



首先得在应用程序的AndroidManifest.xml文件中添加权限

<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />

如果targetSdkVersion>=23,需要引导用户开启权限:

private static final int REQUEST_CODE = 1;
private  void requestAlertWindowPermission() {
    Intent intent = new Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION);
    intent.setData(Uri.parse("package:" + getPackageName()));
    startActivityForResult(intent, REQUEST_CODE);
}

@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
    super.onActivityResult(requestCode, resultCode, data);
    if (requestCode == REQUEST_CODE) {
        if (Settings.canDrawOverlays(this)) {
            Log.i(LOGTAG, "onActivityResult success");
        }
    }
}        

2、android.R.id.content


Activity的setContentView(...)就是将其布局添加到id为content的Framelayout中。因此通过操作android.R.id.content,可以添加浮动页面到当前页面。主要代码如下:


public void showBannerAdView(Activity topActivity, AdLayoutParams params){
        if(topActivity == null){
            return;
        }

        removeFloatContainer(topActivity);
        if(params == null){
            params = new AdLayoutParams();
        }
        if(params.getHeight() <= 0){
            params.setHeight(ViewGroup.LayoutParams.WRAP_CONTENT);
        }
        if(params.getWidth() <= 0){
            params.setWidth(ViewGroup.LayoutParams.MATCH_PARENT);
        }

        ViewGroup topActivityBox =  topActivity.findViewById(android.R.id.content);
        BannerAdView bannerAdView = new BannerAdView(topActivity);
        bannerAdView.setId(R.id.float_banner_ad_view);

        FrameLayout.LayoutParams floatBoxParams = new FrameLayout.LayoutParams(params.getWidth(), params.getHeight());
        floatBoxParams.gravity = params.getGravity();
        topActivityBox.addView(bannerAdView, floatBoxParams);
    }


    private void removeFloatContainer(Activity activity) {
        if (activity != null) {
            View floatBox = activity.findViewById(R.id.float_banner_ad_view);
            removeFromParent(floatBox);
        }
    }

    private void removeFromParent(View view) {
        if (view != null) {
            ViewParent parent = view.getParent();
            if (parent != null) {
                ((ViewGroup) parent).removeView(view);
            }
        }
    }




参考文档:

1、https://www.cnblogs.com/mythou/p/3244208.html

2、http://blog.csdn.net/vfush/article/details/51203092

3、http://blog.csdn.net/colinandroid/article/details/77748695

2015-10-25 10:22:40 birthmarkqiqi 阅读数 477

//创建创建全局变量类

复制代码
 1 public class MyApplication extends Application {
 2     
 3     /**
 4      * 创建全局变量
 5      * 全局变量一般都比较倾向于创建一个单独的数据类文件,并使用static静态变量
 6      * 
 7      * 这里使用了在Application中添加数据的方法实现全局变量
 8      * 注意在AndroidManifest.xml中的Application节点添加android:name=".MyApplication"属性
 9      * 
10      */
11     private WindowManager.LayoutParams wmParams=new WindowManager.LayoutParams();
12 
13     public WindowManager.LayoutParams getMywmParams(){
14         return wmParams;
15     }
复制代码

 

//Activity类 

复制代码
 1 public class MyFloatViewActivity extends Activity {
 2    
 3 
 4     /** Called when the activity is first created. */
 5     
 6     private WindowManager wm=null;
 7     private WindowManager.LayoutParams wmParams=null;
 8     
 9     private MyFloatView myFV=null;
10 
11     
12     @Override
13     public void onCreate(Bundle savedInstanceState) {
14         super.onCreate(savedInstanceState);
15         setContentView(R.layout.main);
16     
18     }
19     
22     private void createView(){
23         myFV=new MyFloatView(getApplicationContext());
24         myFV.setImageResource(R.drawable.icon);
25         //获取WindowManager
26         wm=(WindowManager)getApplicationContext().getSystemService("window");
27         //设置LayoutParams(全局变量)相关参数
28         wmParams = ((MyApplication)getApplication()).getMywmParams();
29 
30          /**
31          *以下都是WindowManager.LayoutParams的相关属性
32          * 具体用途可参考SDK文档
33          */
34         wmParams.type=LayoutParams.TYPE_PHONE;   //设置window type
35         wmParams.format=PixelFormat.RGBA_8888;   //设置图片格式,效果为背景透明
36 
37         //设置Window flag
38         wmParams.flags=LayoutParams.FLAG_NOT_TOUCH_MODAL
39                               | LayoutParams.FLAG_NOT_FOCUSABLE;
40         /*
41          * 下面的flags属性的效果形同“锁定”。
42          * 悬浮窗不可触摸,不接受任何事件,同时不影响后面的事件响应。
43          wmParams.flags=LayoutParams.FLAG_NOT_TOUCH_MODAL 
44                                | LayoutParams.FLAG_NOT_FOCUSABLE
45                                | LayoutParams.FLAG_NOT_TOUCHABLE;
46         */
47         
48         
49         wmParams.gravity=Gravity.LEFT|Gravity.TOP;   //调整悬浮窗口至左上角
50         //以屏幕左上角为原点,设置x、y初始值
51         wmParams.x=0;
52         wmParams.y=0;
53         
54         //设置悬浮窗口长宽数据
55         wmParams.width=40;
56         wmParams.height=40;
57     
58         //显示myFloatView图像
59         wm.addView(myFV, wmParams);
60         
61     }
62     
63     @Override
64     public void onDestroy(){
65         super.onDestroy();
66         //在程序退出(Activity销毁)时销毁悬浮窗口
67         wm.removeView(myFV);
68     }
69     
70     @Override
71     protected void onRestart() {
72         // TODO Auto-generated method stub
73         wm.removeView(myFV);
74         super.onRestart();
75     }
76 
79     @Override
80     protected void onStop() {
81         // TODO Auto-generated method stub
82         //创建悬浮窗口
83         createView();
84         super.onStop();
85     }  
复制代码

 

//浮动窗口内容类

 

复制代码
 1 public class MyFloatView extends ImageView {
 2     private float mTouchStartX;
 3     private float mTouchStartY;
 4     private float x;
 5     private float y;
 6     
 8     private WindowManager wm=(WindowManager)getContext().getApplicationContext().getSystemService("window");
 9     
10     //此wmParams为获取的全局变量,用以保存悬浮窗口的属性
11     private WindowManager.LayoutParams wmParams = ((MyApplication)getContext().getApplicationContext()).getMywmParams();
12 
13     public MyFloatView(Context context) {
14         super(context);        
15         // TODO Auto-generated constructor stub
16     }
17     
18      @Override
19      public boolean onTouchEvent(MotionEvent event) {
20          
21          //getRawX()获取相对屏幕的坐标,即以屏幕左上角为原点         
22          x = event.getRawX();   
23          y = event.getRawY()-25;   //25是系统状态栏的高度
24          Log.i("currP", "currX"+x+"====currY"+y);
25          switch (event.getAction()) {
26             case MotionEvent.ACTION_DOWN:
27                 //getX()获取相对View的坐标,即以此View左上角为原点
28                 mTouchStartX =  event.getX(); 
29                 mTouchStartY =  event.getY();
30                 
31                 Log.i("startP", "startX"+mTouchStartX+"====startY"+mTouchStartY);
32                 
33                 break;
34             case MotionEvent.ACTION_MOVE:                
35                 updateViewPosition();
36                 break;
37 
38             case MotionEvent.ACTION_UP:
39                 updateViewPosition();
40                 mTouchStartX=mTouchStartY=0;
41                 break;
42             }
43             return true;
44         }
45      
46      private void updateViewPosition(){
47         //更新浮动窗口位置参数,x是鼠标在屏幕的位置,mTouchStartX是鼠标在图片的位置
48         wmParams.x=(int)( x-mTouchStartX);
49         System.out.println(mTouchStartX);
50         wmParams.y=(int) (y-mTouchStartY);
51         wm.updateViewLayout(this, wmParams);
52         
53      }
54 
55 }
复制代码

 

在androidManifest中设置:

<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" /> 

2017-07-12 12:58:14 u010429311 阅读数 753

1. 问题描述

通过WindowManager往窗口里添加浮动按钮,在Android7.0时该按钮可以全局保留,直至进程被杀掉。而Android7.0以下(以Android4.4为例)浮动按钮随Activity的onStop()方法被覆盖。
以下为浮动按钮的实现代码:

WindowManager mWm = (WindowManager)mContext.getSystemService(Context.WINDOW_SERVICE);
WindowManager.LayoutParams params = new WindowManager.LayoutParams();
params.type= LayoutParams.TYPE_PHONE;     // 系统提示类型,重要
params.format=1;
params.flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE; 
params.flags = params.flags | WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH;
params.flags = params.flags | WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS; 
params.width = dipToPixel(60);
params.height = dipToPixel(60);

params.x = dipToPixel(140);
params.y = dipToPixel(230);

View view = LayoutInflater.from(mContext).inflate(R.layout.float_view, null);
mWm.addView(view, params);
FloatView floatView = new FloatView(this); //传入Activity Context

2.解决方案

考虑到可能由于传入的Context为Activity导致的问题,进行修改后发现浮动按钮在Android4.4下正常运行。
FloatView floatView = new FloatView(getApplicationContext());
以下则以两方面分析差异原因:
* 使用Activity表现不同的原因。
* 使用Application表现相同的原因。

3.原因分析

##### * 使用Activity表现不同的原因。
首先,可以了解到Activity对getSystemService方法进行了重写:

@Override
    public Object getSystemService(@ServiceName @NonNull String name) {
        if (getBaseContext() == null) {
            throw new IllegalStateException(
                    "System services not available to Activities before onCreate()");
        }

        if (WINDOW_SERVICE.equals(name)) {
            return mWindowManager;
        } else if (SEARCH_SERVICE.equals(name)) {
            ensureSearchManager();
            return mSearchManager;
        }
        return super.getSystemService(name);
    }

WindowManager在Activity初始化的时候就已经被创建。mWindowManager变量在ActivityThread的handleLaunchedActivityactivity.attach()方法中赋值。最终该变量指向了WindowManagerImpl对象。

public WindowManagerImpl createLocalWindowManager(Window parentWindow) {
        return new WindowManagerImpl(mContext, parentWindow);
    }

此处的parentWindow为当前Activity的Window
之后使用WindowManageraddView方法添加按钮,最终会调用WindowManagerGlobaladdView方法。其中该方法有如下语句:

 if (parentWindow != null) {
            parentWindow.adjustLayoutParamsForSubWindow(wparams);
        }

从该该方法开始,表现出现差异:

//Android 7.0
 void adjustLayoutParamsForSubWindow(WindowManager.LayoutParams wp) {
        CharSequence curTitle = wp.getTitle();
        if (wp.type >= WindowManager.LayoutParams.FIRST_SUB_WINDOW &&
                wp.type <= WindowManager.LayoutParams.LAST_SUB_WINDOW) {
            if (wp.token == null) {
                View decor = peekDecorView();
                if (decor != null) {
                    wp.token = decor.getWindowToken();
                }
            }
            //...
        } else if (wp.type >= WindowManager.LayoutParams.FIRST_SYSTEM_WINDOW &&
                wp.type <= WindowManager.LayoutParams.LAST_SYSTEM_WINDOW) {
            if (curTitle == null || curTitle.length() == 0) {
                final StringBuilder title = new StringBuilder(32);
                title.append("Sys").append(wp.type);
                if (mAppName != null) {
                    title.append(":").append(mAppName);
                }
                wp.setTitle(title);
            }
        } else {
            if (wp.token == null) {
                wp.token = mContainer == null ? mAppToken : mContainer.mAppToken;
            }
            if ((curTitle == null || curTitle.length() == 0)
                    && mAppName != null) {
                wp.setTitle(mAppName);
            }
        }
       //...
    }
//Android 4.4
 void adjustLayoutParamsForSubWindow(WindowManager.LayoutParams wp) {
        CharSequence curTitle = wp.getTitle();
        if (wp.type >= WindowManager.LayoutParams.FIRST_SUB_WINDOW &&
                wp.type <= WindowManager.LayoutParams.LAST_SUB_WINDOW) {
            if (wp.token == null) {
                View decor = peekDecorView();
                if (decor != null) {
                    wp.token = decor.getWindowToken();
                }
            }
            //...
        } else {
            if (wp.token == null) {
                wp.token = mContainer == null ? mAppToken : mContainer.mAppToken;
            }
            if ((curTitle == null || curTitle.length() == 0)
                    && mAppName != null) {
                wp.setTitle(mAppName);
            }
        }
       //...
    }

代码大概的逻辑是判断该LayoutParamstype是属于应用窗口级别,还是系统窗口级别。从而为LayoutParams添加对应的token
此处token可以用来绑定ActivityWindowManager。当视图发生变更需要重新遍历或者调用onStop覆盖掉当前Activity时,就可以通过该token找到对应的ViewRootImpl,从而进行View树的相关操作。
在Android7.0时,系统窗口没有设置token,4.4时,token设置为默认的mAppToken,即Activity启动时由AMS创建并传递过来的IBinder对象。
在Activity进入后台后,会调用Activity中的performStop方法,方法中执行以下语句:
WindowManagerGlobal.getInstance().setStoppedState(mToken, true);
从而调用了setStoppedState方法:

public void setStoppedState(IBinder token, boolean stopped) {
        synchronized (mLock) {
            int count = mViews.size();
            for (int i = 0; i < count; i++) {
                //token是Activity的mToken 不为空
                if (token == null || mParams.get(i).token == token) {
                    ViewRootImpl root = mRoots.get(i);
                    root.setWindowStopped(stopped);
                }
            }
        }
    }

当LayoutParams的token与Activity的token匹配时,则取出ViewRootImpl调用setWindowStopped方法,从而执行onStop过程的后续操作。
Android7.0 token为null,不匹配,无法进入判断,则不进行对浮动按钮的后续操作,所以会常驻留窗口。
Android4.4 token为Activity持有的mToken,匹配,会执行后续操作。
##### * 使用Application表现相同的原因。
Application没有对getSystemService进行重写,全部实现交由ContextImpl实现。

@Override
    public Object getSystemService(String name) {
        return SystemServiceRegistry.getSystemService(this, name);
    }

上述从SystemServiceRegistry取出相应服务。SystemServiceRegistry在APP创建时会初始化各种服务。其中包括WindowManager

registerService(Context.WINDOW_SERVICE, WindowManager.class,
                new CachedServiceFetcher<WindowManager>() {
            @Override
            public WindowManager createService(ContextImpl ctx) {
                return new WindowManagerImpl(ctx);
}});

通过Application的getSystemService获得的WindowManager 仍然是WindowManagerImpl对象。Application和Activity创建的区别也在此处。

//Application调用
public WindowManagerImpl(Context context) {
        this(context, null);
    }

//Activity调用
private WindowManagerImpl(Context context, Window parentWindow) {
        mContext = context;
        mParentWindow = parentWindow;
}

Application获得的WindowManagerImplmParentWindow属性为空。这样会在addView时跳过token的赋值。

 if (parentWindow != null) {
            parentWindow.adjustLayoutParamsForSubWindow(wparams);
 }

从而在setStopState方法判断mParams.get(i).token == token中不论是7.0还是4.4中token都为null,不匹配,无法进入后续的stop操作,浮动按钮就常驻窗口。

4. 个人理解

Android系统为每个Activity初始化时添加一个本地的WindowManager引用。由于Activity涉及频繁的View操作,对用WindowManager的使用频率较高,提前初始化对于效率会是一种提升。

2018-08-25 17:36:36 luweicheng24 阅读数 1734

需求描述

类似微信视频、语音时点击返回会形成一个App小窗口浮动在界面上,点击继续是通通话,如下图:
微信小窗口

效果展示

应用内窗口演示

技术分析

其实实现这个功能只需要你细心分析一下就有思路了:首先这个小窗口是浮动在app最上层的视图,其次所有触屏事件需先由该小窗口处理,还有就是小窗口的生命周期和Application也能虽可能不能同生,但是确是可以共死。所以可以在Application中创建一个view添加到WindowManage,这里将视图为view的window的type设置成系统级别的窗口,这样这个window可以在在全局呈现。另外,还需要让这个window可以随手指拖动而滑动,手指释放后会回弹到距离这个释放点最近的屏幕侧边,所以需要重写view 的OnTouch事件。

代码细节实现

  • 创建全局Application,在Application创建的时候初始化一个view,以及一个WindowManager.LayoutParams,并设置get方法,方便外部调用:
private SmallWindowView windowView;
    private WindowManager wm;
    private WindowManager.LayoutParams mLayoutParams;
    public SmallWindowView getWindowView() {
        return windowView;
    }
    public WindowManager getWm() {
        return wm;
    }
    public WindowManager.LayoutParams getmLayoutParams() {
        return mLayoutParams;
    }
    @Override
    public void onCreate() {
        super.onCreate();
        initSmallViewLayout();
    }
    public void initSmallViewLayout() {
        windowView = (SmallWindowView) LayoutInflater.from(this).inflate(R.layout.small_window, null);
        wm = (WindowManager) getSystemService(Context.WINDOW_SERVICE);
        mLayoutParams = new WindowManager.LayoutParams(
                ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT,
                WindowManager.LayoutParams.TYPE_SYSTEM_ALERT,
                WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
                        | WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE,
                PixelFormat.TRANSLUCENT);
        mLayoutParams.gravity = Gravity.NO_GRAVITY;
        //使用非CENTER时,可以通过设置XY的值来改变View的位置
        windowView.setWm(wm);
        windowView.setWmParams(mLayoutParams);
    }
  • 编写一个BaseActivity 实现可供子类来显示隐藏窗口的方法:
  private WindowManager wm;
    private SmallWindowView windowView;
    private WindowManager.LayoutParams mLayoutParams;
    private int OVERLAY_PERMISSION_REQ_CODE = 2;
    private boolean isRange = false;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        wm = ((MyApplication)getApplication()).getWm();
        windowView = ((MyApplication)getApplication()).getWindowView();
        mLayoutParams = ((MyApplication)getApplication()).getmLayoutParams();
    }


    public void alertWindow() {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { // 7.0 以上需要引导用去设置开启窗口浮动权限
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { // 8.0 以上type需要设置成这个
                mLayoutParams.type = WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
            }
            requestDrawOverLays();
        } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { // 6.0 动态申请
            ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.SYSTEM_ALERT_WINDOW}, 1);
        }
    }

    @Override
    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
        super.onRequestPermissionsResult(requestCode, permissions, grantResults);
        if (requestCode == 1 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
            if (wm != null && windowView.getWm() == null) {
                wm.addView(windowView, mLayoutParams);
            }
        } else {
            Toast.makeText(this, "权限申请失败", Toast.LENGTH_SHORT).show();
        }
    }


    private int[] location = new int[2]; // 小窗口位置坐标

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        if (event.getAction() == MotionEvent.ACTION_DOWN) {
            isRange = calcPointRange(event);
        }
        if (isRange) {
            windowView.dispatchTouchEvent(event);
        }
        return super.onTouchEvent(event);
    }

    /**
     *  计算当前点击事件坐标是否在小窗口内
     * @param event
     * @return
     */
    private boolean calcPointRange(MotionEvent event) {
        windowView.getLocationOnScreen(location);
        int width = windowView.getMeasuredWidth();
        int height = windowView.getMeasuredHeight();
        float curX = event.getRawX();
        float curY = event.getRawY();
        if (curX >= location[0] && curX <= location[0] + width && curY >= location[1] && curY <= location[1] + height) {
            return true;
        }
        return false;
    }

    private static final String TAG = "BaseActivity";

    // android 23 以上先引导用户开启这个权限 该权限动态申请不了
    @TargetApi(Build.VERSION_CODES.M)
    public void requestDrawOverLays() {
        if (!Settings.canDrawOverlays(BaseActivity.this)) {
            Toast.makeText(this, "can not DrawOverlays", Toast.LENGTH_SHORT).show();
            Intent intent = new Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION, Uri.parse("package:" + BaseActivity.this.getPackageName()));
            startActivityForResult(intent, OVERLAY_PERMISSION_REQ_CODE);
        } else {
            if (wm != null && windowView.getWindowId() == null) {
                wm.addView(windowView, mLayoutParams);
            }
            Toast.makeText(this, "权限已经授予", Toast.LENGTH_SHORT).show();
        }
    }

    @TargetApi(Build.VERSION_CODES.M)
    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
        if (requestCode == OVERLAY_PERMISSION_REQ_CODE) {
            if (!Settings.canDrawOverlays(this)) {
                Toast.makeText(this, "设置权限拒绝", Toast.LENGTH_SHORT).show();
            } else {
                Toast.makeText(this, "设置权限成功", Toast.LENGTH_SHORT).show();
            }
        }
    }
    // 移除window
    public void dismissWindow() {
        if (wm != null && windowView != null && windowView.getWindowId() != null) {
            wm.removeView(windowView);
        }
    }
  • 自定义一个处理Window内滑动事件的ViewGroup
/**
 * Author   : luweicheng on 2018/8/24 11:22
 * E-mail   :1769005961@qq.com
 * GitHub   : https://github.com/luweicheng24
 * function:
 **/

public class SmallWindowView extends LinearLayout {
    private final int screenHeight;
    private final int screenWidth;
    private int statusHeight;
    private float mTouchStartX;
    private float mTouchStartY;
    private float x;
    private float y;

    private WindowManager wm;
    public WindowManager.LayoutParams wmParams;


    public SmallWindowView(Context context) {
        this(context, null);
    }

    public WindowManager getWm() {
        return wm;
    }

    public void setWm(WindowManager wm) {
        this.wm = wm;
    }

    public WindowManager.LayoutParams getWmParams() {
        return wmParams;
    }

    public void setWmParams(WindowManager.LayoutParams wmParams) {
        this.wmParams = wmParams;
        this.wmParams.x = screenWidth; // 窗口先贴附在右边
    }

    public SmallWindowView(Context context, @Nullable AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public SmallWindowView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        statusHeight = getStatusHeight(context);
        DisplayMetrics dm = getResources().getDisplayMetrics();
        screenHeight = dm.heightPixels;
        screenWidth = dm.widthPixels;

    }


    /**
     * 获得状态栏的高度
     *
     * @param context
     * @return
     */
    public static int getStatusHeight(Context context) {
        int statusHeight = -1;
        try {
            Class clazz = Class.forName("com.android.internal.R$dimen");
            Object object = clazz.newInstance();
            int height = Integer.parseInt(clazz.getField("status_bar_height")
                    .get(object).toString());
            statusHeight = context.getResources().getDimensionPixelSize(height);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return statusHeight;
    }

    boolean isRight = true;

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        x = event.getRawX(); // 触摸点相对屏幕的x坐标
        y = event.getRawY() - statusHeight; // 触摸点相对于屏幕的y坐标
        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
                if (wmParams.x > 0) {
                    isRight = true;
                }
                if (wmParams.x < 0) {
                    isRight = false;
                }
                mTouchStartX = event.getX();// 触摸点在View内的相对x坐标
                mTouchStartY = event.getY();// 触摸点在View内的相对Y坐标
                Log.i("startP", "startX" + mTouchStartX + "====startY" + mTouchStartY);
                break;

            case MotionEvent.ACTION_MOVE:
                updateViewPosition(); //  跟新window布局参数
                break;
            case MotionEvent.ACTION_UP:
                if (wmParams.x <= 0) {  //窗口贴附在左边
                    wmParams.x = Math.abs(wmParams.x) <= screenWidth / 2 ? -screenWidth : screenWidth;
                } else {  // 窗口贴附在右边
                    wmParams.x = wmParams.x <= screenWidth / 2 ? screenWidth : -screenWidth;
                }

                // wmParams.x = screenWidth;
                wmParams.y = (int) (y - screenHeight / 2);// 跟新y坐标
                wm.updateViewLayout(this, wmParams);
                break;
        }
        return true;
    }
    private void updateViewPosition() {
        wmParams.gravity = Gravity.NO_GRAVITY;
        //更新浮动窗口位置参数
        int dx = (int) (mTouchStartX - x);
        int dy = (int) (y-screenHeight / 2);
        if (isRight) {
            wmParams.x = screenWidth / 2 - dx;
        } else {
            wmParams.x = -dx - screenWidth / 2;
        }
        wmParams.y = dy;
        Log.i("winParams", "x : " + wmParams.x + "y :" + wmParams.y + "  dy :" + dy);
        wm.updateViewLayout(this, wmParams);
        //刷新显示
    }
}

以上就能实现一个应用内小窗口了,这里windowManager的布局参数有坑要踩:

  • 不设置Gravity属性,window的坐标是以屏幕左上角为(0,0)原点,而当第一次接受到触摸事件之后就会以默认原点更改为屏幕中心,Github源码,给个小星星
没有更多推荐了,返回首页