Android 类似360悬浮窗口实现源码

  当我们在手机上安装360安全卫士时,手机屏幕上时刻都会出现一个小浮动窗口,点击该浮动窗口可跳转到安全卫士的操作界面,而且该浮动窗口不受其他activity的覆盖影响仍然可见(多米音乐也有相关的和主界面交互的悬浮小窗口)。它能悬浮在手机桌面,且不受Activity界面的影响,说明该悬浮窗口是不隶属于Activity界面的,也就是说,他是隶属于启动它的应用程序所在进程。如360App所在的应用进程,当杀掉它所在的应用进程时,它才会消失。悬浮窗口的实现涉及到WindowManager(基于4.0源码分析),它是一个接口,实现类有WindowManagerImpl,CompatModeWrapper(WindowManagerImpl的内部类),LocalWindowManager(Window的内部类)。  

      通过WindowManager的addView()方法,并设置WindowManager.LayoutParams的相关属性,就可以往WindowManager中加入所需要的View,而根据WindowManager.LayoutParams属性不同,也就能实现不同的效果。比如创建系统顶级窗口,实现悬浮窗口效果。如果需要将View从WindowManager中移除,只需要调用removeView()即可。

1、代码实现主界面为一个Button按钮点击跳转到小悬浮窗口,然后关闭本窗口。

package com.example.suspend;

import android.os.Bundle;
import android.app.Activity;
import android.content.Intent;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.TextView;

public class MainActivity extends Activity {
    private Button suspend;
    private TextView text;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
        initUI();        
    }

    private void initUI() {
        // TODO Auto-generated method stub
    //    WindowService wind = new WindowService();
        suspend = (Button)findViewById(R.id.suspend);
        suspend.setOnClickListener(new suspendListener());
        text = (TextView)findViewById(R.id.text);
        text.setText(MyWindowManager.getUsedPercentValue(getApplicationContext()));
    }
    public class suspendListener implements OnClickListener{

        @Override
        public void onClick(View arg0) {
            // TODO Auto-generated method stub
            //启动悬浮窗口关闭本窗口
            Intent intent = new Intent(MainActivity.this,WindowService.class);
            startService(intent);
            finish();
        }    
    }

}

2、WindowService 中使用了一个定时器,定时为500ms,在定时器里创建小窗口,在启动前先判断是否在桌面

/**
     * 判断当前界面是否桌面
     */
    private boolean isHome(){
        ActivityManager mactivityManager =(ActivityManager)getSystemService(Context.ACTIVITY_SERVICE);
        List<ActivityManager.RunningTaskInfo> rti = mactivityManager.getRunningTasks(1);
        return getHomes().contains(rti.get(0).topActivity.getPackageName());
    }

    /**
     * 获得属于桌面的应用的应用包名称
     * @return 返回包含所有包名的字符串列表
     */
    private List<String> getHomes(){
       List<String> names = new ArrayList<String>();
        PackageManager packageManager = this.getPackageManager();
        Intent intent = new Intent(Intent.ACTION_MAIN);
        intent.addCategory(Intent.CATEGORY_HOME);
        List<ResolveInfo> resolveInfo = packageManager.queryIntentActivities(intent,PackageManager.MATCH_DEFAULT_ONLY);
        for (ResolveInfo ri : resolveInfo){
            names.add(ri.activityInfo.packageName);
            System.out.println("packageName" + names);
            Log.d(TAG,"tag:"+names);
        }
        return names;
    }

完整代码为:

package com.example.suspend;

import android.app.ActivityManager;
import android.app.Service;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.os.Handler;
import android.os.IBinder;
import android.util.Log;
import java.util.ArrayList;
import java.util.List;
import java.util.Timer;
import java.util.TimerTask;

public class WindowService extends Service {
    private static final String TAG = "PACKAGENAME";
    //用于线程中创建或移除悬浮窗。
   private Handler handler = new Handler();
    //定时器,定时进行检测当前应该创建还是移除悬浮
    private Timer timer;
    @Override
    public IBinder onBind(Intent intent) {
        // TODO: Return the communication channel to the service.
        return null;
    }

    /**
     * 启动service的时候,onCreate方法只有第一次会调用,onStartCommand和onStart每次都被调用。
     * onStartCommand会告诉系统如何重启服务,如判断是否异常终止后重新启动,在何种情况下异常终止  
     * 这个整形可以有四个返回值:start_sticky、start_no_sticky、START_REDELIVER_INTENT、START_STICKY_COMPATIBILITY。  
     */
    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        /**
         * 开启定时器,每隔500ms刷新一次
         */
        if (timer == null){
            timer = new Timer();
            timer.scheduleAtFixedRate(new RefreshTask(),0,500);
        }
        return super.onStartCommand(intent, flags, startId);
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
        //Service 被终止的同时也停止定时器继续运行
        timer.cancel();
        timer = null;
    }

    class RefreshTask extends TimerTask{

        @Override
        public void run() {
             //判断当前界面是桌面,且没有悬浮显示,则创建悬浮窗
            if (isHome() && !MyWindowManager.isWindowShowing()){
                handler.post(new Runnable() {
                    @Override
                    public void run() {
                       MyWindowManager.createSmallWindow(getApplicationContext());
                    }
                });
            }
            //当前界面不是桌面,且有悬浮窗口显示,则移除悬浮窗口
            else if (!isHome() && MyWindowManager.isWindowShowing()){
                handler.post(new Runnable() {
                    @Override
                    public void run() {
                         MyWindowManager.removeSmallWindow(getApplicationContext());
                         MyWindowManager.removeBigWindow(getApplicationContext());
                    }
                }) ;
            }
            // 当前界面是桌面,且有悬浮窗显示,则更新内存数据。
            else if (isHome() && MyWindowManager.isWindowShowing()){
                handler.post(new Runnable() {
                    @Override
                    public void run() {
                        MyWindowManager.updateUsedPercent(getApplicationContext());
                    }
                });
            }
        }
    }

    /**
     * 判断当前界面是否桌面
     */
    private boolean isHome(){
        ActivityManager mactivityManager =(ActivityManager)getSystemService(Context.ACTIVITY_SERVICE);
        List<ActivityManager.RunningTaskInfo> rti = mactivityManager.getRunningTasks(1);
        return getHomes().contains(rti.get(0).topActivity.getPackageName());
    }

    /**
     * 获得属于桌面的应用的应用包名称
     * @return 返回包含所有包名的字符串列表
     */
    private List<String> getHomes(){
       List<String> names = new ArrayList<String>();
        PackageManager packageManager = this.getPackageManager();
        Intent intent = new Intent(Intent.ACTION_MAIN);
        intent.addCategory(Intent.CATEGORY_HOME);
        List<ResolveInfo> resolveInfo = packageManager.queryIntentActivities(intent,PackageManager.MATCH_DEFAULT_ONLY);
        for (ResolveInfo ri : resolveInfo){
            names.add(ri.activityInfo.packageName);
            System.out.println("packageName" + names);
            Log.d(TAG,"tag:"+names);
        }
        return names;
    }
}

3、创建窗口方法具体代码有写

public static void createSmallWindow(Context context){
        //WindowManager基本用到:addView,removeView,updateViewLayout
        WindowManager windowManager = getWindowManager(context);
        //获取屏幕宽高 abstract Display  getDefaultDisplay();  //获取默认显示的 Display 对象
        int screenWidth = windowManager.getDefaultDisplay().getWidth();
        int screenHeight = windowManager.getDefaultDisplay().getHeight();

        //设置小悬浮窗口的位置以及相关参数
        if (smallWindowActivity == null) {
            smallWindowActivity = new SmallWindowActivity(context);
            if (smallWindowParams == null) {
                smallWindowParams = new LayoutParams();//
                smallWindowParams.type = LayoutParams.TYPE_PHONE;//设置窗口的window type
                smallWindowParams.format = PixelFormat.RGBA_8888;//设置图片格式,效果为背景透明
                smallWindowParams.flags = LayoutParams.FLAG_NOT_TOUCH_MODAL
                        | LayoutParams.FLAG_NOT_FOCUSABLE;//下面的flags属性的效果形同“锁定”。 悬浮窗不可触摸,不接受任何事件,同时不影响后面的事件响应。
                smallWindowParams.gravity = Gravity.LEFT | Gravity.TOP;//调整悬浮窗口位置在左边中间
                smallWindowParams.width = SmallWindowActivity.viewWidth;//设置悬浮窗口的宽高
                smallWindowParams.height = SmallWindowActivity.viewHeight;
                smallWindowParams.x = screenWidth;//设置悬浮窗口位置
                smallWindowParams.y = screenHeight / 2;
            }
            smallWindowActivity.setParams(smallWindowParams);
            windowManager.addView(smallWindowActivity, smallWindowParams);//将需要加到悬浮窗口中的View加入到窗口中
        }
    }

要移除窗口可使用 windowManager.removeView(smallWindowActivity);

小窗口实现的是显示手机内存百分比,下面为计算内存百分比的方法:

/**
     * 计算已使用内存的百分比,并返回。
     *
     * @param context
     *            可传入应用程序上下文。
     * @return 已使用内存的百分比,以字符串形式返回。
     */
    public static String getUsedPercentValue(Context context) {
        String dir = "/proc/meminfo";
        try {
            FileReader fr = new FileReader(dir);
            BufferedReader br = new BufferedReader(fr, 2048);
            String memoryLine = br.readLine();
            String subMemoryLine = memoryLine.substring(memoryLine.indexOf("MemTotal:"));
            br.close();
            long totalMemorySize = Integer.parseInt(subMemoryLine.replaceAll("\D+", ""));
            long availableSize = getAvailableMemory(context) / 1024;
            int percent = (int) ((totalMemorySize - availableSize) / (float) totalMemorySize * 100);
            return percent + "%";
        } catch (IOException e) {
            e.printStackTrace();
        }
        return "悬浮窗";
    }

    /**
     * 更新小悬浮窗的TextView上的数据,显示内存使用的百分比。
     *
     * @param context
     *            可传入应用程序上下文。
     */
    public static void updateUsedPercent(Context context) {
        if (smallWindowActivity != null) {
            TextView percentView = (TextView) smallWindowActivity.findViewById(R.id.percent);
            percentView.setText(getUsedPercentValue(context));
        }
    }
    /**
     * 获取当前可用内存,返回数据以字节为单位。
     *
     * @param context
     *            可传入应用程序上下文。
     * @return 当前可用内存。
     */
    private static long getAvailableMemory(Context context) {
        ActivityManager.MemoryInfo mi = new ActivityManager.MemoryInfo();
        getActivityManager(context).getMemoryInfo(mi);
        return mi.availMem;
    }

    /**
     * 如果ActivityManager还未创建,则创建一个新的ActivityManager返回。否则返回当前已创建的ActivityManager。
     *
     * @param context
     *            可传入应用程序上下文。
     * @return ActivityManager的实例,用于获取手机可用内存。
     */
    private static ActivityManager getActivityManager(Context context) {
        if (mactivityManager == null) {
            mactivityManager = (ActivityManager)context.getSystemService(Context.ACTIVITY_SERVICE);
        }
        return mactivityManager;
    }

完整的MyWindowManager类代码为

package com.example.suspend;


import android.app.ActivityManager;
import android.content.Context;
import android.graphics.PixelFormat;
import android.view.Gravity;
import android.view.WindowManager.LayoutParams;
import android.view.WindowManager;
import android.widget.TextView;

import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;

public class MyWindowManager{
    //小悬浮窗View的实例
    private static SmallWindowActivity smallWindowActivity;

    //大悬浮窗View的实例
    private static BigWindowActivity bigWindowActivity;

    //小悬浮View的参数
    private static LayoutParams smallWindowParams;

    //大悬浮View的参数
    private  static LayoutParams bigWindowParams;

    //用于控制在屏幕上添加或移除悬浮窗
    private static WindowManager mWindowManager;


    //用于获取手机可用内存
    private  static ActivityManager mactivityManager;

    public static void createSmallWindow(Context context){
        //WindowManager基本用到:addView,removeView,updateViewLayout
        WindowManager windowManager = getWindowManager(context);
        //获取屏幕宽高 abstract Display  getDefaultDisplay();  //获取默认显示的 Display 对象
        int screenWidth = windowManager.getDefaultDisplay().getWidth();
        int screenHeight = windowManager.getDefaultDisplay().getHeight();

        //设置小悬浮窗口的位置以及相关参数
        if (smallWindowActivity == null) {
            smallWindowActivity = new SmallWindowActivity(context);
            if (smallWindowParams == null) {
                smallWindowParams = new LayoutParams();//
                smallWindowParams.type = LayoutParams.TYPE_PHONE;//设置窗口的window type
                smallWindowParams.format = PixelFormat.RGBA_8888;//设置图片格式,效果为背景透明
                smallWindowParams.flags = LayoutParams.FLAG_NOT_TOUCH_MODAL
                        | LayoutParams.FLAG_NOT_FOCUSABLE;//下面的flags属性的效果形同“锁定”。 悬浮窗不可触摸,不接受任何事件,同时不影响后面的事件响应。
                smallWindowParams.gravity = Gravity.LEFT | Gravity.TOP;//调整悬浮窗口位置在左边中间
                smallWindowParams.width = SmallWindowActivity.viewWidth;//设置悬浮窗口的宽高
                smallWindowParams.height = SmallWindowActivity.viewHeight;
                smallWindowParams.x = screenWidth;//设置悬浮窗口位置
                smallWindowParams.y = screenHeight / 2;
            }
            smallWindowActivity.setParams(smallWindowParams);
            windowManager.addView(smallWindowActivity, smallWindowParams);//将需要加到悬浮窗口中的View加入到窗口中
        }
    }

    /**
     * 创建一个大悬浮窗。位置为屏幕正中间。
     *
     * @param context
     *            必须为应用程序的Context.
     */
//    @SuppressWarnings("deprecation")
    public static void createBigWindow(Context context) {
        WindowManager windowManager = getWindowManager(context);
        int screenWidth = windowManager.getDefaultDisplay().getWidth();
        int screenHeight = windowManager.getDefaultDisplay().getHeight();
        if (bigWindowActivity == null) {
            bigWindowActivity = new BigWindowActivity(context);
            if (bigWindowParams == null) {
                bigWindowParams = new LayoutParams();
                bigWindowParams.x = screenWidth / 3 - BigWindowActivity.viewWidth / 3;
                bigWindowParams.y = screenHeight / 3 - BigWindowActivity.viewHeight / 3;
                bigWindowParams.type = LayoutParams.TYPE_PHONE;
                bigWindowParams.format = PixelFormat.RGBA_8888;
                bigWindowParams.gravity = Gravity.LEFT | Gravity.TOP;
                bigWindowParams.width = BigWindowActivity.viewWidth;
                bigWindowParams.height = BigWindowActivity.viewHeight;
            }
            windowManager.addView(bigWindowActivity, bigWindowParams);
        }
    }

    /**
     * 将小悬浮窗从屏幕上移除。
     * abstract void removeViewImmediate(View view);//是removeView(View) 的一个特殊扩展,
     * 在方法返回前能够立即调用该视图层次的View.onDetachedFromWindow() 方法。
     * @param context
     *            必须为应用程序的Context.
     */
    public static void removeSmallWindow(Context context) {
        if (smallWindowActivity != null) {
            WindowManager windowManager = getWindowManager(context);
            windowManager.removeView(smallWindowActivity);//移除悬浮窗口
            smallWindowActivity = null;
        }
    }

    /**
     * 将大悬浮窗从屏幕上移除。
     *
     * @param context
     *            必须为应用程序的Context.
     */
    public static void removeBigWindow(Context context) {
        if (bigWindowActivity != null) {
            WindowManager windowManager = getWindowManager(context);
            windowManager.removeView(bigWindowActivity);
            bigWindowActivity = null;
        }
    }

    /**
     * 是否有悬浮窗(包括小悬浮窗和大悬浮窗)显示在屏幕上。
     *
     * @return 有悬浮窗显示在桌面上返回true,没有的话返回false。
     */
    public static boolean isWindowShowing() {
        return smallWindowActivity != null || bigWindowActivity != null;
        //return smallWindowActivity != null;
    }

    /**
     * 如果WindowManager还未创建,则创建一个新的WindowManager返回。否则返回当前已创建的WindowManager。
     *
     * @param context
     *            必须为应用程序的Context.
     * @return WindowManager的实例,用于控制在屏幕上添加或移除悬浮窗。
     */
    private static WindowManager getWindowManager(Context context) {
        if (mWindowManager == null) {
            mWindowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
        }
        return mWindowManager;
    }

    /**
     * 计算已使用内存的百分比,并返回。
     *
     * @param context
     *            可传入应用程序上下文。
     * @return 已使用内存的百分比,以字符串形式返回。
     */
    public static String getUsedPercentValue(Context context) {
        String dir = "/proc/meminfo";
        try {
            FileReader fr = new FileReader(dir);
            BufferedReader br = new BufferedReader(fr, 2048);
            String memoryLine = br.readLine();
            String subMemoryLine = memoryLine.substring(memoryLine.indexOf("MemTotal:"));
            br.close();
            long totalMemorySize = Integer.parseInt(subMemoryLine.replaceAll("\D+", ""));
            long availableSize = getAvailableMemory(context) / 1024;
            int percent = (int) ((totalMemorySize - availableSize) / (float) totalMemorySize * 100);
            return percent + "%";
        } catch (IOException e) {
            e.printStackTrace();
        }
        return "悬浮窗";
    }

    /**
     * 更新小悬浮窗的TextView上的数据,显示内存使用的百分比。
     *
     * @param context
     *            可传入应用程序上下文。
     */
    public static void updateUsedPercent(Context context) {
        if (smallWindowActivity != null) {
            TextView percentView = (TextView) smallWindowActivity.findViewById(R.id.percent);
            percentView.setText(getUsedPercentValue(context));
        }
    }
    /**
     * 获取当前可用内存,返回数据以字节为单位。
     *
     * @param context
     *            可传入应用程序上下文。
     * @return 当前可用内存。
     */
    private static long getAvailableMemory(Context context) {
        ActivityManager.MemoryInfo mi = new ActivityManager.MemoryInfo();
        getActivityManager(context).getMemoryInfo(mi);
        return mi.availMem;
    }

    /**
     * 如果ActivityManager还未创建,则创建一个新的ActivityManager返回。否则返回当前已创建的ActivityManager。
     *
     * @param context
     *            可传入应用程序上下文。
     * @return ActivityManager的实例,用于获取手机可用内存。
     */
    private static ActivityManager getActivityManager(Context context) {
        if (mactivityManager == null) {
            mactivityManager = (ActivityManager)context.getSystemService(Context.ACTIVITY_SERVICE);
        }
        return mactivityManager;
    }

}

4、小窗口的代码继承了LinearLayout,可实现手动触摸移动,分别设置了手指点击下、移动和离开的处理。实现代码为:

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        switch (event.getAction()){
            case MotionEvent.ACTION_DOWN:
                //手指按下时记录必要数据,纵坐标的值都需要减去状态栏高度
                xInView = event.getX();
                yInView = event.getY();
                xDownInScreen = event.getRawX();
                yDownInScreen = event.getRawY() - getStatusBarHeight();
                xInScreen = event.getRawX();
                yInScreen = event.getRawY() - getStatusBarHeight();
                break;
            case MotionEvent.ACTION_MOVE:
                xInScreen = event.getRawX();
                yInScreen = event.getRawY()-getStatusBarHeight();
                //手指一动的时候就更新悬浮窗位置
                updateViewPosition();
                break;
            case MotionEvent.ACTION_UP:
                //如果手指离开屏幕时,xDownInScreen和xInScreen相等,且yDownInScreen == yInScreen
                //则视为触发
                if (xDownInScreen == xInScreen && yDownInScreen == yInScreen) {
                    MyWindowManager.createBigWindow(getContext());//创建大窗口
                    MyWindowManager.removeSmallWindow(getContext());//移除小窗口
                    Toast.makeText(getContext(), "手指离开屏幕!", Toast.LENGTH_SHORT).show();
                }
                break;
            default:
                break;
        }
        return true;
    }

完整代码:

package com.example.suspend;
import android.content.Context;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
import android.widget.LinearLayout;
import android.widget.TextView;
import android.widget.Toast;
import android.view.WindowManager;

import java.lang.reflect.Field;

public class SmallWindowActivity extends LinearLayout {
    
    public static int viewWidth;//小悬浮宽度
    public static int viewHeight;//小悬浮高度

    private static int statusBarHeight;//状态栏高度

    private WindowManager windowManager;//更新小悬浮的位置
    private WindowManager.LayoutParams mParams;//小悬浮高度
    
    private float xInScreen;//记录当前手指位置在屏幕上的横坐标值
    private float yInScreen;//记录当前手指位置在屏幕上的纵坐标值

    private float xDownInScreen;//记录手指按下时在屏幕上的横坐标的值
    private float yDownInScreen;//记录手指按下时在屏幕上的纵坐标的值

    private float xInView;//记录手指按下时在小悬浮窗的View上的横坐标的值
    private float yInView;//记录手指按下时在小悬浮窗的View上的纵坐标的值
    /**
     *
     * @param context
     */
    public SmallWindowActivity(Context context) {
        super(context);
        windowManager = (WindowManager)context.getSystemService(Context.WINDOW_SERVICE);
        LayoutInflater.from(context).inflate(R.layout.activity_small_window,this);
        View view = findViewById(R.id.small_window_layout);
        //获取手机屏幕宽高
        viewWidth = view.getLayoutParams().width;
        viewHeight = view.getLayoutParams().height;
        //显示手机内存空间百分比
        TextView percentView = (TextView)findViewById(R.id.percent);
        percentView.setText(MyWindowManager.getUsedPercentValue(context));
    }
    /**
     * 手指触摸屏幕处理
     */
    @Override
    public boolean onTouchEvent(MotionEvent event) {
        switch (event.getAction()){
            case MotionEvent.ACTION_DOWN:
                //手指按下时记录必要数据,纵坐标的值都需要减去状态栏高度
                xInView = event.getX();
                yInView = event.getY();
                xDownInScreen = event.getRawX();
                yDownInScreen = event.getRawY() - getStatusBarHeight();
                xInScreen = event.getRawX();
                yInScreen = event.getRawY() - getStatusBarHeight();
                break;
            case MotionEvent.ACTION_MOVE:
                xInScreen = event.getRawX();
                yInScreen = event.getRawY()-getStatusBarHeight();
                //手指一动的时候就更新悬浮窗位置
                updateViewPosition();
                break;
            case MotionEvent.ACTION_UP:
                //如果手指离开屏幕时,xDownInScreen和xInScreen相等,且yDownInScreen == yInScreen
                //则视为触发
                if (xDownInScreen == xInScreen && yDownInScreen == yInScreen) {
                    MyWindowManager.createBigWindow(getContext());//创建大窗口
                    MyWindowManager.removeSmallWindow(getContext());//移除小窗口
                    Toast.makeText(getContext(), "手指离开屏幕!", Toast.LENGTH_SHORT).show();
                }
                break;
            default:
                break;
        }
        return true;
    }

    //将悬浮窗的参数传入,用于更新小悬浮窗的位置
    public void setParams(WindowManager.LayoutParams params) {
        mParams = params;
    }

    //更新小悬浮窗在屏幕中的位置
    private void updateViewPosition() {
        mParams.x = (int) (xInScreen - xInView);
        mParams.y = (int) (yInScreen - yInView);
        windowManager.updateViewLayout(this, mParams);
    }

    /**
     * 用于获取状态栏高度
     * @return 返回状态栏高度的像素值
     */
    private int getStatusBarHeight() {
        if (statusBarHeight == 0) {
            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);
                statusBarHeight = getResources().getDimensionPixelSize(x);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        return statusBarHeight;
    }
}

小窗口仅实现了显示内存百分比,添加了一个TextView,小窗口的布局代码为:

<?xml version="1.0" encoding="UTF-8"?>
<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/small_window_layout"
    android:layout_width="60dip"
    android:layout_height="25dip"
    android:background="@drawable/bg_small"
    >
    <TextView
        android:id="@+id/percent"
        android:layout_width="fill_parent"
        android:layout_height="fill_parent"
        android:gravity="center"
        android:textColor="#ffffff"
        />
</LinearLayout>

5、当点击小窗口的时候添加了监听就是当手指离开的时候启动创建一个另一个窗口然后关闭本窗口。

大窗口也是实现点击可以随意移动,还添加了两个按钮的功能,发送短信和返回。实现代码为:

package com.example.suspend;

import android.content.Context;
import android.content.Intent;
import android.util.DisplayMetrics;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
import android.widget.Button;
import android.widget.LinearLayout;

public class BigWindowActivity extends LinearLayout {

    //记录大悬浮窗的宽度
    public static int viewWidth;

    //记录大悬浮窗高度
    public static int viewHeight;
    
    private Button phone,sms,app,music;
    int screenWidth,screenHeight;
    int lastX,lastY;//记录移动的最后的位置
    int dx,dy;
    
    public BigWindowActivity(final Context context) {
        super(context);
         LayoutInflater.from(context).inflate(R.layout.big_window, this);
         View view  = findViewById(R.id.big_window_layout);
         viewWidth = view.getLayoutParams().width;
         viewHeight = view.getLayoutParams().height;
              
         initUI();
         
    }

     private void initUI() {
        // TODO Auto-generated method stub
         //获取屏幕的分辨率
        DisplayMetrics dm = getResources().getDisplayMetrics();
        screenWidth = dm.widthPixels;
        screenHeight = dm.heightPixels-50;
        Button back = (Button)findViewById(R.id.fanhui);
        //添加触摸监听
        back.setOnTouchListener(new OnTouchListener() {
            
            @Override
            public boolean onTouch(View v, MotionEvent event) {
                // TODO Auto-generated method stub
                //获取Action
                int ea = event.getAction();
                Log.i("TAG","Touch:"+ea);
                switch (ea){
                    case MotionEvent.ACTION_DOWN:
                        lastX = (int)event.getRawX();
                        lastY = (int)event.getRawY();
                        break;
                    case MotionEvent.ACTION_MOVE:
                        //移动中动态设置位置
                          mobilesetting(v,event);
                        break;
                    case MotionEvent.ACTION_UP://当手指离开的时候执行
                        if (dx==dy){
                            comeback(getContext());
                        }
                        break;
                }
                return false;
            }
        });        
        
        sms = (Button)findViewById(R.id.SMS);
        sms.setOnTouchListener(new OnTouchListener() {
            
            @Override
            public boolean onTouch(View v, MotionEvent event) {
                // TODO Auto-generated method stub
                int ea = event.getAction();
                Log.i("TAG","Touch:"+ea);
                switch (ea){
                    case MotionEvent.ACTION_DOWN:
                        lastX = (int)event.getRawX();
                        lastY = (int)event.getRawY();
                        break;
                    case MotionEvent.ACTION_MOVE:
                        //移动中动态设置位置
                          mobilesetting(v,event);
                        break;
                    case MotionEvent.ACTION_UP://当手指离开的时候执行
                        if (dx==dy){
                            sms();
                        }
                        break;
                }
                
                return false;
            }
        });
    }
     /**
      * 点击返回移除大窗口创建小窗口
      * @param context
      */
    public void comeback(Context context){
            // 点击返回的时候,移除大悬浮窗,创建小悬浮窗
            MyWindowManager.removeBigWindow(context);
            MyWindowManager.createSmallWindow(context);
     }
    //发短信
     public void sms(){
         Intent it = new Intent(Intent.ACTION_VIEW);
         it.putExtra("sms_body", "The SMS text");
         it.setType("vnd.android-dir/mms-sms");
         it.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
         getContext().startActivity(it);
         comeback(getContext());
     }
    
    /**
     * 移动控件设置位置
     * @param v
     * @param event
     */
    public void mobilesetting(View v,MotionEvent event){
        //移动中动态设置位置
        dx =  (int)event.getRawX()-lastX;//移动中x当前位置
        dy = (int)event.getRawY()-lastY;

        int left = v.getLeft()+dx;
        int top = v.getTop()+dy;
        int right = v.getRight()+dx;
        int bottom = v.getBottom()+dy;

        if(left<0){
            left=0;
            right = left+v.getWidth();//0
        }
        if (right>screenWidth){
            right = screenWidth;
            left = right - v.getWidth();//max
        }
        if(top < 0){
            top = 0;
            bottom = top + v.getHeight();
        }
        if(bottom > screenHeight){
            bottom = screenHeight;
            top = bottom - v.getHeight();
        }
        v.layout(left, top, right, bottom);
        //将当前的位置再次设置
        lastX = (int) event.getRawX();
        lastY = (int) event.getRawY();
    }
//     @Override
//    public boolean onTouchEvent(MotionEvent event) {
//        // TODO Auto-generated method stub
//        //点击屏幕弹出的框会消失
//        //popupWindow.dismiss();
//        return super.onTouchEvent(event);
//    }
}

布局代码为四个按钮控件:

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:id="@+id/big_window_layout"
     >
    
<Button
        android:layout_marginTop="200dip"
        android:layout_marginLeft="150dip"
        android:id="@+id/fanhui"
        android:layout_width="@dimen/bigwidth"
        android:layout_height="@dimen/bigheight"
        android:textColor="#ffffff"
        android:textSize="@dimen/bigtextsize"
        android:background="@xml/shape"
        android:text="@string/rt"
        />

    <Button
        android:id="@+id/app"
        android:layout_width="@dimen/bigwidth"
        android:layout_height="@dimen/bigheight"
        android:layout_above="@+id/music"
        android:layout_alignLeft="@+id/fanhui"
        android:background="@xml/shape"
        android:textSize="@dimen/bigtextsize"
        android:text="@string/appone" />

    <Button
        android:id="@+id/music"
        android:layout_width="@dimen/bigwidth"
        android:layout_height="@dimen/bigheight"
        android:layout_alignBaseline="@+id/fanhui"
        android:layout_alignBottom="@+id/fanhui"
        android:layout_toLeftOf="@+id/app"
        android:textSize="@dimen/bigtextsize"
        android:text="@string/mu"
        android:background="@xml/shape"
         />

    <Button
        android:id="@+id/SMS"
        android:layout_width="@dimen/bigwidth"
        android:layout_height="@dimen/bigheight"
        android:layout_below="@+id/fanhui"
        android:layout_toRightOf="@+id/music"
        android:textSize="@dimen/bigtextsize"
        android:background="@xml/shape"
        android:text="@string/sendsms" />

    <Button
        android:id="@+id/Phone"
        android:layout_width="@dimen/bigwidth"
        android:layout_height="@dimen/bigheight"
        android:layout_alignBaseline="@+id/fanhui"
        android:layout_alignBottom="@+id/fanhui"
        android:textSize="@dimen/bigtextsize"
        android:layout_toRightOf="@+id/fanhui"
        android:background="@xml/shape"
        android:text="@string/callphone" />
    
</RelativeLayout>

控件的实现效果是在xml里面添加了shape.xml,设置了控件的颜色跟形状。

<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
    android:shape="oval">          
    <!-- 实心 -->            
    <solid android:color="#ff9d77"/>            
    <!-- 渐变 -->            
    <gradient            
        android:startColor="#ff8c00"            
        android:endColor="#FFFFFF"            
        android:angle="270" />            
    <!-- 描边 -->            
    <stroke            
        android:width="2dp"            
        android:color="#dcdcdc" />            
    <!-- 圆角 -->            
    <corners            
        android:radius="2dp" />            
    <padding            
        android:left="10dp"            
        android:top="10dp"            
        android:right="10dp"            
        android:bottom="10dp" />            
</shape>

6、使用到的权限为:

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.suspend"
    android:versionCode="1"
    android:versionName="1.0" >

    <uses-sdk
        android:minSdkVersion="8"
        android:targetSdkVersion="18" />
    <!-- 添加悬浮窗口权限 -->
    <uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/>
    <uses-permission android:name="android.permission.INTERNET"/>
    <uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS"/>
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
    <application
        android:allowBackup="true"
        android:icon="@drawable/ic_launcher"
        android:label="@string/app_name"
        android:theme="@style/AppTheme" >
        <activity
            android:name="com.example.suspend.MainActivity"
            android:label="@string/app_name" >
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
        <service android:name=".WindowService"></service>
    </application>

</manifest>

实现的效果图:




代码下载地址:点击打开链接

原文地址:https://www.cnblogs.com/zhujiabin/p/9172755.html